
Live, systemweiter USB-Transfer-Sniffer in eBPF — dekodiert USB-Datenverkehr inline (Control-SETUP, SCSI, HID) über zwei universelle URB-Hooks. Kein usbmon, kein Hardware-Sniffer. CO-RE-portabel.

Ein Echtzeit-, farbcodierter Feed von USB-Verkehr systemweit — aufgebaut auf den zwei universellen URB-Engpässen, durch die jeder Host-Controller-Treiber leitet, sodass er auf xHCI/EHCI/OHCI/dwc gleichermaßen funktioniert, ohne kontrollerspezifische Tracepoints und ohne usbmon. Vollständig CO-RE-portabel.
| fentry hook | was es uns verrät |
|---|---|
usb_submit_urb | eine Übertragung wurde in die Warteschlange gestellt (Gerät, Endpoint, Typ, Nutzlast) |
usb_hcd_giveback_urb | sie wurde abgeschlossen (Status, bewegte Bytes, Latenz, Nutzlast) |
Ein lru_hash, der nach dem URB-Zeiger geschlüsselt ist, verbindet die beiden: SUBMIT trägt eine Startzeit ein, der Abschluss liest sie für die Submit→Complete-Latenz wieder aus und löscht sie dann. Dies spiegelt die Request/Response-Kopplung von httpbody wider — SUBMIT ist die „Anfrage“ (was der Host sendet), COMPLETE die „Antwort“ (was das Gerät zurückgibt).
Bei Control-Transfers wird das 8-Byte-SETUP-Paket in den Standard-Request-Namen dekodiert (GET_DESCRIPTOR, SET_CONFIGURATION, …); Daten-Phasen werden als Text dargestellt, wenn sie wie Text aussehen, andernfalls als Hexdump.
Die Ausgabe ist eine Zeile pro Ereignis (kompakt). Wenn ein Gerät zum ersten Mal erscheint, erhält es eine ▸-Legendenzeile (bus-dev, vid:pid, Produkt, Linkgeschwindigkeit); danach trägt jede Zeile nur das kurze DEV-Tag, sodass die linken Spalten auch bei starkem Verkehr ausgerichtet und übersichtlich bleiben. Jede Zeile zeigt Zeit, Art (SUBMIT/CMPLT), Übertragungstyp, epNdir, den Richtungspfeil (← Gerät→Host IN, → Host→Gerät OUT), Byte-Anzahl, Status, Latenz und den zugehörigen Kernel-Treiber, dann ein · und das nützlichste Detail (dekodiertes SETUP, SCSI-Befehl oder eine kurze Nutzlast-Vorschau). Übergeben Sie stattdessen --hex für den vollständigen mehrzeiligen Hexdump. Hex-Bytes werden auf einem TTY nach Wertklasse eingefärbt (Null blau, druckbares ASCII cyan, Leerzeichen grün, andere Steuerzeichen magenta, hohe/Nicht-ASCII-Werte gelb); bei weitergeleiteter Ausgabe bleiben sie ungefärbt.
usbmon-Einrichtung. SETUP-Pakete und Nutzlasten werden dekodiert, während Sie das Gerät bedienen.READ(10) lba=… blocks=…, WRITE(10), CSW PASS/FAIL).--errors-only zeigt Stalls (EPIPE), Timeouts, Babble und CRC-Fehler auf allen Geräten gleichzeitig.INT-Reports oder SET_REPORT-Control-Writes, die Sie nicht ausgelöst haben.--json erzeugt NDJSON; leiten Sie es an jq oder eine Datei weiter, um Nutzlasten über Läufe hinweg zu vergleichen.curl -fsSL https://yeet.cx | sh
Dann führen Sie es direkt von GitHub aus — yeet holt das Beispiel und baut es für Sie, kein Klonen erforderlich:
yeet run github:yeet-src/usbsnoop
Um stattdessen aus einem lokalen Checkout zu bauen:
make
Schreibt das BTF des Kernels nach vmlinux.h (für struct urb, usb_device und den Geräte-Deskriptor) und kompiliert dann. Erfordert clang, bpftool und einen Kernel mit BTF.
yeet run . # all devices, runs until Ctrl-C
yeet run . -- --secs 30 # stop after 30s (prints a summary)
yeet run . -- --vid 0x320f # one vendor
yeet run . -- --vendor-id 0x046d --product-id 0xc52b # one device by id
yeet run . -- --bus 3 --dev 4 # one device by bus address
yeet run . -- --type control,int # only these transfer types
yeet run . -- --no-data # metadata only, skip payload capture
yeet run . -- --max-data 64 # cap rendered payload at 64 bytes
yeet run . -- --errors-only # only failed completions (stalls, timeouts)
yeet run . -- --hex # full multi-line hexdump per transfer
yeet run . -- --json | jq . # NDJSON, one object per event
| Flag | Standard | Bedeutung |
|---|---|---|
--secs | unbegrenzt | wie lange laufen; weglassen, um bis Ctrl-C zu laufen (eine Zahl stoppt + gibt eine Zusammenfassung aus) |
--vid, --vendor-id | beliebig | nach Hersteller-ID filtern (hex 0x1d6b oder dezimal) |
--pid, --product-id | beliebig | nach Produkt-ID filtern |
--bus | beliebig | nach Busnummer filtern |
--dev | beliebig | nach Geräteadresse filtern |
--type | alle | CSV von iso, int, control, bulk |
--no-data | aus | Übertragungspuffer nicht lesen (nur Metadaten) |
--max-data | 4096 | max. angezeigte Nutzlast-Bytes pro Ereignis |
--errors-only | aus | nur nicht-OK-Abschlüsse zeigen (überspringt SUBMIT und OK) |
--hex | aus | vollständiger mehrzeiliger Hexdump pro Übertragung (andernfalls kompakte Inline-Vorschau) |
--json | aus | NDJSON ausgeben (ein Objekt pro Ereignis) statt der TTY-Ansicht |
--page-offset-base | aus | Kernel-page_offset_base-Adresse (hex) — ermöglicht SG-Nutzlasterfassung (x86-64) |
--vmemmap-base | aus | Kernel-vmemmap_base-Adresse (hex) — gepaart mit --page-offset-base |
Die gesamte Filterung erfolgt kernel-seitig, sodass herausgefilterter Verkehr nie den Userspace erreicht.
Jede Ereigniszeile endet mit dem zugehörigen Kernel-Treiber in eckigen Klammern ([hid_irq_in], [usb_api_blocking_completion]) — urb->complete wird kernel-intern über bpf_snprintf("%ps") symbolisiert, sodass kein /proc/kallsyms-Lookup nötig ist. Massenspeicher-Bulk-Transfers dekodieren ihren Bulk-Only-Transport-Wrapper in den SCSI-Befehl (CBW READ(10) lba=… blocks=… / CSW PASS). Bei einem zeitgesteuerten Ende (Erreichen von --secs) werden eine Pro-Gerät-Zusammenfassung und ein log2-Latenzhistogramm ausgegeben; ein Ctrl-C-Ende überspringt sie (es gibt keinen JS-sichtbaren Signal-Hook).
Bulk-Verkehr (Massenspeicher und Verwandte) übergibt dem Stack oft ein struct scatterlist-Array (urb->sg) statt eines einzelnen linearen transfer_buffer, sodass die Nutzlast über Seiten verstreut liegt. usbsnoop durchläuft dieses Array und kopiert die Bytes jedes Segments, aber um sie zu erreichen, muss eine Seite in ihre virtuelle Kernel-Adresse übersetzt werden — die Umkehrung von x86-64s page_to_virt, die das page_offset_base des laufenden Kernels und das vmemmap_base benötigt (beide KASLR-randomisiert).
Die JS-Isolate kann /proc/kallsyms nicht lesen und der Lader hat keine ksym-Unterstützung, daher übergeben Sie die beiden Symbol-Adressen und die BPF-Seite dereferenziert sie: