【Windows编程】基于USB设备的开机锁

*代码参考了《黑客防线2011精华奉献本上册》的文章《U盘打造开机锁》。

原理

   U盘是一种即插即用的可移动设备(PnP),它具有VID、PID以及产品序列号等可以标识其身份。引用一下文章来浅要介绍一下VID与PID的相关内容:

  不同的U盘具有不同的序列号,因此我们可以通过识别U盘的序列号来判断该计算机上是否插有该U盘,如果没有则会强制关机,如果有U盘的话,则可以进行操作。这就是通过U盘来实现开机锁的基本原理。

相关内容

  开发环境是VIsual Studio 2005+WDK7600。需要注意的是WDK是必须的。因为程序中需要用到WDK中的一些头文件与库文件。读者可以自行去微软网站下载相关软件。

  新建Win32项目,选择空项目、不预编译头文件。然后再在项目当中添加一个cpp源文件(我所用的名字是Upansuo.cpp)。程序就是在Upansuo.cpp中实现的。程序主要完成两方面的工作:1)获取USB设备,从中筛选出USB移动设备(以下直接称之为U盘),并获取其信息。2)根据获得的U盘信息,来进行启动管理。以下根据代码来理解程序的执行流程与实现原理。

代码解释

  从WinMain函数开始看起,Windows程序的执行都是从它开始的:

1 int _stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPreInstance,LPSTR lpCmdLine,int nShowCmd)
2 {
3 int i,nDevice; //USB设备的数量
4 int ndevice=0; //U盘设备的数量
5 wchar_t *szDevicePath[MAX_DEVICE]; //存储设备路径
6 HANDLE hDevice;
7
8 PSTORAGE_DEVICE_DESCRIPTOR DeviceDesc;

    如同注释所说的,nDevice为USB设备的数量,ndevice为U盘(可移动的数据存储设备)的数量。szDevicePath为存储设备路径,它是wchar_t类型的指针数组。wchar_t是微软运行时库(The Microsoft run-time library )所定义的标准类型(standard types),中文称它为“宽字符”,使得不同语言的文字都有相应的编码,从而能够显示相当多的各国字符,而不仅仅是ANSI字符中的那么一点儿。MAX_DEVICE为自定义的常量:

1 #define MAX_DEVICE 100

  hDevice为句柄,如同名字所暗示的,它在程序中用来标识设备,用以返回设备的句柄。PSTORAGE_DEVICE_DESCRIPTOR是"winioctl.h"中定义的一个数据结构的指针:

 1 typedef __struct_bcount(Size) struct _STORAGE_DEVICE_DESCRIPTOR {
2
3 //
4 // Sizeof(STORAGE_DEVICE_DESCRIPTOR)
5 //
6
7 DWORD Version;
8
9 //
10 // Total size of the descriptor, including the space for additional
11 // data and id strings
12 //
13
14 DWORD Size;
15
16 //
17 // The SCSI-2 device type
18 //
19
20 BYTE DeviceType;
21
22 //
23 // The SCSI-2 device type modifier (if any) - this may be zero
24 //
25
26 BYTE DeviceTypeModifier;
27
28 //
29 // Flag indicating whether the device's media (if any) is removable. This
30 // field should be ignored for media-less devices
31 //
32
33 BOOLEAN RemovableMedia;
34
35 //
36 // Flag indicating whether the device can support mulitple outstanding
37 // commands. The actual synchronization in this case is the responsibility
38 // of the port driver.
39 //
40
41 BOOLEAN CommandQueueing;
42
43 //
44 // Byte offset to the zero-terminated ascii string containing the device's
45 // vendor id string. For devices with no such ID this will be zero
46 //
47
48 DWORD VendorIdOffset;
49
50 //
51 // Byte offset to the zero-terminated ascii string containing the device's
52 // product id string. For devices with no such ID this will be zero
53 //
54
55 DWORD ProductIdOffset;
56
57 //
58 // Byte offset to the zero-terminated ascii string containing the device's
59 // product revision string. For devices with no such string this will be
60 // zero
61 //
62
63 DWORD ProductRevisionOffset;
64
65 //
66 // Byte offset to the zero-terminated ascii string containing the device's
67 // serial number. For devices with no serial number this will be zero
68 //
69
70 DWORD SerialNumberOffset;
71
72 //
73 // Contains the bus type (as defined above) of the device. It should be
74 // used to interpret the raw device properties at the end of this structure
75 // (if any)
76 //
77
78 STORAGE_BUS_TYPE BusType;
79
80 //
81 // The number of bytes of bus-specific data which have been appended to
82 // this descriptor
83 //
84
85 DWORD RawPropertiesLength;
86
87 //
88 // Place holder for the first byte of the bus specific property data
89 //
90
91 BYTE RawDeviceProperties[1];
92
93 } STORAGE_DEVICE_DESCRIPTOR, *PSTORAGE_DEVICE_DESCRIPTOR;

  第一行的代码不用管它,只需要简单地理解成typedef struct _STORAGE_DEVICE_DESCRIPTOR即可。下面看它的成员:

Version

  按照头文件中的注释,它即是这个数据结构的大小Sizeof(STORAGE_DEVICE_DESCRIPTOR)。

Size

  描述符(descriptor)的总共大小,包括了附加数据和ID字符串的长度。

DeviceType

  指定设备的小型计算机系统接口(SCSI)的类型。应该是这样翻译的。MSDN原文是:“Specifies the device type as defined by the Small Computer Systems Interface (SCSI) specification.”

DeviceTypeModifier

  指定设备的SCSI Modifier,如果没有SCSI Modifier,则成员的值为0.

RemovableMedia

  BOOLEAN类型,指示设备的媒介(Media)是不是可移动(拆除)的。如果设备没有媒介,则该成员被忽略。

CommandQueueing

  BOOLEAN类型,指示设备是否支持mulitple outstanding commands,端口设备负责同步。

VendorIdOffset

  指示生产商ID的偏移。如果设备不存在ANSII字符的生产商ID,该成员的值则为0.

ProductIdOffset

  指示产品ID的偏移。如果设备部存在ANSII字符的产品ID,该成员的值为0.

ProductRevisionOffset

  指示产品修正ID的偏移。如果设备部存在ANSII字符的产品修正ID,该成员的值为0.

(大概我们可以将VendorIdOffset理解为主ID号,而将ProductIdOffset理解为副ID号)

SerialNumberOffset

  指示产品序列号的偏移。如果设备没有序列号,则该成员的值为0.

BusType

  该成员的类型为STORAGE_BUS_TYPE。指示设备的总线类型。它在结构的最后(说明设备的属性)(interpret the raw device properties at the end of this structure)。BusType是一个枚举类型,BusTypeUsb对应的值为0x07:

 1 typedef enum _STORAGE_BUS_TYPE {
2 BusTypeUnknown = 0x00,
3 BusTypeScsi,
4 BusTypeAtapi,
5 BusTypeAta,
6 BusType1394,
7 BusTypeSsa,
8 BusTypeFibre,
9 BusTypeUsb,
10 BusTypeRAID,
11 BusTypeiScsi,
12 BusTypeSas,
13 BusTypeSata,
14 BusTypeSd,
15 BusTypeMmc,
16 BusTypeVirtual,
17 BusTypeFileBackedVirtual,
18 BusTypeMax,
19 BusTypeMaxReserved = 0x7F
20 } STORAGE_BUS_TYPE, *PSTORAGE_BUS_TYPE;

RawPropertiesLength

  附加在此描述符后的详细的总线数据(bus-specific data)的长度。

RawDeviceProperties

  存放总线详细的属性信息(bus specific property data)的第一字节。(翻译可能不对,MSDN原文是“Place holder for the first byte of the bus specific property data”)

  再回到原来的程序当中,接下来的两行:

1     DeviceDesc=(PSTORAGE_DEVICE_DESCRIPTOR)new BYTE[sizeof(STORAGE_DEVICE_DESCRIPTOR)+512-1];
2 DeviceDesc->Size=sizeof(STORAGE_DEVICE_DESCRIPTOR)+512-1;

  第一行的代码为DeviceDesc分配了551字节的内存(sizeof(STORAGE_DEVICE_DESCRIPTOR)+512-1),然后设置了DeviceDesc的成员Size为相应的大小。附加的数据信息长度为511字节。

1     for(i=0;i<MAX_DEVICE;i++)
2 {
3 szDevicePath[i]=new wchar_t[256];
4 }

  此循环用来给szDevicePath数组中的每个元素(设备的路径)分配内存。路径的最大长度为256个宽字符。接下来调用了自定义的函数GetDevicePath()用来获取设备的路径:

1 nDevice=::GetDevicePath((LPGUID)&UsbClassGuid,szDevicePath);

  传递进来的有两个参数:1)UsbClassGuid;2)设备路径数组的头指针。需要注意的是第一个参数。它的类型为LPGUID。LPGUID是GUID型的指针,它在头文件"guiddef.h"中有定义:

1 typedef GUID *LPGUID;

  而GUID的定义也在"guiddef.h"中:

 1 #if defined(__midl)
2 typedef struct {
3 unsigned long Data1;
4 unsigned short Data2;
5 unsigned short Data3;
6 byte Data4[ 8 ];
7 } GUID;
8 #else
9 typedef struct _GUID {
10 unsigned long Data1;
11 unsigned short Data2;
12 unsigned short Data3;
13 unsigned char Data4[ 8 ];
14 } GUID;

  可以看到,GUID是一个(4+2+2+8)=16字节=128位的二进制数,它用来指示产品的唯一性。USB大容量存储设备(USB Mass Storage Device)的GUID为“a5dcbf10-6530-11d2-901f-00c04fb951ed”。因此,在程序的最开头,有一行定义:

1 DEFINE_GUID(UsbClassGuid, 0xa5dcbf10L, 0x6530, 0x11d2, 0x90, 0x1f, 0x00, 0xc0, 0x4f, 0xb9, 0x51, 0xed);

  宏DEFINE_GUID的实现如下:

1 #ifdef INITGUID
2 #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
3 EXTERN_C const GUID DECLSPEC_SELECTANY name \
4 = { l, w1, w2, { b1, b2, b3, b4, b5, b6, b7, b8 } }
5 #else
6 #define DEFINE_GUID(name, l, w1, w2, b1, b2, b3, b4, b5, b6, b7, b8) \
7 EXTERN_C const GUID FAR name
8 #endif // INITGUID

   它的作用是为name分配(l,w1,w2,b1,b2,b3,b4,b5,b6,b7,b8)组成GUID。分配之后,name即成为一个const GUID。GetDevicePath()函数的实现如下:

 1 int GetDevicePath(LPGUID lpGuid,LPTSTR* pszDevicePath)
2 {
3 HDEVINFO hDevInfoSet;
4 SP_DEVICE_INTERFACE_DATA ifdata;
5 PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail;
6 int nCount;
7 BOOL bResult;
8
9 hDevInfoSet=::SetupDiGetClassDevs((LPGUID)&UsbClassGuid,NULL,NULL,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);
10
11 if(hDevInfoSet==INVALID_HANDLE_VALUE)
12 {
13 return 0;
14
15 }
16 pDetail=(PSP_DEVICE_INTERFACE_DETAIL_DATA)::GlobalAlloc(LMEM_ZEROINIT,INTERFACE_DETAIL_SIZE);
17 pDetail->cbSize=sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
18
19 nCount=0;
20 bResult=TRUE;
21
22 while(bResult)
23 {
24 ifdata.cbSize=sizeof(ifdata);
25 bResult=::SetupDiEnumDeviceInterfaces(
26 hDevInfoSet,
27 NULL,
28 lpGuid,
29 nCount,
30 &ifdata);
31 if(bResult)
32 {
33 bResult=::SetupDiGetInterfaceDeviceDetail(
34 hDevInfoSet,
35 &ifdata,
36 pDetail,
37 INTERFACE_DETAIL_SIZE,
38 NULL,
39 NULL
40 );
41 if(bResult)
42 {
43 wcscpy_s(pszDevicePath[nCount],wcslen(pDetail->DevicePath)+1,pDetail->DevicePath);
44 nCount++;
45 }
46
47 }
48 }
49 GlobalFree(pDetail);
50 ::SetupDiDestroyDeviceInfoList(hDevInfoSet);
51 return nCount;
52 }

  HDEVINFO类型是一个32位空指针,具体的定义在"guiddef.h"中:

1 typedef PVOID HDEVINFO;

  而SP_DEVICE_INTERFACE_DATA是一个结构,用来存储设备结构的信息:在"SETUPAPI.h"具体定义如下:

1 typedef struct _SP_DEVICE_INTERFACE_DATA {
2 DWORD cbSize;
3 GUID InterfaceClassGuid;
4 DWORD Flags;
5 ULONG_PTR Reserved;
6 } SP_DEVICE_INTERFACE_DATA, *PSP_DEVICE_INTERFACE_DATA;

  PSP_DEVICE_INTERFACE_DETAIL_DATA分ANSII字符版本与宽字符版本,在该程序中使用的是宽字符版本:

1 typedef struct _SP_DEVICE_INTERFACE_DETAIL_DATA_W {
2 DWORD cbSize;
3 WCHAR DevicePath[ANYSIZE_ARRAY];
4 } SP_DEVICE_INTERFACE_DETAIL_DATA_W, *PSP_DEVICE_INTERFACE_DETAIL_DATA_W;

  接下来的函数调用是:

1 hDevInfoSet=::SetupDiGetClassDevs((LPGUID)&UsbClassGuid,NULL,NULL,DIGCF_PRESENT|DIGCF_DEVICEINTERFACE);

  SetupDiGetClassDevs()返回一个设备的信息集。传递的第一个参数是UsbClassUuid,即设备的GUID,第二个与第三个参数为NULL,第四个设置了相应的标志位,返回的设备需要当前存在于系统中(DIGCF_PRESENT),并且设备的接口支持所指定的设备接口类(DIGCF_DEVICEINTERFACE)。通过这个函数调用,hDevInfoSet就指向了当前系统中存在的USB大容量存储设备的信息集。

附录

VID与PID

   根据USB规范的规定,所有的USB设备都有供应商ID(VID)和产品识别码(PID),主机通过不同的VID和PID来区别不同的设备,VID和PID都是两个字节长,其中,供应商ID(VID)由供应商向USB执行论坛申请,每个供应商的 VID是唯一的,PID由供应商自行决定,理论上来说,不同的产品、相同产品的不同型号、相同型号的不同设计的产品最好采用不同的PID,以便区别相同厂家的不同设备。

      VID和PID通常情况下有两种存储方式,第一种是主控生产商的VID和PID,存储在主控的bootcode中;第二种是设备生产商的VID和PID,该VID和PID存储在主控外部的非易失性存储设备中(EEPROM或Flash)的设备固件中,当USB设备连接主机时,如果固件中有设备生产商的VID和PID,会将该VID和PID报告给主机,而忽略主控生产商的VID和PID。所以理论上一个USB存储设备的VID应该是设备生产商的VID,而不是主控生产商的VID,这两个VID应该是不同的(主控生产商自己生产的设备除外)。

      由于VID和PID重复并不会对产品的使用带来严重影响,很多USB设备生产商(山寨厂居多)为了方便,并不会向USB执行论坛申请自己的VID,而是依然沿用主控生产商的VID或随便向产品写入VID和PID;同时,正规厂家只需要申请VID,PID由厂家自行确定,所以存在相同型号的产品,可能采用了不同的主控(商业需要,很正常),而他们的PID是一样的,基于上述原因通过VID和PID就不能准确识别USB设备的主控型号,这个问题大家在使用USB设备的过程中需要注意。

GUID

  GUID: 即Globally Unique Identifier(全球唯一标识符) 也称作 UUID(Universally Unique IDentifier) 。 GUID是一个通过特定算法产生的二进制长度为128位的数字标识符,用于指示产品的唯一性。GUID 主要用于在拥有多个节点、多台计算机的网络或系统中,分配必须具有唯一性的标识符。
  在 Windows 平台上,GUID 广泛应用于微软的产品中,用于标识如如注册表项、类及接口标识、数据库、系统目录等对象。

  GUID 的格式为“xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx”,其中每个 x 是 0-9 或 a-f 范围内的一个32位十六进制数。例如:6F9619FF-8B86-D011-B42D-00C04FC964FF 即为有效的 GUID 值。

完整代码


作者:Chenny Chen
出处:http://www.cnblogs.com/XjChenny/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

原文地址:https://www.cnblogs.com/XjChenny/p/2176106.html