最近在深入研究 USB HID 协议, 翻了不少教材和Spec文档,发现很多内容写得过于”照本宣科”----堆砌了一堆 Usage Page/Collection 的定义,读完却依然不知道这串 Hex 数据到底代表什么.
我认为, 理解 HID 最好的方式不是死磕枯燥的定义, 而是结合工具看疗效. 我将抛开晦涩的理论, 以最常见的 USB Keyboard 为例, 通过 Wireshark 抓包实战, 从 Report Descriptor 的解析入手, 一步步拆解 Input Report Raw Data. 当你亲手利用 Descriptor 将那串 0x00 00 20... 的 16 进制代码精准映射到具体的按键动作时, 那些原本枯燥的协议定义也就瞬间”活”过来了. 希望这篇基于实战的总结, 能帮到同样在啃 HID 协议的读者.
HID Report Descriptor 从一个简单的 Mouse HID “Report Descriptor”开始 . 这段 Report Descriptor是微软 HID Descriptor Tool提供的一个样例 :
From:MSDEV\Projects\test\Mouse.hid
再来看一个实际的 Mouse Report Descriptor, 一个多功能鼠标 (USB Node下有多个 HID Node)
(DevMgr view)
Winddk HidClient.exe会列出大量相同 VID&PID的设备,需要根据 UsagePage & Usage来确定当前操作的是哪个 HID设备。
对于此例中的USB Mouse,可以通过Wireshark的USB Capture功能获得其Hid Report Descriptor(须拔插USB发射器):
读者可下载附件中的usbkeyboardInput.pcapng文件, 打开后在Display filter输入usbhid(只显示usbhid协议), 即可得到HID Report Descriptor。(这是 USB Control Transfer 的一部分)
USB Mouse的 HID Report Descriptor中有 5组 UsagePage/Usage/Collection & End Collection, 可能每一组对应 DevMgr/USB Input device下的 DevNode(另外设备命名也相似 )
相较而言, USB keyboard的 HID Report Descriptor及其 DevNode则简洁很多 , 只有一组 :
另外 , 仔细观察 Keyboard与 Mouse的 HID Report Descriptor, 除了 UsagePage/Usage/Collection & End Collection组数数量差异 , 在 Collection & End Collection中还有一个细小的差异 :
USB Keyboard的 HIDReport Descriptor中只有一组 UsagePage/Usage/Collection & End Collection, 基础款 USB Keyboard Collection和 End Collection之间通常没有 Report ID;
USB Mouse的 HIDReport Descriptor中有 5组 UsagePage/Usage/Collection & End Collection, 每组的 Collection和 End Collection之间中都有单独的 Report ID. Report ID的作用将在后面的章节中阐述 .
2. HID Report Descriptor 的作用 ---- 解析 HID Report
引用 <HID跨接口设计与开发 >书中 (第九章 P84)的一段话 : 报告描述符用于描述报告的数据格式与数值定义 , 一个实例的所有报告信息都包含在同一个报告描述符内 .
下图是书中 HID Mouse 的 Input Report 的示例。鼠标会以此格式为模板 , 当发生按键 / 移动之类的事件 , FW 会将按键状态 / 位移相对值填入该模板 , 形成一个 3 字节的 Input Report, 发送给 OS.
OS收到 Input Report后 , 以此模板从 Input Report中解析各字段 , 做出相应的动作 . 这段话引出了 HID Report Descriptor的模板化的作用 : 在 device端 , device按 HID Report Descriptor的规定 , 生成 Input Report; 在 OS端 , OS按 HID Report Descriptor的规定 , 解读 Input Report中各个字段的含义 .
下图 , 是书中表 9-2 HID Mouse Input Report对应的 HID Report Descriptor:
以上仅是书本中的例子 (书是照本宣科的烂书 , 远不如问 AI有用 ), 以现实中 USB HID Mouse移动引起的 HID Input report为例 : USBCap抓到的 HID Data(Raw data)为 0x0201000000000000(下图中红框部分 ); Wireshark根据 USB Mouse汇报的 HID Report Descriptor, 解析了 Raw data各个字段 , 并最终呈现给分析者 (下图绿框部分 ).
本文的意图亦是如此 :根据 USB device 汇报的 HID Report Descriptor, 解析 Raw Data中的含义 (诚然 , USB HID device尚可通过 Wireshark捕获 , 但 Wireshark解析将止步与其他使用 HID协议的设备 —I2C Touch Panel/I2C Touch Pad).
2.1. USB HID Keyboard的输入报告 (Input Report)一例
用键盘在Wireshark Display Filter 中 输入字符 1~8a~k, 见下图 :
第 27帧 , Wireshark解析的 HID Data如下 :
Frame 27:
HID Data: 0000200000000000
本节 将结合 USB Keyboard的 HID Report Descriptor分析这串Raw data 其对应的按键操作 .
2.1.1. 解析 HID Report Descriptor的步骤 解析 HID Report Descriptor, 就是构造 /解释 HID Report模板的过程 .
a. HID Report Descriptor以 UsagePage/Usage/Collection & End Collection进行分组 , 每个分组代表一个 DevNode(或者说一个功能 ), 因此第一步是从多个 UsagePage/Usage/Collection & End Collection分组中挑选出一组 UsagePage/Usage/Collection & End Collection, 对 Collection & End Collection之间的内容进行分析 ;
(先选蓝框 ,再选绿框 )
b. Collection & End Collection之间又有 3类 Main Item, 分别为 Input Item /Output Item /Feature Item. 把相同 Main Item合并到一起 , 描述一类 Report. 如所有的 Input()合到一起 , 用于描述 Input Report的 Raw Data; 所有的 Output()合到一起 , 用于描述 Output Report Raw Data.
附注 : Input Report是设备发送给 OS的数据 . 对于 HID Keyboard按下 /抬起数字键 /字母键 , 属于 Input Report;
Output Report是 OS发送给设备的数据 . 对于 HID Keyboard,按下 /抬起 CapsLocks/NumLock/ScrollLock键 (这类 OS指示键盘指示灯发生变化 ), 属于 Output Report;
除了 Main Item, Collection & End Collection 之间还有 2 种 Global Item: Report Size 和 Report Count.
Report Size: 字段在 Report 中数据占了多少 bit;
Report Count: 上述字段在 Report 中的数量 . Report Size* Report Count= 字段在 Report 中总 Bit 数
将 Report Descriptor 解析为 Report 时 , 有个简单的规则 : 从上往下解析 Report Descriptor, 以 Report Size/Report Count 作为字段 (Report Count=1)/ 字段数组 (Report Count>1) 的开始 ; 遇到 Main Item 后 , 作为字段 (Report Count=1)/ 字段数组 (Report Count>1) 的结束 ( 其他内容如 Usage Page/Usage Min&Usage Max 当做描述性语句 , 暂时忽略 ).
以下 USB Keyboard Report Descriptor 是上述结论的示例 . 红框中的内容可用于生成 Input Report 各个字段 ; 而黄框中的内容可用于生成 Output Report 各个字段 .
根据上图可知每次按键 ( 普通数字键 / 字母键 ) 将产生一个 8 字节的 Input Report:
Byte7 Byte6 Byte5 Byte4 Byte3 Byte2 Byte1 Byte0 6*Input(Data, Array, Abs) 1*Input(Const, Var, Abs) 1*Input(Data, Var, Abs)
表 1. Input Report中各偏移
附注 : Byte1 上的 Input(Const, Var, Abs)前面没有 Report Size和 Report Count, 因此它将沿用 Byte0的 Report Size和 Report Count.
c. 解读各个字段含义 . 迄今为止 , 已解读的 Main Item/Global Item可以规划 Report大小 /字段范围 , 而字段的含义则需要结合 Global Item中的 Usage Page以及 Local Item中的 Usage Minimum/Usage Maximum来解读 .
解读表 1). Byte 0: 用来定义修饰键 , 每一个修饰键用一个 bit表示 (对照 SPEC逐个 Item翻译意义不大 , 直接 AI…)
Item Note Usage Page(Keyboard) Usage Minimum~ Usage Maximum使用了 Usage ID 0xE0~0xE7. 它们是Keyboard/Keypad Usage Page(0x07) 下定义的标准编号 Usage Minimum(0xE0) Left Control Usage Maximum(0xE7) Right GUI (Win/Cmd) Logical Minimum(0) 按键松开 Logical Maximum(1) 按键按下
解读表 1). Byte 1. 根据 Boot Keyboard Protocol 定义 , Byte 1被称为 Reserved Byte (保留字节 ) 它的作用主要是为了字节对齐 .
解读表 1). Byte 2~7: 用来定义普通按键 , 不过这次使用一个 Byte来描述 165个普通按键的状态
Item Note Usage Page(Keyboard) Usage Minimum~ Usage Maximum使用了Usage ID 0x00~0xA4. 它们是Keyboard/Keypad Usage Page (0x07) 下定义的标准编号 Usage Minimum(0x00) N/A Usage Maximum(0xA4) N/A Logical Minimum(0) Logical Maximum(164)
2.1.2. Hid Keyboard Input Report实例 结合以上 a),b),c)三点 , 我相信读者已经可以解析 USB HID keyboard的 Input Report了 . 仍然以上文附带的 usbkeyboardInput.pcapng文件为例 , 来解析其 Capture的 Input Report.
(分析前 , 还需要引入一个新话题 : 我的 USB 接收器是一个 USB Composite Device (复合设备 ), 即在同一个 USB 设备下包含多个 Interface, Wirshark中怎么区分哪些 Frame中 HID Data是发送给键盘的 ? 哪些是发送给鼠标的 ? 这涉及到 USB Device Endpoint的概念 , 我打算开个番外篇介绍了 . 有个简单的区分方法是根据 HID Report Descriptor计算出来的长度差异作依据 . HID data长度为 8字节 ,是 Keyboard产生的数据 ; HID data长度为 3字节 , 大概率是 Mouse产生的数据 . 另外 , 在附件 usbkeyboardInput.pcapng中 , usb.src==”2.4.1”对应 Keyboard; usb.src==”2.4.2”对应 Mouse)
a. 解析 27# / 29# Frame的 HID Data含义
Frame 27# Byte 0=0x00 修饰键处于抬起状态 , Byte 2=0x20, 即 UsageID=0x20处于按下状态 . 查询后得知是 Page=0x07, UsageID=0x20对应按键 3按下 ;
Frame 29# Byte 0=0x00 修饰键处于抬起状态 , Byte 2~7=0x00, 所有的普通按键都处于按下状态 . 结合 Frame #27可知按键 3抬起 ;
b. 解析 31# / 33# Frame的 HID Data含义
Frame 31# Byte 0=0x00 修饰键处于抬起状态 , Byte 2=0x21, 即 UsageID=0x21处于按下状态 . 查询后得知是 Page=0x07, UsageID=0x21对应按键 4按下 ;
Frame 33# Byte 0=0x00 修饰键处于抬起状态 , Byte 2~7=0x00, 所有的普通按键都处于按下状态 . 结合 Frame #31可知按键 4抬起 ;
c. 解析 55# / 57# Frame的 HID Data含义
Frame 55# Byte 0=0x00 修饰键处于抬起状态 , Byte 2=0x16, 即 UsageID=0x16处于按下状态 . 查询后得知是 Page=0x07, UsageID=0x16对应按键 s按下 ;
Frame 57# Byte 0=0x00 修饰键处于抬起状态 , Byte 2~7=0x00, 所有的普通按键都处于按下状态 . 结合 Frame #57可知按键 s抬起 ;
d. 额外实验 Shift+s/Ctrl+d
(附件 usbkeyboardInput.pcapng没有包含这 2个动作 , 忘了记录 , 留给读者自己实验了 )
Frame 139# Byte 0=0x02 修饰键 LeftShift处于按下状态 , Byte 2=0x16, 即 UsageID=0x16处于按下状态 . 查询后得知是 Page=0x07, UsageID=0x16对应按键 LeftShift+s按下 ;
Frame 159# Byte 0=0x01 修饰键 LeftCtrl处于按下状态 , Byte 2=0x07, 即 UsageID=0x07处于按下状态 . 查询后得知是 Page=0x07, UsageID=0x07对应按键 LeftCtrl+d按下 ;
这篇文章看看反响, 如果评优了, 计划更新3篇番外篇: USB Mouse Hid input report / I2C touch Pad Hid input report / USB endpoint
[培训]Windows内核深度攻防:从Hook技术到Rootkit实战!
最后于 18小时前
被hyjxiaobia编辑
,原因:
上传的附件: