0%

应用层禁止USB存储设备写入功能(1)

前言

在应用层禁用USB存储设备有两个方向:一个是完全的禁用USB存储设备,既不能读也不能写。
另一个是限制USB存储设备只能读不能写。

完全禁用

所有的USB存储设备都要挂接 usbstor.sys 驱动中,所以我们只要禁止这个驱动启动,就可以
禁止所有的USB存储设备,禁止的方法包括:直接把这个驱动改个名字,或者移动到别的文件夹,
还可以去注册表把 usbstor 服务的启动方式改为 禁用 等等。

在Win10系统中,某些USB存储设备会挂接到 uaspstor.sys 驱动中,也是同样的禁用方式。

以上方法是在USB存储设备 加载之前 进行拦截,我们还可以在 加载之后 再去阻止。比如:去掉
USB存储设备分区的盘符,主动弹出USB存储设备等。

那么如何知道USB存储设备已接入了呢,在有硬件设备加载后会触发 WM_DEVICECHANGE 消息,其
wParam 参数为 DBT_DEVICEARRIVAL 时,表示设备接入,对应的 lParam 参数是描述设备信息的
结构体 DEV_BROADCAST_HDR,其成员 dbch_devicetype 描述的就是设备类型。

1
2
3
4
5
typedef struct _DEV_BROADCAST_HDR {
DWORD dbch_size;
DWORD dbch_devicetype;
DWORD dbch_reserved;
} DEV_BROADCAST_HDR, *PDEV_BROADCAST_HDR;

dbch_devicetype 类型为 DEV_BROADCAST_VOLUME 时,表示 DEV_BROADCAST_HDR 结构体实际上是
DEV_BROADCAST_VOLUME 类型的结构体。

1
2
3
4
5
6
7
typedef struct _DEV_BROADCAST_VOLUME {
DWORD dbcv_size;
DWORD dbcv_devicetype;
DWORD dbcv_reserved;
DWORD dbcv_unitmask;
WORD dbcv_flags;
} DEV_BROADCAST_VOLUME, *PDEV_BROADCAST_VOLUME;

其中 dbcv_unitmask 参数,按位表示加载设备的 盘符,即bit[0]表示A盘符,bit[1]表示B盘符,以此类推。
删除分区的盘符使用 DeleteVolumeMountPoint 函数,参数例如 "C:\" 的形式。

1
2
3
BOOL WINAPI DeleteVolumeMountPoint(
_In_ LPCTSTR lpszVolumeMountPoint
);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
case WM_DEVICECHANGE:
if (wParam == DBT_DEVICEARRIVAL)
{
if (((PDEV_BROADCAST_HDR)lParam)->dbch_devicetype == DBT_DEVTYP_VOLUME)
{
DWORD dwUnit = ((PDEV_BROADCAST_VOLUME)lParam)->dbcv_unitmask;
char szDriver[4] = "C:\\";
for (char i = 0; i < 26; i++)
{
szDriver[0] = ((dwUnit >> i) & 1) * ('A' + i);
if (szDriver[0] != 0)
{
UINT uiType = GetDriveType(szDriver);
if (uiType == DRIVE_REMOVABLE)
{
// 删除分区对应的盘符
DeleteVolumeMountPoint(szDriver);
// 弹出对应盘符的设备
EjectUsbStorVolume(szDriver[0]);
}
}
}
}
return (INT_PTR)TRUE;
}
break;

弹出USB存储设备使用 CM_Request_Device_Eject 函数,但是这个函数使用的是 设备实例 来进行弹出,
所以就需要查找到 盘符 对应的 设备实例,我们可以通过 STORAGE_DEVICE_NUMBER 数据进行匹配。

1
2
3
4
5
6
7
8
9
CMAPI
CONFIGRET
WINAPI CM_Request_Device_Eject(
_In_ DEVINST dnDevInst,
_Out_opt_ PPNP_VETO_TYPE pVetoType,
_Out_opt_ LPTSTR pszVetoName,
_In_ ULONG ulNameLength,
_In_ ULONG ulFlags
);
1
2
3
4
5
typedef struct _STORAGE_DEVICE_NUMBER {
DEVICE_TYPE DeviceType;
ULONG DeviceNumber;
ULONG PartitionNumber;
} STORAGE_DEVICE_NUMBER, *PSTORAGE_DEVICE_NUMBER;

如下为使用 盘符 查询 STORAGE_DEVICE_NUMBER 和枚举所有 GUID_DEVINTERFACE_DISK 设备查询的代码

1
2
3
4
5
#include <WinIoCtl.h>
#include <Setupapi.h>
#include <Cfgmgr32.h>
#pragma comment(lib, "Setupapi.lib")
#pragma comment(lib, "Cfgmgr32.lib")
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
BOOL EjectUsbStorVolume(char Ch)
{
if ((Ch < 'A') || (Ch > 'Z')) return FALSE;
// 打开目标卷
char szDriverVolume[8] = "\\\\.\\C:";
szDriverVolume[4] = Ch;
HANDLE hDevice = CreateFile(
szDriverVolume, GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) return FALSE;
// 查询目标卷的设备数字
DWORD dwRet = 0;
STORAGE_DEVICE_NUMBER stDevNum = { 0 };
BOOL bRet = DeviceIoControl(hDevice, IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &stDevNum, sizeof(stDevNum), &dwRet, NULL);
CloseHandle(hDevice); // 先关句柄
if (!bRet) return FALSE;
// 根据设备数字查询对应设备实例
DEVINST DevInst = GetDevInstByDevNum(stDevNum);
if (DevInst == 0) return FALSE;
// 获取父设备实例
DEVINST DevInstParent = 0;
CONFIGRET cr = CM_Get_Parent(&DevInstParent, DevInst, 0);
if (cr != CR_SUCCESS) return FALSE;
// 弹出设备实例对应的设备
ULONG ulStatus = 0;
ULONG ulProblemNum = 0;
cr = CM_Get_DevNode_Status(&ulStatus, &ulProblemNum, DevInstParent, 0);
if ((cr == CR_SUCCESS) && (ulStatus & DN_REMOVABLE))
{
CM_Request_Device_Eject(DevInstParent, NULL, NULL, 0, 0);
return TRUE;
}
return FALSE;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
DEVINST GetDevInstByDevNum(STORAGE_DEVICE_NUMBER &stDevNum)
{
// 获取设备类信息的句柄
DWORD dwFlag = DIGCF_PRESENT | DIGCF_DEVICEINTERFACE;
HDEVINFO hDevInfo = SetupDiGetClassDevs(
&GUID_DEVINTERFACE_DISK, NULL, NULL, dwFlag);
if (hDevInfo == INVALID_HANDLE_VALUE) return FALSE;
// 根据设备类信息句柄枚举设备信息
DWORD dwRet = 0;
BOOL bRet = FALSE;
DWORD dwDevInst = 0;
DWORD dwDevIndex = 0;
HANDLE hDevice = NULL;
char Buffer[512] = { 0 };
SP_DEVINFO_DATA DevInfoData = { 0 };
SP_DEVICE_INTERFACE_DATA DevInterfaceData = { 0 };
PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData =
(PSP_DEVICE_INTERFACE_DETAIL_DATA)Buffer;
STORAGE_DEVICE_NUMBER stDevNumTemp = { 0 };
do
{
// 查询设备信息
ZeroMemory(&DevInfoData, sizeof(SP_DEVINFO_DATA));
DevInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
if (!SetupDiEnumDeviceInfo(hDevInfo, dwDevIndex, &DevInfoData)) break;
dwDevIndex++;
// 获取服务信息
ZeroMemory(Buffer, sizeof(Buffer));
if (!SetupDiGetDeviceRegistryProperty(hDevInfo, &DevInfoData,
SPDRP_ENUMERATOR_NAME, NULL, (PBYTE)Buffer, sizeof(Buffer), NULL)) continue;
// 判断是不是USBSTOR和UASPStor服务
if ((_stricmp(Buffer, "USBSTOR") != 0) &&
(_stricmp(Buffer, "UASPStor") != 0)) continue;
// 查询设备接口信息
ZeroMemory(&DevInterfaceData, sizeof(SP_DEVICE_INTERFACE_DATA));
DevInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
if (!SetupDiEnumDeviceInterfaces(hDevInfo, NULL,
&GUID_DEVINTERFACE_DISK, dwDevIndex - 1, &DevInterfaceData)) continue;
// 查询设备的路径
ZeroMemory(Buffer, sizeof(Buffer));
pDevDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
if (!SetupDiGetDeviceInterfaceDetail(hDevInfo,
&DevInterfaceData, pDevDetailData, sizeof(Buffer), NULL, NULL)) continue;
// 打开目标设备
hDevice = CreateFile(
pDevDetailData->DevicePath, GENERIC_WRITE | GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDevice == INVALID_HANDLE_VALUE) continue;
// 查询目标的设备数字
ZeroMemory(&stDevNumTemp, sizeof(STORAGE_DEVICE_NUMBER));
bRet = DeviceIoControl(hDevice, IOCTL_STORAGE_GET_DEVICE_NUMBER,
NULL, 0, &stDevNumTemp, sizeof(stDevNumTemp), &dwRet, NULL);
CloseHandle(hDevice); // 先关句柄
if (!bRet) continue;
// 对比设备类型和设备号
if ((stDevNumTemp.DeviceType == stDevNum.DeviceType) &&
(stDevNumTemp.DeviceNumber == stDevNum.DeviceNumber))
{
dwDevInst = DevInfoData.DevInst;
break;
}
} while (1);
// 获取的设备列表要手动销毁
SetupDiDestroyDeviceInfoList(hDevInfo);
return dwDevInst;
}

限制只读

我们可以通过配置注册表的方式来使U盘只读,打开注册表以下路径

1
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\StorageDevicePolicies

如果未找到 StorageDevicePolicies 项,就手动创建该注册表项,然后在该项创建一个 DWORD 类型
WriteProtect 注册表值,配置这个值为 0 时,不限制只读,而配置成 1 时,U盘就变成只读的了。

注册表路径

注意:改策略不是实时生效的,在改完以后新接入的U盘才受控制。