[麦麦茶水间] 【每周分享】最近”迷恋“上了USB:又想看看电脑和U盘之间的USB通信报文了

[复制链接]
12|0
dffzh 发表于 2026-6-9 19:09 | 显示全部楼层 |阅读模式
, , , ,
#申请原创# #技术资源#

最近得空时候看过好几种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的高带宽特性:
其对应的数据结构如下所示:
  1. typedef struct _USB_SUPERSPEED_ENDPOINT_COMPANION_DESCRIPTOR {
  2.   UCHAR  bLength;
  3.   UCHAR  bDescriptorType;
  4.   UCHAR  bMaxBurst;
  5.   union {
  6.     UCHAR AsUchar;
  7.     struct {
  8.       UCHAR MaxStreams : 5;
  9.       UCHAR Reserved1 : 3;
  10.     } Bulk;
  11.     struct {
  12.       UCHAR Mult : 2;
  13.       UCHAR Reserved2 : 5;
  14.       UCHAR SspCompanion : 1;
  15.     } Isochronous;
  16.   } bmAttributes;
  17.   USHORT wBytesPerInterval;
  18. } 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)的报文里面我们可以得到下面这些信息:
此结构主要用于保存设备、配置、接口、类、供应商、终结点或设备字符串描述符:
对应的字符串数据结构如下所示:
  1. typedef struct _USB_STRING_DESCRIPTOR {
  2.   UCHAR bLength;
  3.   UCHAR bDescriptorType;
  4.   WCHAR bString[1];
  5. } 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
其他的依葫芦画瓢,类似操作即可。
不过啊,这些报文太多,涉及的内容和专业术语也比较多,不是简单几天就能研究透彻的,所以暂时也没办法和能力再继续更加深入的写出来,如有机会,待以后得空研究清楚了再贴出来,此处只做个简单的初步认识吧,希望对坛友们有一些用处。







本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?注册

×
您需要登录后才可以回帖 登录 | 注册

本版积分规则

484

主题

3516

帖子

26

粉丝
快速回复 在线客服 返回列表 返回顶部
0