Project 4: BadUSB / USB HID Attack Demonstration Tool
Build an ethical, safety-gated USB HID demo device that types pre-approved payloads using TinyUSB, with explicit warnings and user-controlled arming.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Advanced |
| Time Estimate | 2–3 weeks |
| Main Programming Language | C/C++ (ESP-IDF + TinyUSB) |
| Alternative Programming Languages | Arduino (TinyUSB) |
| Coolness Level | High (with strict ethics) |
| Business Potential | Medium (security training, awareness demos) |
| Prerequisites | USB basics, HID reports, embedded UI patterns |
| Key Topics | USB enumeration, HID report formats, safety gating |
1. Learning Objectives
By completing this project, you will:
- Build a USB HID keyboard device on ESP32-S3 using TinyUSB.
- Define HID descriptors and report formats correctly.
- Implement a safe, explicit arming workflow with clear warnings.
- Create a payload scripting format and reliable playback engine.
- Understand ethical constraints and safe usage patterns.
2. All Theory Needed (Per-Concept Breakdown)
2.1 USB Enumeration and HID Descriptors
Fundamentals
USB devices must enumerate before the host will accept them. Enumeration is the handshake where the device presents descriptors that describe what it is and how the host should communicate. For a keyboard device, the key descriptor is the HID report descriptor, which defines the format of key reports. If any descriptor is wrong, the host may reject the device or misinterpret its reports. Understanding enumeration and descriptors is the foundation for reliable HID behavior.
Deep Dive into the concept
The USB stack on ESP32-S3 (via TinyUSB) defines descriptors in C arrays. The device descriptor declares USB version, vendor ID, product ID, and device class. The configuration descriptor describes interfaces and endpoints. For HID, the interface class is HID, and you provide a HID descriptor that points to your HID report descriptor. The report descriptor is a mini language that describes report fields. For a keyboard, it typically includes 8 bytes: modifier keys, reserved, and six keycodes. If the report descriptor mismatches the report you actually send, keystrokes will be garbled or ignored.
Enumeration timing matters. The host expects responses within strict windows; if your firmware blocks or delays, enumeration fails. That’s why TinyUSB runs as its own task; you must not block it. Also, ensure that the device identifies as a keyboard only when safe. You can implement a dual-mode device that enumerates as a vendor-specific device until the user arms it, then re-enumerates as HID. This adds safety by preventing accidental typing on unintended hosts.
When debugging, the host OS logs are essential. On Linux, dmesg shows descriptor errors. On macOS, system_profiler lists USB device details. A small mistake in report descriptor (e.g., wrong usage page) can cause the host to ignore your reports. Therefore, keep a known-good HID example as a reference and modify minimally.
How this fits in projects
USB descriptor discipline carries into P06-custom-application-launcher-mini-os.md (app interface definitions) and the capstone P08-complete-cardputer-security-toolkit.md where USB HID is one module.
Definitions & key terms
- Enumeration → host-device handshake to identify and configure
- Descriptor → data structure describing device capabilities
- HID report descriptor → defines format of HID input reports
- Endpoint → USB communication channel
Mental model diagram (ASCII)
[USB Host] <-> [Descriptors] <-> [HID Interface] <-> [Reports]
How it works (step-by-step, with invariants and failure modes)
- Device connects and presents device descriptor.
- Host requests configuration and HID descriptors.
- Host configures endpoints and enables HID interface.
- Device sends HID reports matching descriptor.
Invariants: descriptors must match reports; TinyUSB task must run. Failure modes: malformed descriptors cause enumeration failure; blocking tasks cause timeouts.
Minimal concrete example
const uint8_t hid_report_desc[] = { TUD_HID_REPORT_DESC_KEYBOARD() };
Common misconceptions
- “Descriptors are optional.” → They define the entire USB identity.
- “Any report size works.” → It must match the report descriptor.
- “Enumeration failures are random.” → They are usually descriptor errors.
Check-your-understanding questions
- What is the role of the HID report descriptor?
- Why must USB tasks not block?
- What happens if your report format doesn’t match the descriptor?
Check-your-understanding answers
- It defines the exact byte layout of HID reports.
- Enumeration has strict timing; blocking causes host timeouts.
- Keystrokes are misinterpreted or ignored.
Real-world applications
- USB keyboards, barcode scanners, and accessibility devices.
- Security training and awareness devices.
Where you’ll apply it
- This project: see §4.1 and §5.10 for descriptor setup.
- Also used in:
P08-complete-cardputer-security-toolkit.md.
References
- TinyUSB HID examples.
- USB HID specification summaries.
Key insight
USB enumeration is strict: your descriptors are the contract, and the host enforces it.
Summary
To build a reliable HID device, master the descriptor chain and keep TinyUSB running without interruption.
Homework/Exercises to practice the concept
- Modify a keyboard report descriptor to add media keys.
- Observe host logs when enumeration fails.
Solutions to the homework/exercises
- Add the consumer control usage page and an extra report ID.
- Compare
dmesgoutput before/after descriptor changes.
2.2 HID Report Encoding and Payload Playback
Fundamentals
HID keyboard reports are fixed-format packets that represent key presses and releases. A basic report contains a modifier byte and up to six keycodes. To “type” a string, you must send a press report, then a release report, with delays between them. If you skip the release, the host thinks the key is held, causing repeated input. For a payload engine, you need a small scripting format that converts text and actions into a sequence of HID reports.
Deep Dive into the concept
Keyboard HID reports are simple but strict. Each report is usually 8 bytes: byte 0 is modifiers (Ctrl, Shift, Alt), byte 1 reserved, bytes 2–7 hold up to six concurrent keycodes. To type a single character, you send a report with that keycode (and shift modifier if needed), then a report with all zeros to indicate release. For macros and payloads, you must serialize these press/release pairs with delays. Timing matters: if you send too fast, the host may drop input; too slow, and the payload becomes sluggish. A reliable strategy is to add a configurable inter-key delay (e.g., 20–50 ms) and a longer delay for special keys like Enter.
You also need to map characters to keycodes and modifiers based on keyboard layout. Most examples assume US layout; if the host uses another layout, characters may differ. For safety and determinism, you should lock the tool to a known layout and display a warning. The payload format can be simple: a line-based script with commands like TYPE, ENTER, WAIT. A parser converts this script into a sequence of HID actions. Error handling is critical: invalid commands should stop execution safely and show an error.
Because this tool is sensitive, you must also provide safe controls. A payload should only execute after explicit arming, and should be interruptible immediately by a hardware button. That means your playback engine must check a “stop” flag between actions. You should also provide a dry-run mode where the payload is printed to screen rather than executed, for review.
How this fits in projects
The idea of deterministic command playback appears in P02-universal-ir-remote-with-learning-capability.md for IR signals and in P07-bluetooth-hid-keyboardmouse-injector.md for BLE HID.
Definitions & key terms
- HID report → fixed-format packet representing key state
- Keycode → numeric code representing a key
- Modifier → shift/control/alt flags
- Payload script → sequence of typed actions
Mental model diagram (ASCII)
[Script] -> [Parser] -> [Keycode Queue] -> [HID Reports] -> [USB Host]
How it works (step-by-step, with invariants and failure modes)
- Parse payload text into actions (TYPE, ENTER, WAIT).
- Convert characters into keycodes + modifiers.
- Send press report, then release report.
- Insert delays and check stop flag between actions.
Invariants: every press has a release; delays are enforced; stop flag checked. Failure modes: missing release causes stuck keys; invalid keycodes cause gibberish; no delays lead to dropped input.
Minimal concrete example
send_report(KEY_A, MOD_SHIFT);
send_report(0, 0); // release
Common misconceptions
- “One report can type a string.” → You need press/release per character.
- “Delays are optional.” → Hosts may drop or reorder without delays.
- “Layout doesn’t matter.” → It absolutely does for special characters.
Check-your-understanding questions
- Why must you send a key release report?
- What happens if the host uses a different keyboard layout?
- Why check a stop flag between actions?
Check-your-understanding answers
- Without release, the host thinks the key is held down.
- Characters may be wrong or payload fails.
- To allow immediate abort for safety.
Real-world applications
- Automated typing tools for testing or demos.
- Accessibility devices that generate keyboard input.
Where you’ll apply it
- This project: see §4.4 algorithm overview and §5.10 Phase 2.
- Also used in:
P07-bluetooth-hid-keyboardmouse-injector.md.
References
- USB HID usage tables.
- TinyUSB HID keyboard examples.
Key insight
Reliable payloads are about correct press/release sequencing and safe timing.
Summary
Convert payload scripts into HID reports with strict press/release discipline and safety checks.
Homework/Exercises to practice the concept
- Implement a TYPE + ENTER command sequence.
- Add a STOP button that cancels playback.
Solutions to the homework/exercises
- Parse line-based commands and enqueue actions with delays.
- Check a volatile stop flag between report sends.
2.3 Safety, Ethics, and Arming Workflow
Fundamentals
BadUSB-style devices are powerful and can be misused. This project must be framed as a security awareness and training tool. Safety is not optional; it is part of the technical design. A safe workflow requires explicit arming, visual warnings, and immediate cancellation. The device should never auto-run payloads on connection. It should also avoid ambiguous states where the user doesn’t know whether it is armed.
Deep Dive into the concept
Ethics are a core requirement in security tooling. A demonstration device must ensure that a user cannot accidentally trigger a payload on an unintended machine. To achieve this, implement a multi-step arming process: for example, the device boots in “safe mode,” shows a warning screen, and requires a long-press on a physical key to arm. Once armed, it should display a distinct color banner or icon. When the USB cable is connected, it still should not run; the user must explicitly select a payload and confirm. A second long-press can start execution. This creates a deliberate two-step gate.
The device should also include a “kill switch.” A dedicated hardware key should cancel execution immediately, clear any queued reports, and optionally re-enumerate as a non-HID device. This is important because once you start sending keystrokes, you may have triggered a sensitive action on the host. The kill switch gives you a way to mitigate damage. Additionally, log every execution to a local file so you can audit what was sent. This creates accountability and helps prevent misuse.
From a UI perspective, you should display context: which host is connected (if the OS exposes a name), which payload is selected, and whether the device is armed. Use a bright, unmistakable warning label. Provide a demo-only payload set that is harmless (e.g., open a text editor and type “training demo”). Avoid including dangerous scripts. This is part of ethical design.
How this fits in projects
Safety workflows apply across the series, especially in P07-bluetooth-hid-keyboardmouse-injector.md and P08-complete-cardputer-security-toolkit.md.
Definitions & key terms
- Arming → deliberate action required before execution
- Kill switch → immediate stop/cancel mechanism
- Safe mode → device state with no HID functionality
- Audit log → record of executed payloads
Mental model diagram (ASCII)
[Safe Mode] -> [Armed] -> [Payload Selected] -> [Confirm] -> [Execute]
^ |
+------------------- Kill Switch -------------------+
How it works (step-by-step, with invariants and failure modes)
- Device boots in Safe Mode with warnings.
- User long-presses to arm; UI switches to Armed state.
- User selects payload and confirms.
- Payload executes; user can cancel at any time.
- After completion, device returns to Safe Mode.
Invariants: no execution without explicit arm; kill switch always works. Failure modes: UI fails to show state; accidental auto-run; stop flag not checked.
Minimal concrete example
if (!armed) return; // block all HID sends
Common misconceptions
- “It’s just a demo; no safety needed.” → Demos still cause real harm.
- “A single confirmation is enough.” → Use multi-step arming.
- “Logging is unnecessary.” → Logging is an accountability safeguard.
Check-your-understanding questions
- Why should the device boot in Safe Mode?
- What is the purpose of a kill switch?
- Why include an audit log?
Check-your-understanding answers
- To prevent accidental execution on an unintended host.
- To stop payloads immediately if something goes wrong.
- To provide accountability and debugging data.
Real-world applications
- Security training labs.
- USB device risk awareness demos.
Where you’ll apply it
- This project: see §3.2 requirement 5 and §5.5 design questions.
- Also used in:
P07-bluetooth-hid-keyboardmouse-injector.md,P08-complete-cardputer-security-toolkit.md.
References
- Ethical guidelines in penetration testing handbooks.
- USB security awareness resources.
Key insight
Safety is a design constraint, not a disclaimer.
Summary
A responsible HID demo tool must require explicit arming, provide clear warnings, and offer immediate cancellation.
Homework/Exercises to practice the concept
- Design a UI flow with two confirmations and a kill switch.
- Implement an audit log entry after each execution.
Solutions to the homework/exercises
- Use a state machine with SAFE → ARMED → READY → EXECUTE states.
- Append payload name and timestamp to a log file on SD.
3. Project Specification
3.1 What You Will Build
A USB HID demo device that:
- enumerates as a keyboard when armed,
- executes safe, pre-approved payloads,
- provides an on-device payload editor,
- includes explicit warnings, arming, and kill switch.
3.2 Functional Requirements
- USB enumeration: stable HID keyboard device using TinyUSB.
- Payload engine: parse simple script commands (TYPE, WAIT, ENTER).
- Safety: multi-step arming and immediate stop control.
- UI: select, edit, and execute payloads with warnings.
- Logging: store execution logs on SD.
3.3 Non-Functional Requirements
- Reliability: enumerates on at least two host OSes.
- Safety: no payload executes without explicit confirmation.
- Usability: payloads can be reviewed before execution.
3.4 Example Usage / Output
1) Boot -> Safe Mode
2) Long-press [OK] to arm
3) Select payload “Demo: Open Notepad”
4) Confirm and run
3.5 Data Formats / Schemas / Protocols
Payload format (line-based):
WAIT 500
TYPE "hello world"
ENTER
3.6 Edge Cases
- Host disconnects mid-payload.
- Keyboard layout mismatch.
- User cancels mid-execution.
3.7 Real World Outcome
A successful build enumerates reliably and executes a harmless demo payload only after explicit arming and confirmation, with a visible warning banner at all times.
3.7.1 How to Run (Copy/Paste)
idf.py set-target esp32s3
idf.py build
idf.py -p /dev/ttyUSB0 flash monitor
3.7.2 Golden Path Demo (Deterministic)
- Arm device, select “Demo: Open Notepad.”
- Host opens text editor and types “training demo”.
Failure demo (deterministic):
- Connect USB while unarmed and press Run. Expected: device refuses to execute, shows “SAFE MODE,” and no keystrokes are sent. Exit code: 1.
3.7.3 If CLI: exact terminal transcript
I (4100) usb: enumerated HID
I (4101) safety: armed=YES payload=demo_notepad
I (4102) payload: started
I (4103) payload: completed ok
Exit codes: 0 = success, 1 = not armed, 2 = payload parse error, 3 = USB enumeration error.
3.7.4 If Web App
Not applicable.
3.7.5 If API
Not applicable.
3.7.6 If Library
Not applicable.
3.7.7 If GUI / Desktop / Mobile
Not applicable.
3.7.8 If TUI
+----------------------------+
| USB HID Demo |
| Status: ARMED |
| Payload: Demo_Notepad |
| [OK] Run [X] Cancel |
+----------------------------+
4. Solution Architecture
4.1 High-Level Design
[UI] -> [Safety State] -> [Payload Engine] -> [HID Reports] -> [USB]
4.2 Key Components
| Component | Responsibility | Key Decisions |
|---|---|---|
| TinyUSB task | Enumeration and HID | Must never block |
| Payload parser | Convert script to actions | Simple line-based DSL |
| Safety state machine | Arming + kill switch | Multi-step confirmation |
| UI | Warnings + selection | High contrast, explicit status |
4.3 Data Structures (No Full Code)
typedef struct {
bool armed;
bool executing;
char payload_name[32];
} safety_state_t;
4.4 Algorithm Overview
Key Algorithm: Payload Execution
- Parse script lines into actions.
- For each action, send HID report or wait.
- Check stop flag between actions.
Complexity Analysis:
- Time: O(n) actions
- Space: O(n) actions
5. Implementation Guide
5.1 Development Environment Setup
idf.py set-target esp32s3
idf.py build
5.2 Project Structure
project-root/
├── main/
│ ├── usb_hid.c
│ ├── payload.c
│ ├── safety.c
│ ├── ui.c
│ └── storage.c
└── README.md
5.3 The Core Question You’re Answering
“How do I build a USB HID device that behaves predictably and ethically?”
5.4 Concepts You Must Understand First
- USB enumeration and descriptors.
- HID report encoding.
- Safety state machines.
5.5 Questions to Guide Your Design
- What is your payload format and error handling strategy?
- How will you ensure safe mode on boot?
- What is your kill switch behavior?
5.6 Thinking Exercise
Design a payload DSL that can type, wait, and press hotkeys safely.
5.7 The Interview Questions They Will Ask
- Why is USB HID a security risk?
- What descriptors are required for HID?
- How do you prevent accidental execution?
5.8 Hints in Layers
Hint 1: Start with a single key press and release.
Hint 2: Add string typing with delays.
Hint 3: Build a safety state machine with explicit arming.
5.9 Books That Will Help
| Topic | Book | Chapter |
|---|---|---|
| USB basics | Making Embedded Systems | Ch. 7 |
| Security ethics | Penetration Testing | Ch. 1 |
5.10 Implementation Phases
Phase 1: Enumeration + Single Key (3–4 days)
Phase 2: Payload Engine + UI (5–7 days)
Phase 3: Safety + Logging (4–6 days)
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale |
|---|---|---|---|
| Payload format | JSON, DSL | DSL | Simple and readable |
| Layout | US only, multi-layout | US only | Deterministic behavior |
| Arming | single vs multi-step | multi-step | Safer operation |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples |
|---|---|---|
| Unit Tests | Payload parsing | invalid commands |
| Integration Tests | USB + payload | open editor and type |
| Safety Tests | arming flow | ensure no auto-run |
6.2 Critical Test Cases
- Device does not type when unarmed.
- Kill switch stops payload immediately.
- Enumeration succeeds on Windows and macOS.
6.3 Test Data
Payload: TYPE "hello" ENTER
Payload: WAIT 1000
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution |
|---|---|---|
| Wrong report descriptor | gibberish input | use known-good descriptor |
| Missing release | stuck key | send empty report |
| No safety gate | accidental typing | enforce safe mode |
7.2 Debugging Strategies
- Use host logs (
dmesg, Windows Device Manager) to validate descriptors. - Test with a text editor before any other apps.
7.3 Performance Traps
- Overly short delays cause lost keystrokes.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add a “dry run” mode that prints payload without executing.
8.2 Intermediate Extensions
- Add keyboard layout switching.
8.3 Advanced Extensions
- Add dual-mode enumeration: vendor device until armed.
9. Real-World Connections
9.1 Industry Applications
- Security training labs.
- USB device compliance testing.
9.2 Related Open Source Projects
- TinyUSB HID examples.
9.3 Interview Relevance
- USB descriptors, device classes, and safe firmware design.
10. Resources
10.1 Essential Reading
- USB HID usage tables.
- TinyUSB documentation.
10.2 Video Resources
- USB HID descriptor walkthroughs.
10.3 Tools & Documentation
- USB analyzer tools (Wireshark + usbmon).
10.4 Related Projects in This Series
P07-bluetooth-hid-keyboardmouse-injector.mdP08-complete-cardputer-security-toolkit.md
11. Self-Assessment Checklist
11.1 Understanding
- I can explain USB enumeration.
- I can describe HID report formats.
11.2 Implementation
- Device enumerates reliably.
- Safety workflow prevents accidental execution.
11.3 Growth
- I can explain why HID tools require ethics safeguards.
12. Submission / Completion Criteria
Minimum Viable Completion:
- Enumerates and types a safe demo payload after arming.
Full Completion:
- Multi-step safety workflow and payload editor.
Excellence (Going Above & Beyond):
- Dual-mode enumeration and audit logging.