{"id":51002117,"url":"https://github.com/yeet-src/usbsnoop","last_synced_at":"2026-06-20T15:33:14.458Z","repository":{"id":361508068,"uuid":"1253938455","full_name":"yeet-src/usbsnoop","owner":"yeet-src","description":"Live, system-wide USB transfer sniffer in eBPF — decodes USB traffic inline (control SETUP, SCSI, HID) from two universal URB hooks. No usbmon, no hardware sniffer. CO-RE portable.","archived":false,"fork":false,"pushed_at":"2026-05-31T00:21:10.000Z","size":9820,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-31T01:14:37.823Z","etag":null,"topics":["bpf","co-re","ebpf","libbpf","linux","observability","reverse-engineering","tracing","usb","usb-sniffer","yeet"],"latest_commit_sha":null,"homepage":"https://yeet.cx","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/yeet-src.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T00:53:55.000Z","updated_at":"2026-05-31T01:13:02.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/yeet-src/usbsnoop","commit_stats":null,"previous_names":["yeet-src/usbsnoop"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/yeet-src/usbsnoop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fusbsnoop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fusbsnoop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fusbsnoop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fusbsnoop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/yeet-src","download_url":"https://codeload.github.com/yeet-src/usbsnoop/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/yeet-src%2Fusbsnoop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34576043,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-20T02:00:06.407Z","response_time":98,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bpf","co-re","ebpf","libbpf","linux","observability","reverse-engineering","tracing","usb","usb-sniffer","yeet"],"created_at":"2026-06-20T15:33:13.827Z","updated_at":"2026-06-20T15:33:14.452Z","avatar_url":"https://github.com/yeet-src.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# usbsnoop — live USB transfer sniffer from two fentry hooks\n\n![usbsnoop demo](assets/demo.gif)\n\nA real-time, colorized feed of USB traffic **system-wide** — built on the two\nuniversal URB chokepoints every host-controller driver funnels through, so it\nworks on xHCI/EHCI/OHCI/dwc alike with no per-controller tracepoints and no\n`usbmon`. Fully CO-RE portable.\n\n| fentry hook              | what it tells us                                        |\n| ------------------------ | ------------------------------------------------------- |\n| `usb_submit_urb`         | a transfer was queued (device, endpoint, type, payload) |\n| `usb_hcd_giveback_urb`   | it completed (status, bytes moved, latency, payload)    |\n\nAn `lru_hash` keyed by the URB pointer stitches the two together: submit stamps\na start time, completion reads it back for the submit→complete latency, then\ndeletes it. This mirrors `httpbody`'s request/response pairing — **SUBMIT** is\nthe \"request\" (what the host sends), **COMPLETE** the \"response\" (what the\ndevice returns).\n\nControl transfers get their 8-byte SETUP packet decoded into the standard\nrequest name (`GET_DESCRIPTOR`, `SET_CONFIGURATION`, …); data stages render as\ntext when they look textual and as a hexdump otherwise.\n\nOutput is **one line per event** (compact). The first time a device appears it\ngets a `▸` legend line (`bus-dev`, `vid:pid`, product, link speed); after that\neach row carries only the short `DEV` tag, so the left-hand columns stay aligned\nand scannable under heavy traffic. Each row shows time, kind (SUBMIT/CMPLT),\ntransfer type, `epNdir`, the direction arrow (`←` device→host IN, `→`\nhost→device OUT), byte counts, status, latency, and the owning kernel driver,\nthen a `·` and the most useful detail (decoded SETUP, SCSI command, or a short\npayload preview). Pass `--hex` for the full multi-line hexdump instead. Hex\nbytes are colored by value class (null blue, printable ASCII cyan, whitespace\ngreen, other control magenta, high/non-ASCII yellow) on a TTY; piped output is\nplain.\n\n## Use cases\n\n- **Reverse-engineering peripherals** — watch a device enumerate and exchange\n  vendor control requests and HID reports live, no hardware sniffer or `usbmon`\n  setup. SETUP packets and payloads are decoded as you poke at the device.\n- **Driver / firmware debugging** — see exactly which commands your driver or\n  app sends a device and what comes back, with submit→complete latency on every\n  transfer.\n- **Mass-storage / SCSI inspection** — Bulk-Only Transport wrappers decode to\n  the SCSI command (`READ(10) lba=… blocks=…`, `WRITE(10)`, `CSW PASS/FAIL`).\n- **Catching errors** — `--errors-only` surfaces stalls (`EPIPE`), timeouts,\n  babble, and CRC errors across every device at once.\n- **Spotting rogue devices** — a freshly plugged device shows what it does the\n  instant it attaches; BadUSB-style HID injection surfaces as `INT` reports or\n  `SET_REPORT` control writes you didn't trigger.\n- **Capture for offline analysis** — `--json` emits NDJSON; pipe to `jq` or a\n  file to diff payloads across runs.\n- **Performance triage** — on a timed exit you get a per-device rollup and a\n  log2 latency histogram to find the slow or chatty devices.\n\n## Install\n\n```sh\ncurl -fsSL https://yeet.cx | sh\n```\n\nThen run it straight from GitHub — yeet fetches the example and builds it for\nyou, no clone needed:\n\n```sh\nyeet run github:yeet-src/usbsnoop\n```\n\n## Build\n\nTo build from a local checkout instead:\n\n```sh\nmake\n```\n\nDumps the kernel's BTF to `vmlinux.h` (for `struct urb`, `usb_device`, and the\ndevice descriptor), then compiles. Requires `clang`, `bpftool`, and a kernel\nwith BTF.\n\n## Run\n\n```sh\nyeet run .                              # all devices, runs until Ctrl-C\nyeet run . -- --secs 30                 # stop after 30s (prints a summary)\nyeet run . -- --vid 0x320f              # one vendor\nyeet run . -- --vendor-id 0x046d --product-id 0xc52b # one device by id\nyeet run . -- --bus 3 --dev 4           # one device by bus address\nyeet run . -- --type control,int        # only these transfer types\nyeet run . -- --no-data                 # metadata only, skip payload capture\nyeet run . -- --max-data 64             # cap rendered payload at 64 bytes\nyeet run . -- --errors-only             # only failed completions (stalls, timeouts)\nyeet run . -- --hex                      # full multi-line hexdump per transfer\nyeet run . -- --json | jq .             # NDJSON, one object per event\n```\n\n## Flags\n\n| flag           | default | meaning                                              |\n| -------------- | ------- | ---------------------------------------------------- |\n| `--secs`       | forever | how long to run; omit to run until Ctrl-C (a number stops + prints a summary) |\n| `--vid`, `--vendor-id`  | any | filter by vendor id (hex `0x1d6b` or decimal)    |\n| `--pid`, `--product-id` | any | filter by product id                             |\n| `--bus`        | any     | filter by bus number                                 |\n| `--dev`        | any     | filter by device address                             |\n| `--type`       | all     | csv of `iso`, `int`, `control`, `bulk`               |\n| `--no-data`    | off     | don't read transfer buffers (metadata only)          |\n| `--max-data`   | `4096`  | max bytes of payload rendered per event              |\n| `--errors-only`| off     | show only non-OK completions (skips SUBMIT and OK)   |\n| `--hex`        | off     | full multi-line hexdump per transfer (compact inline preview otherwise) |\n| `--json`       | off     | emit NDJSON (one object per event) instead of the TTY view |\n| `--page-offset-base` | off | kernel `page_offset_base` address (hex) — enables SG payload capture (x86-64) |\n| `--vmemmap-base`     | off | kernel `vmemmap_base` address (hex) — paired with `--page-offset-base` |\n\nAll filtering happens kernel-side, so filtered-out traffic never reaches\nuserspace.\n\nEach event line ends with the owning kernel driver in brackets\n(`[hid_irq_in]`, `[usb_api_blocking_completion]`) — `urb-\u003ecomplete` symbolized\nin-kernel via `bpf_snprintf(\"%ps\")`, so no `/proc/kallsyms` lookup is needed.\nMass-storage bulk transfers decode their Bulk-Only Transport wrapper into the\nSCSI command (`CBW READ(10) lba=… blocks=…` / `CSW PASS`). On a timed exit\n(reaching `--secs`) a per-device summary and a log2 latency histogram print;\na Ctrl-C exit skips it (there is no JS-visible signal hook).\n\n## Scatter-gather payloads\n\nBulk traffic (mass storage and friends) often hands the stack a `struct\nscatterlist` array (`urb-\u003esg`) instead of a single linear `transfer_buffer`, so\nthe payload lives scattered across pages. usbsnoop walks that array and copies\neach segment's bytes, but reaching them means translating a page to its kernel\nvirtual address — the inverse of x86-64's `page_to_virt`, which needs the\nrunning kernel's `page_offset_base` and `vmemmap_base` (both KASLR-randomized).\n\nThe JS isolate can't read `/proc/kallsyms` and the loader has no ksym support,\nso you pass the two symbol *addresses* in and the BPF side dereferences them:\n\n```sh\nyeet run . -- \\\n  --page-offset-base 0x$(sudo awk '$3==\"page_offset_base\"{print $1}' /proc/kallsyms) \\\n  --vmemmap-base     0x$(sudo awk '$3==\"vmemmap_base\"{print $1}'     /proc/kallsyms)\n```\n\nWithout those flags, SG transfers still show full metadata, just no payload\nbytes — the prior behavior. This path is **x86-64 only**: on other arches leave\nthe flags off.\n\n## Limits\n\n- Only the first **16384 bytes** of each transfer are captured (a power of two —\n  the verifier read-clamp depends on it). Larger buffers are truncated; the\n  header still reports the true `actual/requested` length. Each ring record\n  carries a full `data[16384]`, so the 8 MiB ring holds ~512 events.\n- Scatter-gather payloads need the `--page-offset-base` / `--vmemmap-base` flags\n  above and an x86-64 host; each segment is captured up to a page, and only the\n  first 64 segments of a transfer are walked.\n- A transfer submitted before usbsnoop attached has no start stamp, so its\n  completion shows no latency.\n- USB descriptors are little-endian and read directly — correct on the\n  little-endian hosts BPF runs on.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeet-src%2Fusbsnoop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fyeet-src%2Fusbsnoop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fyeet-src%2Fusbsnoop/lists"}