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:

  1. Build a USB HID keyboard device on ESP32-S3 using TinyUSB.
  2. Define HID descriptors and report formats correctly.
  3. Implement a safe, explicit arming workflow with clear warnings.
  4. Create a payload scripting format and reliable playback engine.
  5. 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)

  1. Device connects and presents device descriptor.
  2. Host requests configuration and HID descriptors.
  3. Host configures endpoints and enables HID interface.
  4. 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

  1. What is the role of the HID report descriptor?
  2. Why must USB tasks not block?
  3. What happens if your report format doesn’t match the descriptor?

Check-your-understanding answers

  1. It defines the exact byte layout of HID reports.
  2. Enumeration has strict timing; blocking causes host timeouts.
  3. 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

  1. Modify a keyboard report descriptor to add media keys.
  2. Observe host logs when enumeration fails.

Solutions to the homework/exercises

  1. Add the consumer control usage page and an extra report ID.
  2. Compare dmesg output 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)

  1. Parse payload text into actions (TYPE, ENTER, WAIT).
  2. Convert characters into keycodes + modifiers.
  3. Send press report, then release report.
  4. 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

  1. Why must you send a key release report?
  2. What happens if the host uses a different keyboard layout?
  3. Why check a stop flag between actions?

Check-your-understanding answers

  1. Without release, the host thinks the key is held down.
  2. Characters may be wrong or payload fails.
  3. 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

  1. Implement a TYPE + ENTER command sequence.
  2. Add a STOP button that cancels playback.

Solutions to the homework/exercises

  1. Parse line-based commands and enqueue actions with delays.
  2. 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)

  1. Device boots in Safe Mode with warnings.
  2. User long-presses to arm; UI switches to Armed state.
  3. User selects payload and confirms.
  4. Payload executes; user can cancel at any time.
  5. 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

  1. Why should the device boot in Safe Mode?
  2. What is the purpose of a kill switch?
  3. Why include an audit log?

Check-your-understanding answers

  1. To prevent accidental execution on an unintended host.
  2. To stop payloads immediately if something goes wrong.
  3. 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

  1. Design a UI flow with two confirmations and a kill switch.
  2. Implement an audit log entry after each execution.

Solutions to the homework/exercises

  1. Use a state machine with SAFE → ARMED → READY → EXECUTE states.
  2. 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

  1. USB enumeration: stable HID keyboard device using TinyUSB.
  2. Payload engine: parse simple script commands (TYPE, WAIT, ENTER).
  3. Safety: multi-step arming and immediate stop control.
  4. UI: select, edit, and execute payloads with warnings.
  5. 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

  1. Parse script lines into actions.
  2. For each action, send HID report or wait.
  3. 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

  1. USB enumeration and descriptors.
  2. HID report encoding.
  3. Safety state machines.

5.5 Questions to Guide Your Design

  1. What is your payload format and error handling strategy?
  2. How will you ensure safe mode on boot?
  3. 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

  1. Why is USB HID a security risk?
  2. What descriptors are required for HID?
  3. 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

  1. Device does not type when unarmed.
  2. Kill switch stops payload immediately.
  3. 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.
  • 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).
  • P07-bluetooth-hid-keyboardmouse-injector.md
  • P08-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.