Documentation
¶
Overview ¶
Package usbhid decodes USB HID Keyboard Boot Protocol reports — the 8-byte input reports that every BadUSB-class device (Hak5 Rubber Ducky, Bash Bunny, OMG Cable, Adafruit Trinket BadUSB, the Bruce ESP32 BadUSB add-on) generates to inject keystrokes into a victim host.
This package is the **defensive sibling** of the badusb_* family — those Specs *generate* BadUSB scripts and target profiles; this decoder *reconstructs* the keystrokes from a usbmon capture of a suspected rogue device, so an incident responder can answer "what did the attacker actually type?" from a pcap alone.
Operationally, this Spec is the post-incident forensic primitive used in:
- **Insider-threat investigations** — a rogue HID device plugged into a workstation; the corporate USB-monitoring stack (usbmon, Sysmon, EDR) recorded the URBs but not the rendered text.
- **DEF CON Recon Village CTFs** — challenges that hand out a usbmon pcap and ask "what was typed".
- **Vendor abuse triage** — a benign HID device suspected of typing without operator intent; comparing recorded reports against authorised payloads.
Wrap-vs-native judgement
Native. The USB HID Usage Tables (HID 1.11 + HUT 1.5) are publicly available; the 8-byte Boot Protocol Keyboard Report layout is fully fixed (1-byte modifier bitmap + 1-byte reserved + 6 bytes of active HID Usage codes). No crypto at the parse layer. The hard part is the reconstruction policy: how to turn a stream of "currently held" reports into a sequence of distinct keystroke events + a DuckyScript-style transcript.
What this package covers
**Per-report decode** (USB HID 1.11 §B.1 Boot Protocol, 8 bytes):
byte 0: **Modifier bitmap** — bit 0 LCtrl + bit 1 LShift + bit 2 LAlt + bit 3 LGui + bit 4 RCtrl + bit 5 RShift + bit 6 RAlt + bit 7 RGui.
byte 1: Reserved (= 0).
bytes 2-7: up to 6 simultaneous keys held as HID Usage codes (Usage Page Keyboard/Keypad). 0x00 padding for unused slots. 0x01-0x03 are error codes (ErrorRollOver / POSTFail / ErrorUndefined).
**80+ entry HID Usage code name + Shift-variant table** (HID Usage Tables 1.5 §10 — selected high-runners):
0x04-0x1D `a..z` (Shift → `A..Z`).
0x1E-0x27 `1..9 0` (Shift → `!@#$%^&*()`).
0x28 `Enter` / 0x29 `Escape` / 0x2A `Backspace` / 0x2B `Tab` / 0x2C `Space`.
0x2D-0x38 punctuation row (`-/_`, `=/+`, `[/{`, `]/}`, `\/|`, `;/:`, `'/"`, “ `/~ “, `,/<`, `./>`, `//?`).
0x39 Caps Lock.
0x3A-0x45 `F1..F12`.
0x4A-0x4E `Home`, `PageUp`, `Delete`, `End`, `PageDown`.
0x4F-0x52 arrow keys (`Right`, `Left`, `Down`, `Up`).
0x53 NumLock + 0x54-0x63 keypad.
**Key-down event detection** by report-to-report diffing — successive reports declare which keys are currently held; transitions from "not in previous report" to "in current report" mark a fresh keystroke. Suppresses repeat reports of the same key-held state.
**Reconstructed text** — best-effort string concatenation of every printable key-down event (Shift state honoured). Caps Lock toggling is tracked across the report stream.
**DuckyScript-style transcript** — produces a sequence of directives that, fed back into a Rubber-Ducky-class encoder, would approximate the same keystroke sequence:
Consecutive printable characters → `STRING "<text>"`.
Standalone non-printable keys → their DuckyScript keyword (`ENTER`, `TAB`, `ESC`, `BACKSPACE`, `DELETE`, `UP`, `DOWN`, `LEFT`, `RIGHT`, `F1..F12`, `HOME`, `END`, `PAGEUP`, `PAGEDOWN`, `CAPSLOCK`).
Modifier + key combinations → DuckyScript modifier keywords (`CTRL`, `SHIFT`, `ALT`, `GUI`, `CTRL-SHIFT`, `CTRL-ALT`, `ALT-SHIFT`, `GUI-SHIFT`) followed by the bare key.
What this package does NOT cover (deliberately out of scope)
- **usbmon framing** — operators must extract the 8-byte HID reports from a usbmon pcap (Linux) or USBPcap (Windows) by stripping the per-URB headers; this decoder takes the concatenated report stream as hex input.
- **USB enumeration descriptors** — Device / Configuration / Interface / HID Report descriptors that *declare* the report layout (vendor ID, product ID, report-ID field, non-Boot-Protocol report shapes) are out of scope.
- **Composite HID devices** — devices that mix Keyboard + Mouse + Consumer Control reports in the same pipe; callers must split per-report-ID streams before feeding this decoder.
- **Non-Boot-Protocol reports** — devices that opt out of Boot Protocol and define a custom HID Report Descriptor (with Report ID + per-key bitmaps + variable-length reports) are out of scope. Most BadUSB hardware uses Boot Protocol, but enterprise keyboards often don't.
- **Locale-specific keymaps** — the Shift-variant table reflects US QWERTY; reports from a UK / DE / FR / ES / IT / Dvorak / Colemak host would map to different printable characters. Operators with non-US keymaps must re-interpret the surfaced HID Usage codes against their local layout.
- **Replay timing analysis** — the per-report inter-arrival timing in a usbmon pcap can fingerprint BadUSB vs human typing (BadUSB devices type at uniform sub-10ms cadences); this decoder works on pure hex without timing metadata.
- **DuckyScript v2 / v3 control flow** — DuckyScript v2+ adds `IF`, `WHILE`, `VAR`, `RANDOM_INT()`, etc.; this decoder only outputs the v1 STRING / modifier / key primitives that map back from observed keystrokes.
Index ¶
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func ExtractUsbmonReports ¶ added in v0.366.0
ExtractUsbmonReports pulls the 8-byte USB HID Keyboard Boot Protocol reports out of a Linux usbmon text capture (the format emitted by `cat /sys/kernel/debug/usb/usbmon/<N>u`, and what tshark/Wireshark show for usbmon-sourced captures). It returns the concatenated report bytes as a hex string ready for Decode, plus the number of reports found.
usbmon text line format ¶
Each line is, approximately:
<urb-tag> <timestamp> <type> <xfer><dir>:<bus>:<dev>:<ep> <status> <len> <marker> [<data-words>]
e.g.
ffff8801ab33e3c0 1369381512 C Ii:1:003:1 0 8 = 00000400 00000000 - type: S (submit), C (callback/complete), E (submission error) - xfer: C control, Z isoc, I interrupt, B bulk - dir: i in, o out - marker: '=' data follows, '<' data not captured / none - data: bytes in capture order, visually grouped into 4-byte words
What is extracted, and the heuristic ¶
A keyboard Boot Protocol report is exactly 8 bytes and arrives on an Interrupt-IN endpoint, surfaced to the host on the callback (C) line. ExtractUsbmonReports therefore keeps every line that is a callback (C), on an Interrupt-IN transfer (xfer/dir "Ii"), carries data (marker '='), and is exactly 8 bytes long. The 8-byte filter is what separates the keyboard from a co-resident mouse (3-4 byte boot reports) on the same bus; the per-report decode in Decode then validates the Boot Protocol structure. Submit (S) lines, Interrupt-OUT (Io, e.g. LED reports), control/bulk transfers, and non-8-byte interrupt data are skipped.
Data bytes are printed in capture order (no endian swap), grouped into 4-byte words for readability; the words are simply concatenated.
Types ¶
type KeyDownEvent ¶
type KeyDownEvent struct {
ReportIndex int `json:"report_index"`
Code int `json:"code"`
Name string `json:"name"`
Modifiers []string `json:"modifiers,omitempty"`
// Printable rendering of this key under the current modifier
// state (empty for non-printable keys).
Char string `json:"char,omitempty"`
}
KeyDownEvent is a key-press transition (key present in this report, absent from previous).
type Report ¶
type Report struct {
ModifierHex string `json:"modifier_hex"`
ModifiersActive []string `json:"modifiers_active,omitempty"`
Keys []KeyRef `json:"keys,omitempty"`
}
Report is one 8-byte HID Keyboard Boot Protocol report.
type Result ¶
type Result struct {
TotalBytes int `json:"total_bytes"`
ReportCount int `json:"report_count"`
Reports []Report `json:"reports"`
// Aggregated outputs.
KeyDownEvents []KeyDownEvent `json:"key_down_events"`
ReconstructedText string `json:"reconstructed_text"`
DuckyScript string `json:"duckyscript"`
}
Result is the structured forensic decode of a stream of USB HID Keyboard Boot Protocol reports.