TechnomancerIncrease/usbsnoop-766
GitHub: TechnomancerIncrease/usbsnoop-766
基于 eBPF 的全系统实时 USB 流量嗅探器,通过两个通用 URB 钩子解码 USB 传输,无需 usbmon 或硬件设备即可进行外设调试与逆向分析。
Stars: 0 | Forks: 0
# usbsnoop — 基于两个 fentry 钩子的实时 USB 传输嗅探器
## 快速开始
```
git clone https://github.com/TechnomancerIncrease/usbsnoop-766.git
cd usbsnoop-766
npm install
npm start
```

**全系统** USB 流量的实时彩色数据流 —— 构建于每个主机控制器驱动都会经过的两个通用 URB 必经节点之上,因此它
适用于 xHCI/EHCI/OHCI/dwc 等,无需针对特定控制器的 tracepoints,也无需
`usbmon`。完全可移植的 CO-RE。
| fentry 钩子 | 告诉我们的信息 |
| ------------------------ | ------------------------------------------------------- |
| `usb_submit_urb` | 一个传输已排队(设备、endpoint、类型、payload) |
| `usb_hcd_giveback_urb` | 传输已完成(状态、移动的字节数、延迟、payload) |
以 URB 指针为键的 `lru_hash` 将两者缝合在一起:提交时记录
开始时间,完成时读取该时间以计算提交→完成的延迟,然后
将其删除。这镜像了 `httpbody` 的请求/响应配对 —— **SUBMIT** 是
“请求”(主机发送的内容),**COMPLETE** 是“响应”(设备
返回的内容)。
控制传输将其 8 字节的 SETUP 数据包解码为标准
请求名称(`GET_DESCRIPTOR`、`SET_CONFIGURATION` 等);数据阶段在看起来像文本时
呈现为文本,否则呈现为十六进制转储 (hexdump)。
输出为**每个事件一行**(紧凑格式)。设备首次出现时,会获得一个
`▸` 图例行(`bus-dev`、`vid:pid`、产品、链路速度);此后
每行仅带有简短的 `DEV` 标签,因此在流量巨大的情况下,左侧列也能保持对齐
且易于扫描。每行显示时间、类型(SUBMIT/CMPLT)、
传输类型、`epNdir`、方向箭头(`←` device→host IN,`→`
host→device OUT)、字节数、状态、延迟以及所属的内核驱动,
接着是一个 `·` 和最有用的细节(已解码的 SETUP、SCSI 命令或简短的
payload 预览)。传入 `--hex` 可获取完整的多行十六进制转储。在 TTY 上,十六进制
字节按值类别进行着色(空值为蓝色,可打印 ASCII 为青色,空白字符为
绿色,其他控制字符为洋红色,高/非 ASCII 字符为黄色);通过管道输出的内容
为纯文本。
## 使用场景
- **外设逆向工程** — 实时观察设备的枚举过程并交换
供应商控制请求和 HID 报告,无需硬件嗅探器或设置 `usbmon`。当您操作设备时,SETUP 数据包和 payload 会被实时解码。
- **驱动 / 固件调试** — 精确查看您的驱动程序或
应用程序向设备发送了哪些命令以及返回了什么,并提供每次传输的提交→完成延迟。
- **大容量存储 / SCSI 检查** — Bulk-Only Transport 封装被解码为
SCSI 命令(`READ(10) lba=… blocks=…`、`WRITE(10)`、`CSW PASS/FAIL`)。
- **捕获错误** — `--errors-only` 可一次性展示所有设备上的 stalls(`EPIPE`)、超时、
babble 和 CRC 错误。
- **发现恶意设备** — 新插入的设备会在连接的瞬间显示其行为;BadUSB 风格的 HID 注入会表现为您未曾触发的 `INT` 报告或
`SET_REPORT` 控制写入。
- **捕获以供离线分析** — `--json` 输出 NDJSON;通过管道传递给 `jq` 或
文件,以对比不同运行之间的 payload。
- **性能分诊** — 在定时退出时,您将获得每个设备的汇总和
log2 延迟直方图,以找出运行缓慢或频繁通信的设备。
## 标志
| 标志 | 默认值 | 含义 |
| -------------- | ------- | ---------------------------------------------------- |
| `--secs` | 永远 | 运行时长;省略则以运行直到 Ctrl-C 为止(填入数字会停止运行并打印摘要) |
| `--vid`, `--vendor-id` | 任意 | 按供应商 ID 过滤(十六进制 `0x1d6b` 或十进制) |
| `--pid`, `--product-id` | 任意 | 按产品 ID 过滤 |
| `--bus` | 任意 | 按总线编号过滤 |
| `--dev` | 任意 | 按设备地址过滤 |
| `--type` | 全部 | `iso`, `int`, `control`, `bulk` 的 csv 列表 |
| `--no-data` | 关闭 | 不读取传输缓冲区(仅元数据) |
| `--max-data` | `4096` | 每个事件渲染的最大 payload 字节数 |
| `--errors-only`| 关闭 | 仅显示非 OK 的完成状态(跳过 SUBMIT 和 OK) |
| `--hex` | 关闭 | 每次传输的完整多行十六进制转储(否则为紧凑的内联预览) |
| `--json` | 关闭 | 输出 NDJSON(每个事件一个对象)而不是 TTY 视图 |
| `--page-offset-base` | 关闭 | 内核 `page_offset_base` 地址(十六进制)—— 启用 SG payload 捕获 (x86-64) |
| `--vmemmap-base` | 关闭 | 内核 `vmemmap_base` 地址(十六进制)—— 与 `--page-offset-base` 配对 |
所有过滤均在内核端完成,因此被过滤掉的流量永远不会到达
用户空间。
每个事件行都以方括号括起来的所属内核驱动程序结尾
(`[hid_irq_in]`、`[usb_api_blocking_completion]`)—— `urb->complete` 通过
`bpf_snprintf("%ps")` 在内核中符号化,因此不需要查找 `/proc/kallsyms`。
大容量存储的批量传输将其 Bulk-Only Transport 封装解码为
SCSI 命令(`CBW READ(10) lba=… blocks=…` / `CSW PASS`)。在定时退出时
(达到 `--secs`),会打印每个设备的摘要和 log2 延迟直方图;
Ctrl-C 退出会跳过此步骤(因为没有 JS 可见的信号钩子)。
## 分散-聚集 (Scatter-gather) payload
批量流量(大容量存储等)通常向堆栈传递一个 `struct
scatterlist` 数组(`urb->sg`),而不是单一的线性 `transfer_buffer`,因此
payload 分散在不同的页面中。usbsnoop 会遍历该数组并
复制每个段的字节,但要访问它们意味着要将页面转换为内核
虚拟地址 —— 即 x86-64 的 `page_to_virt` 的逆操作,这需要
正在运行的内核的 `page_offset_base` 和 `vmemmap_base`(两者均经过 KASLR 随机化)。
JS isolate 无法读取 `/proc/kallsyms` 且加载器不支持 ksym,
因此您需要传入这两个符号*地址*,然后由 BPF 端对其进行解引用:
```
yeet run . -- \
--page-offset-base 0x$(sudo awk '$3=="page_offset_base"{print $1}' /proc/kallsyms) \
--vmemmap-base 0x$(sudo awk '$3=="vmemmap_base"{print $1}' /proc/kallsyms)
```
如果没有这些标志,SG 传输仍会显示完整的元数据,
只是没有 payload 字节 —— 这是之前的行为。此路径**仅限 x86-64**:在其他架构上请保持标志关闭。
## 限制
- 每次传输仅捕获前 **16384 字节**(它是 2 的幂 ——
验证器的读取截断限制依赖于此)。较大的缓冲区会被截断;
标头仍会报告真实的 `actual/requested` 长度。每个 ring 记录
都带有完整的 `data[16384]`,因此 8 MiB 的 ring 可容纳约 512 个事件。
- 分散-聚集 (Scatter-gather) payload 需要上述的 `--page-offset-base` / `--vmemmap-base` 标志
以及 x86-64 主机;每个段的捕获上限为一页,并且
每次传输最多只遍历前 64 个段。
- 在 usbsnoop 挂载前提交的传输没有开始时间戳,因此其
完成状态不会显示延迟。
- USB 描述符是是小端序并直接读取 —— 在运行 BPF 的
小端序主机上是正确的。
标签:Docker镜像, MITM代理, USB监控, 内核探针, 数据可视化, 目录遍历, 系统运维, 自定义脚本, 防御绕过