zoukankan      html  css  js  c++  java
  • 【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/
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    vue-待办日历和Table
    vue-播种量小工具总结
    20200415-巡检发现的有趣事情
    vue-element-admin学习笔记--Setting保存到cookie
    vue-element-admin学习笔记--权限加载及自定义布局(8)
    vue-element-admin学习笔记--权限加载及自定义布局(7)
    vue-element-admin学习笔记--权限加载及自定义布局(6)
    第六天 二维数组 方法
    第五天(冒泡排序)
    第四天 数组(基础)
  • 原文地址:https://www.cnblogs.com/XjChenny/p/2176106.html
Copyright © 2011-2022 走看看