#申请原创# #技术资源#
最近得空时候看过好几种USB设备(键盘、鼠标、HUB等)和电脑(USB主机)的通信报文,当然是通过Wireshark工具抓取数据报文的方式来看的,似乎有一种“迷恋”上了USB的感觉。不过看过的几种USB设备的URB数据传输方式基本是控制传输和中断传输,这不现在又想看看基于批量传输的USB设备了,正好手头有U盘,于是乎,不得不看看电脑和U盘之间的USB通信报文,下面且待我娓娓道来,U盘是通过HUB连接到电脑USB口的,和直接将U盘连接到电脑USB口是一样的: 先开启Wireshark抓包,然后插入U盘,这个时候,就会看到有一大波数据报文已经袭来,当我知道U盘当前的设备地址(device address)后,通过过滤器设置“usb.device_address == 16”只显示与U盘相关的报文,主要截图如下所示,后面拔插过U盘,所以看到的有些报文的设备地址是其他值,不影响报文解读: 太多了,靠后面的很多都是相似的报文,就不全部截图了。 有了报文以后,接下来我们就来解读一下吧。 我大致扫了一下,如果按照报文的URB传输方式来看的话,主要分为两种类型的报文:
一种是基于控制传输(URB_CONTROL, 码值0x02)的报文: 另外一种是基于批量传输(URB_BULK,码值0x03)的报文: 先来看看基于控制传输的相关报文吧。 这些报文基本上每个USB设备都会有的,其主要作用是主机通过多种描述符(设备/配置/接口/HID/端点/字符串描述符)的形式来获取和确定USB设备的相关信息,包括设备类型,厂商信息和设备配置信息等。
1、从设备描述符(Device Descriptor)的报文里面我们可以得到下面这些信息: 就像里面比较显眼的制造商ID,我们就知道当前U盘的制造商是Kingston Technology; 或者我们从bcdUSB字段的值0x0310也可以知道U盘遵循的USB规范版本号是USB3.1。
2、从配置描述符(ConfigurationDescriptor)的报文里面我们可以得到下面这些信息: 我们从bmAttributes字段的值0x0310便可以知道U盘是需要USB总线进行供电的(电脑的5V+GND),且不支持远程唤醒; 从bMaxPower字段的值63便可以知道U盘对电源的要求,即63*2=126mA,因为是以2mA为单位的,即将字段值乘以2。 3、从接口描述符(InterfaceDescriptor)的报文里面我们可以得到下面这些信息: 这里有一点需要特别注意的:
一般的USB设备的程序里,都是把配置、接口、HID和端点几个描述符放到一个数据结构里进行发送的,主机也是通过请求配置描述符的方式来获取这些数据的,所以当你看USB设备的报文时,如果看到两组关于配置描述符的报文,不要觉得意外哦,它们响应主机的报文长度是不一样的: 从接口描述符的数据里面,我们可以清楚地知道U盘的设备类别或者接口类别是Mass Storage(大容量存储设备类); 还可以知道接口子类别是SCSI transparent command set(SCSI透明命令集),这个看着有点虚,是什么意思呢,其实就是设备通过 USB 总线并使用 SCSI 命令集(如 READ、WRITE、INQUIRE等)进行数据传输,而当主机收到这个值后,便会将该USB设备视为类似硬盘和U盘等存储设备,所以说,每个字段都不是多余的,都会决定后续主机以什么方式来和设备进行数据交互; 还能知道接口协议是仅支持批量传输。 4、从端点描述符(Endpoint Descriptor)的报文里面我们可以得到下面这些信息:
至于端点描述符的个数,由设备端的定义决定,主机请求后,会读取所有的端点描述符。 显而易见,这个U盘有两个端点描述符,对比数据看下,主要的不同是bEndpointAddress字段的两个位置的值不一样,一个是方向:0表示OUT,即电脑->U盘,1表示IN,即U盘->电脑,因此这个输入输出是针对主机而言的;另外一个是端点编号,不同的端点描述符是不一样的,会在设备地址的第三个数值里面体现出来,其实就是数据交互的通道不一样: 另外,这个U盘还有一个SuperspeedEndpoint Companion Descriptor,即超高速端点配套描述符,这是什么呢?这个是 USB3.0以上版本中,紧随标准端点描述符之后的一个扩展描述符,其作用主要是为超高速传输模式提供额外的配置参数,以充分利用USB 3.x的高带宽特性: 其对应的数据结构如下所示: - typedef struct _USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR {
- UCHAR bLength;
- UCHAR bDescriptorType;
- UCHAR bMaxBurst;
- union {
- UCHAR AsUchar;
- struct {
- UCHAR MaxStreams : 5;
- UCHAR Reserved1 : 3;
- } Bulk;
- struct {
- UCHAR Mult : 2;
- UCHAR Reserved2 : 5;
- UCHAR SspCompanion : 1;
- } Isochronous;
- } bmAttributes;
- USHORT wBytesPerInterval;
- } USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR, *PUSB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR;
各个成员字段的含义作简要解释如下: bLength: 指定此描述符的长度(以字节为单位); bDescriptorType: 指定描述符类型; 必须设置为USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR; bMaxBurst: 指定端点作为突发的一部分可以发送或接收的最大数据包数; bmAttributes: bmAttributes.AsUchar:指定结构的长度; bmAttributes.Bulk: bmAttributes.Bulk.MaxStreams:指定批量终结点支持的最大流数; bmAttributes.Bulk.Reserved1:保留; bmAttributes.Isochronous: bmAttributes.Isochronous.Mult: 指定一个从零开始的数字,该数字确定可在服务间隔内发送到终结点的最大数据包数(bMaxBurst* (Mult + 1); bmAttributes.Isochronous.Reserved2:保留; bmAttributes.Isochronous.SspCompanion: wBytesPerInterval:每个间隔的字节数。 从这个描述符里面,我们就能看到此U盘的的这些额外配置参数。 5、从字符串描述符(String Descriptor)的报文里面我们可以得到下面这些信息: 此结构主要用于保存设备、配置、接口、类、供应商、终结点或设备字符串描述符: 对应的字符串数据结构如下所示: - typedef struct _USB_STRING_DESCRIPTOR {
- UCHAR bLength;
- UCHAR bDescriptorType;
- WCHAR bString[1];
- } USB_STRING_DESCRIPTOR, *PUSB_STRING_DESCRIPTOR;
各个成员字段的含义作简要解释如下: bLength: 指定描述符的长度(以字节为单位); bDescriptorType: 指定描述符类型; 必须始终USB_STRING_DESCRIPTOR_TYPE; bString[1]:
指向客户端分配的缓冲区的指针,该缓冲区包含从主机控制器驱动程序返回的Unicode字符串,其中包含请求的字符串描述符,字符串的内容是设备自定义的。
在看完描述符数据后,我们来看下大容量存储设备类必有的一个数据报文,就是请求MAX LUN(Logical Unit Number),即设备支持的最大逻辑单元号,也是通过控制传输: 为什么这个U盘的MAX LUN是0呢? 这里涉及到编号规则了,所有存储单元的编号从 0 开始,即MAX LUN + 1 就等于设备上总的存储单元数量;如果一个普通U盘只有一个存储空间,它的MAX LUN就是 0。如果一个读卡器有 4 个插槽,它的MAX LUN应该就是 3。 另外,我们还注意到,此时的具体通信协议是USBMS,即USB Mass Storage,是USB协议中的一部分,表示大容量存储器或者海量存储器,U盘就是利用这个协议开发的。遵从这个协议,即可让USB从设备连接到USB主设备上,并执行文件的读写等操作;一般的操作系统都集成了USBMS协议的驱动,所以U盘等设备插入PC后,才不用安装驱动而能直接使用。 综上,我们可以知道,U盘和电脑通信的时候,涉及的协议主要是USB协议和USBMS协议。
再来看看基于批量传输的相关报文吧。 对于U盘而言,我们应该可以猜到,什么报文需要批量传输?不错,就是那些存储在U盘里的文件啊。 我们主要看报文,基本上都会涉及到SCSI协议: 所谓的SCSI协议,在USB通信报文中,SCSI指的是 Small Computer System Interface(小型计算机系统接口)的一套命令协议;即当属于 Mass Storage Class(大容量存储设备类)的设备插入电脑时,它传输的数据底层其实是把SCSI命令打包在USB报文里进行传输的。 因此,要想理解U盘等大容量存储设备和主机的通信过程,肯定要了解SCSI命令集。
我们先来看下读取U盘容量的报文: U盘的容量的确是28G,LBA其实是Logical Block Address逻辑块地址的意思。 而接下来为什么会有那么多报文呢?
因为都是通过批量传输的方式在将U盘的数据传输到电脑上面: 我们可以看一个具体传输数据(也就是有效载荷payload)的报文,还包括响应状态Good: 483355个字节大概就是472KB的数据,那些字节很多的基本上都是在传输文件数据: 再比如看一条READ(10)命令的报文,表示从指定扇区开始读取数据: 类似读写LBA的SCSI命令如下所示: 命令 操作码(OpCode) 报文中LBA位置 长度 READ(10) 0x28 第 2-5 字节 32 位 WRITE(10) 0x2A 第 2-5 字节 32 位
再比如最后一条主机通过发送SCSI命令0x1B来控制U盘的物理状态(让设备暂停工作或重新启动): 如上面所列报文所示,总之一句话: U盘通常遵循的是USB海量存储设备类 (USB MSC) 规范,使用 SCSI命令集和主机进行通信,其读写命令(如 READ(10) 或 WRITE(10)等,对应可以看到其操作码OpCode分别为0x28和0x2A)都被封装备在 CBW 中进行传输的。 另外,通过数据报文,我们也可以知道批量传输的方向,是电脑到U盘,还是U盘到电脑: 此外,这些基于批量传输的报文,我们该怎么使用过滤器进行查找呢?
因为这些是以SCSI接口协议为基础的,所以按照规则,我们要通过SCSI协议来进行查找和监控,其中CDB的全称是Command Descriptor Block,即命令描述符块: 当你不知道用什么命令来设置过滤器的时候,就按我另外一篇文章那样,直接将字段作为过滤器来设置,然后粘贴到过滤器里面即可,比如我想将SCSI的命令作为过滤器来查找报文: 其中的SBC是SCSI BlockCommands(SCSI块命令)的意思,有很多SBC命令,列举如下: 其实在报文Info的信息下面也大概可以看到是什么命令报文: 比如INQUIRY查询报文,设置过滤器为 : scsi_sbc.opcode== 0x03 或者
scsi_sbc.opcodeeq 0x03 再比如我想看下是否有应答状态为Good或者Error的报文,设置过滤器为 scsi.status==0x00 //表示Good
scsi.status==0x01 //表示非Good 其他的依葫芦画瓢,类似操作即可。 不过啊,这些报文太多,涉及的内容和专业术语也比较多,不是简单几天就能研究透彻的,所以暂时也没办法和能力再继续更加深入的写出来,如有机会,待以后得空研究清楚了再贴出来,此处只做个简单的初步认识吧,希望对坛友们有一些用处。
|