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 ``` ![usbsnoop 演示](https://raw.githubusercontent.com/TechnomancerIncrease/usbsnoop-766/main/assets/demo.gif) **全系统** 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监控, 内核探针, 数据可视化, 目录遍历, 系统运维, 自定义脚本, 防御绕过