首页
社区
课程
招聘
[分享]关于如何判断两个可移动磁盘卷是否在同一个USB HUB上
发表于: 2011-3-1 14:46 10969

[分享]关于如何判断两个可移动磁盘卷是否在同一个USB HUB上

2011-3-1 14:46
10969
这是一个近期的项目需求功能点,参考相关资料,现在将研究所得的一些想法的实现分享一下。判断多个可移动磁盘卷是否在同一个USB HUB上,我们知道,每一个可移动磁盘卷都有一些唯一的标识,起先的想法是枚举USB可移动卷设备,总觉得设备信息里会有一些关于USB HUB的信息,不过后来没有找到就放弃这种想法了(如果我忽略了有牛们知道的话讨论下)。现在是从USB PORT入手,每个USB HUB上都有一个 NNumberOfPort来指示这个HUB有几个USB PORT,我们需要遍历这个。我们需要获取的是可移动磁盘的VID,PID和SerialNumber这三个标识。在USB PORT上挂接的设备中是可以找到这三个信息的,所以我们匹配这些信息就能找到指定的可移动磁盘对应的是哪一个USB HUB,从而达到标题所指的功能。

USB HUB设备是挂在HCD上的(控制总线),我们得先枚举这个。

一、枚举HCD( Host Controller)

这个很简单,HCD是这样的命名(\\.\HCD0,\\.\HCD1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
..
For( I = 0; ; i++ )
{
    Wsprintf( szHcName,_T(\\\\.\\HCD%d),i );
     
    HandleHcd = CreateFile( szHcName…. );
    If( INVALID_HANDLE_VALUE != handleHcd )
    {
        //…..
        CloseHandle( handleHcd );
    }
    Else
    {
        Break;
    }
}


获取到HCD的设备句柄之后,我们需要获取它下面的首个HUB的设备

二、根据HCD的设备句柄获取HUB设备

用CreateFile找开时需要名字,首先获取该hub的名字,向hcd设备发IOCTL_USB_GET_ROOT_HUB_NAME就可以了。

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
typedef struct _USB_ROOT_HUB_NAME {
    ULONG  ActualLength;            //指示长度
    WCHAR  RootHubName[1];          //名字的指针
} USB_ROOT_HUB_NAME, *PUSB_ROOT_HUB_NAME;
 
USB_ROOT_HUB_NAME hubNameTmp;
PUSB_ROOT_HUB_NAME pHubName;
//获取名字长度
DeviceIoControl( hHCDHandle,IOCTL_USB_GET_ROOT_HUB_NAME,&hubNameTmp,sizeof(USB_ROOT_HUB_NAME), &hubNameTmp,sizeof(USB_ROOT_HUB_NAME),&dwBytes,NULL );
dwBytes = hubNameTmp.ActualLength;  //在结构的这个字段会返回所需的缓冲区长度
 
//获取名字
pHubName = (PUSB_ROOT_HUB_NAME)malloc( dwBytes );
    if( pHubName )
    {
        pHubName->ActualLength = dwBytes;
        if( DeviceIoControl( hHCDHandle,
                             IOCTL_USB_GET_ROOT_HUB_NAME,
                             NULL,0,
                             pHubName,dwBytes,
                             &dwBytes,NULL ) )
        {
            //获取成功,申请返回字符串内存
            lpszHCDName = (TCHAR*)malloc( dwBytes );
            if( lpszHCDName )
            {
                //获取到的是UNICODE字符串
                WCharToMByte( pHubName->RootHubName,lpszHCDName,dwBytes );
 
                //接下来可以根据这个名字打开这个句柄了
                wsprintf( szName,_T("\\\\.\\%s"),lpszHubName );
    hHubHandle = CreateFile( szName,GENERIC_WRITE,FILE_SHARE_WRITE,NULL,OPEN_EXISTING,NULL,NULL );
    if( INVALID_HANDLE_VALUE != hHubHandle )
    {
        //成功获取到hub设备句柄
        CloseHandle( hHubHandle );
    }
            }
        }
        //释放内存
        free( pHubName );
    }


有了HUB的句柄,那关于这个HUB的信息,比如我们关心的nNumberOfPorts也能获取到了,向这个设备发IOCTL_USB_GET_NODE_INFORMATION请求

1
2
3
4
5
6
7
8
….
USB_NODE_INFORMATION NodeInfo;
If( DeviceIoControl( hHubHandle,IOCTL_USB_GET_NODE_INFORMATION,NULL,0,&NodeInfo,sizeof(USB_NODE_INFORMATION),&dwBytes,NULL )
{
    //这里就能获取到这个HUB上的USB端口数了,在hub的设备描述符中
    NumberOfPorts = NodeInfo.u.HubInformation.HubDescriptor.bNumberOfPorts;
}
….


接下去可以去遍历这些端口了!

三、遍历HUB上的usb port

获取一个指定HUB上的端口信息,向这个HUB设备发送IOCTL_USB_GET_NODE_CONNECTION_INFORMATION/EX,因为SerialNumber这个唯一标识是字符串值,而获取到的端口连接信息里只会有iSerialNumber这个指示offset的字段,所以我们还需要发送一个IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION去获取这个UB的字符串描述,然后根据iSerialNumber 这个offset来偏移到SerialNumber,而VID和PID则以USHORT类型直接在设备描述里出现。

信息结构如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
#pragma pack(1)
typedef struct _USB_NODE_CONNECTION_INFORMATION_EX {
    ULONG ConnectionIndex;  //这个值是需要输入的,指定查询哪一个端口,从数字1开始
USB_DEVICE_DESCRIPTOR DeviceDescriptor; //如果有USB连接的话这里是连接的USB设备的描述符,也是我们用来比较两个可移动磁盘卷是不是相同的标识
UCHAR CurrentConfigurationValue;
    UCHAR Speed;
BOOLEAN DeviceIsHub; //表示这个设备是不是一个HUB,也就是说如果是一个HUB的话我们需要递归下去
USHORT DeviceAddress;
    ULONG NumberOfOpenPipes;
    USB_CONNECTION_STATUS ConnectionStatus; //这个端口的连接状态
    USB_PIPE_INFO PipeList[0];
} USB_NODE_CONNECTION_INFORMATION_EX, *PUSB_NODE_CONNECTION_INFORMATION_EX;
#pragma pack()


USB的设备描述结构
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#pragma pack(1)
typedef struct _USB_DEVICE_DESCRIPTOR {
    UCHAR  bLength ;
    UCHAR  bDescriptorType ;
    USHORT  bcdUSB ;
    UCHAR  bDeviceClass ;
    UCHAR  bDeviceSubClass ;
    UCHAR  bDeviceProtocol ;
    UCHAR  bMaxPacketSize0;
    USHORT  idVendor ;      //VID
    USHORT  idProduct ; //PID
    USHORT  bcdDevice ;
    UCHAR  iManufacturer ;
    UCHAR  iProduct ;
    UCHAR  iSerialNumber ;  //SerialNumber,这个字段是指示在连接字符串信息中SerialNumber的offset
    UCHAR  bNumConfigurations ;
} USB_DEVICE_DESCRIPTOR, *PUSB_DEVICE_DESCRIPTOR ;
#pragma pack()


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
67
68
69
…..
PUSB_NODE_CONNECTION_INFORMATION_EX     pNodeConnInfoEx;
For( int I = 0; I <= NumPorts; I ++ )
{
    dwBytes = sizeof(USB_NODE_CONNECTION_INFORMATION_EX) + sizeof(USB_PIPE_INFO) * 32;
    pNodeConnInfoEx = (PUSB_NODE_CONNECTION_INFORMATION_EX)malloc( dwBytes );
    if( pNodeConnInfoEx )
    {
        Memset( pNodeConnInfoEx,0,dwBytes );
        //这里是输入参数,指定要查高询哪一个端口的连接信息,从1开始
        pNodeConnInfoEx->ConnectionIndex = I;
        DeviceIoControl( hHubHandle,IOCTL_USB_GET_NODE_CONNECTION_INFORMATION_EX,pNodeConnInfoEx,dwBytes,pNodeConnInfoEx,dwBytes,&dwBytes,NULL );
 
        //这个端口有挂接设备
        If( pNodeConnInfo->ConnectionStatus == DeviceConnected )
        {
            //这个设备有这三个主要信息
            If( pNodeConnInfo->DeviceDescriptor.idVendor &&
                pNodeConnInfo->DeviceDescriptor.idProduct &&
                pNodeConnInfo->DeviceDescriptor.iSerialNumber )
            {
                //获取这个设备的字符串描述(IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION)
                PUSB_DESCRIPTOR_REQUEST stringDescReq; //这里输入参数结构,需要填充
    PUSB_STRING_DESCRIPTOR  stringDesc;
    PSTRING_DESCRIPTOR_NODE stringDescNode;
 
    nBytes = sizeof(stringDescReqBuf);
 
    stringDescReq = (PUSB_DESCRIPTOR_REQUEST)stringDescReqBuf;
    stringDesc = (PUSB_STRING_DESCRIPTOR)(stringDescReq+1);
 
    //填充输入查询信息参数
    memset(stringDescReq, 0, nBytes);
 
    //连接的端口索引
    stringDescReq->ConnectionIndex = ConnectionIndex;
 
//这个wValume是一个WORD值,高8位指标查询类型,这里要查询字符串描述,所以是USB_STRING_DESCRIPTOR,低8位指定查询的描述符索引,比如要查询SerialNumber,这个值就是上面获取到的iSerialNumber
    stringDescReq->SetupPacket.wValue = (USB_STRING_DESCRIPTOR_TYPE << 8)
        | DescriptorIndex;
 
    //语言ID,置为0即可
    stringDescReq->SetupPacket.wIndex = LanguageID;
 
    //指示整个输出缓冲的大小,用总大小nBytes减去查询结构大小,
    stringDescReq->SetupPacket.wLength = (USHORT)(nBytes - sizeof(USB_DESCRIPTOR_REQUEST));
 
if( DeviceIoControl(hHubDevice,IOCTL_USB_GET_DESCRIPTOR_FROM_NODE_CONNECTION,stringDescReq,nBytes,stringDescReq,nBytes,&nBytesReturned,NULL);
{
    //获取到的是UNICODE字符串
    TCHAR* pSerialNumberString = (TCHAR*)malloc( stringDesc->bLength / sizeof(WCHAR) + 1 );
    //获取iSErialNumber在字符串描述中的偏移,stringDesc->bString + descChars就是SerialNumber的UNICODE字符串了
    int descChars = ( (int)stringDesc->bLength - offsetof(USB_STRING_DESCRIPTOR,bString)) / sizeof(WCHAR);
    if(pSerialNumberString)
    {
    //转为ansi字符串    
stringDesc->bString[descChars] = 0;
        WCharToMByte( stringDesc->bString, pSerialNumberString,stringDesc->bLength / sizeof(WCHAR) + 1 );
 
        //现在有了这三个信息就可以匹配可移动磁盘了
        // pNodeConnInfo->DeviceDescriptor.idVendor
        // pNodeConnInfo->DeviceDescriptor.idProduct
        // pSerialNumberString
        ……
    }
}
}
}
…..


现在有了这三个唯一的标识,只要跟目标可移动磁盘匹配就可以了,一般我们实际有都不会指定一个可移动磁盘设备,而是指定一个盘符,接下来我们讨论如何通过盘符获取上面这三个唯一标识(SerialNumber,VID,PID)。

四、通过盘符获取磁盘设备的唯一标识

可移动磁盘,包括U盘,也包括移动硬盘,需要注意的分区。两个不同的分区,但是他们是处于同一个设备,所以Disk Number是一样的,我这里用这个来处理带有分区的可移动磁盘。
很简单,只要向磁盘卷设备发送IOCTL_STORAGE_GET_DEVICE_NUMBER就可以了,获取到的信息结构如下
1
2
3
4
5
typedef struct _STORAGE_DEVICE_NUMBER {
    DEVICE_TYPE DeviceType;         //这里是FILE_DEVICE_DISK
    DWORD       DeviceNumber;       //这里就是我们需要的DiskNumber
    DWORD       PartitionNumber;
} STORAGE_DEVICE_NUMBER, *PSTORAGE_DEVICE_NUMBER;


1
2
3
4
5
6
7
8
9
10
….
Wsprintf( szName,_T(\\\\.\\%c:),cVolume );
HANDLE hDevice = CreateDevice( szName,…. );
….
 
 
 
DeviceIoControl( hDevice,IOCTL_STORAGE_GET_DEVICE_NUMBER,NULL,0,&sdNumber,sizeof(STORAGE_DEVICE_NUMBER),&dwBytes,NULL );
..
//sdNumber.DeviceNumber就是DISK NUMBER


获取到disk number之后我们可以用CM_Get_Device_ID函数获取这些信息,但是这个信息的第一个参数是DEVINST类型,可以通过上面获取到的disk number,用SetupDi函数枚举出来。
(网上现成代码如下)
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
67
68
69
70
71
72
73
DEVINST
GetDrivesDevInstByDiskNumber( long DiskNumber )
{
    DEVINST devInstResult = 0;
    GUID* guid = (GUID*)&GUID_DEVINTERFACE_DISK;
    //GUID* guid = (GUID*)&UsbClassGuid;
    HDEVINFO hDevInfo = SetupDiGetClassDevs( guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
 
    if (hDevInfo == INVALID_HANDLE_VALUE){
        return 0;
    }
 
    DWORD dwIndex = 0;
    SP_DEVICE_INTERFACE_DATA devInterfaceData = {0};
    devInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
    BOOL bRet = FALSE;
 
    BYTE Buf[1024];
    PSP_DEVICE_INTERFACE_DETAIL_DATA pspdidd = (PSP_DEVICE_INTERFACE_DETAIL_DATA)Buf;
    SP_DEVICE_INTERFACE_DATA         spdid;
    SP_DEVINFO_DATA                  spdd;
    DWORD                            dwSize;
 
    spdid.cbSize = sizeof(spdid);
 
    while ( true )
    {
        bRet = SetupDiEnumDeviceInterfaces(hDevInfo, NULL, guid, dwIndex, &devInterfaceData);
        if (!bRet)
        {
            break;
        }
 
        SetupDiEnumInterfaceDevice(hDevInfo, NULL, guid, dwIndex, &spdid);
 
        dwSize = 0;
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, NULL, 0, &dwSize, NULL);
 
        if ( dwSize!=0 && dwSize<=sizeof(Buf) )
        {
            pspdidd->cbSize = sizeof(*pspdidd);
 
            ZeroMemory((PVOID)&spdd, sizeof(spdd));
            spdd.cbSize = sizeof(spdd);
 
            long res = SetupDiGetDeviceInterfaceDetail(hDevInfo, &spdid, pspdidd, dwSize, &dwSize, &spdd);
            if ( res )
            {
 
                HANDLE hDrive = CreateFile(pspdidd->DevicePath, 0, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, NULL, NULL);
                if ( hDrive != INVALID_HANDLE_VALUE )
                {
                    STORAGE_DEVICE_NUMBER sdn;
                    DWORD dwBytesReturned = 0;
                    res = DeviceIoControl(hDrive, IOCTL_STORAGE_GET_DEVICE_NUMBER, NULL, 0, &sdn, sizeof(sdn), &dwBytesReturned, NULL);
                    if ( res )
                    {
                        if ( DiskNumber == (long)sdn.DeviceNumber )
                        {
                            CloseHandle(hDrive);
                            SetupDiDestroyDeviceInfoList(hDevInfo);
                            devInstResult = spdd.DevInst;
                            break;
                        }
                    }
                    CloseHandle(hDrive);
                }
            }
        }
        dwIndex++;
    }
    return devInstResult;
}


通过CM_Get_Device_ID能获取到的只是SerialNumber,在获取到的字符串的最后一节(以‘\’)隔开
通过下面代码提取SerialNumber
1
2
3
4
5
6
7
8
9
10
11
12
13
if( CR_SUCCESS == CM_Get_Device_ID( devInst,szBuffer,200,0 ) )
{
    p = _tcsrchr( szBuffer,TEXT('\\') );
    if( p )
    {
        _tcscpy( pDid->SerialNumber,p+1 );
        p = _tcschr( pDid->SerialNumber,TEXT('&') );
        if( p )
        {
            p[0] = 0;
        }
    }
}


获取vid和pid也是通过setupdi函数枚举出来,在获取到DevicePath之后通过下面代码获取
Sscanf( pDetail->DevicePath, _T("\\\\?\\usb#vid_%04X&pid_%04X"),&Vid,&Pid );

注:关于SerialNumber的获取,DevicePath里也有SerialNumber,也可以通过上面的方法提取出来,而不用过CM_Get_Device_ID函数

这样就获取到指定盘符的三个唯一标识,去跟指定usb hub上挂接的设备进行比较,就可以知道两个卷是不是挂接在同一个usb hub上了。

上述有来自网上代码
代码USB View有相关实现,只是贴出讲解,如有错误请提出改正

参考:
http://blog.csdn.net/jakeyjia/archive/2006/12/01/1424111.aspx
WDK.7600.USb View

[注意]看雪招聘,专注安全领域的专业人才平台!

收藏
免费
支持
分享
最新回复 (2)
雪    币: 120
活跃值: (160)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
学习一下代码。。。感谢。
2011-3-1 14:56
0
雪    币: 333
活跃值: (46)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
3
MARK  :磁盘、分区、卷标

thx~~
2011-11-23 16:52
0
游客
登录 | 注册 方可回帖
返回

账号登录
验证码登录

忘记密码?
没有账号?立即免费注册