【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 值。

完整代碼

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章