Project 8: USB Rubber Ducky (HID Keyboard)
Build a USB HID keyboard emulator that executes scripted keystrokes for authorized security testing.
Quick Reference
| Attribute | Value |
|---|---|
| Difficulty | Level 2: Intermediate |
| Time Estimate | 1-2 weeks |
| Main Programming Language | C (Pico SDK + TinyUSB) |
| Alternative Programming Languages | MicroPython |
| Coolness Level | Level 3: Security Tool |
| Business Potential | 3. The “Security Testing” Tier |
| Prerequisites | USB basics, GPIO, text parsing |
| Key Topics | USB HID, script parsing, safety/arming, timing control |
1. Learning Objectives
By completing this project, you will:
- Implement USB HID keyboard descriptors.
- Parse a simple script language (DuckyScript-like).
- Translate scripts into timed key reports.
- Add safety mechanisms to prevent accidental execution.
- Validate behavior across operating systems.
2. All Theory Needed (Per-Concept Breakdown)
2.1 USB HID Keyboard Reports
Fundamentals
USB HID keyboards send periodic reports describing which keys are pressed. The report includes modifier keys (Ctrl, Shift) and up to six keycodes. Hosts interpret these as standard keyboard events without custom drivers.
Deep Dive into the concept
HID is a class specification built to abstract human input devices. A keyboard sends an 8-byte report: 1 byte modifiers, 1 byte reserved, and 6 bytes keycodes. A key press is represented by including its keycode in the report; a release is represented by sending a report with that keycode removed. For scripted typing, you must send a press report followed by a release report with a small delay. Special keys (Enter, Tab) have defined HID keycodes. You also need to handle key combinations (e.g., Ctrl+R) by setting modifier bits and keycodes in the same report. Timing matters: too fast and the OS may drop keys; too slow and the script feels sluggish. This project requires a clear mapping table between ASCII characters and HID keycodes, including Shift modifiers for uppercase and symbols.
How this fits on projects
USB HID design drives §3.2 requirements and §5.10 Phase 2.
Definitions & key terms
- HID report -> fixed-size data packet with key states
- Modifier -> Ctrl/Shift/Alt/GUI bits
- Keycode -> USB HID usage for a key
Mental model diagram (ASCII)
Report: [mod][0][key1][key2][key3][key4][key5][key6]
How it works (step-by-step)
- Build a report with modifier and keycode.
- Send report (press).
- Send empty report (release).
- Repeat for each key.
Minimal concrete example
uint8_t report[8] = { MOD_LCTRL, 0, HID_KEY_R, 0,0,0,0,0 };
Common misconceptions
- “USB HID is just ASCII” -> HID uses keycodes, not characters.
- “Hold key forever” -> you must send releases to avoid stuck keys.
Check-your-understanding questions
- Why send an empty report after a key press?
- How do you type an uppercase letter?
- What is the max number of simultaneous keys in a report?
Check-your-understanding answers
- It signals key release to the host.
- Send the keycode with the Shift modifier set.
- Six non-modifier keys.
Real-world applications
- USB keyboards, barcode scanners, accessibility devices.
Where you’ll apply it
- In this project: §3.2, §5.10 Phase 2.
- Also used in: P03-midi-controller-music-from-motion.md for USB descriptors.
References
- USB HID specification
- TinyUSB HID examples
Key insights
HID reports are simple but must be timed correctly to be reliable.
Summary
Keyboard emulation is about correct keycode mapping and clean press/release sequences.
Homework/Exercises to practice the concept
- Map ASCII ‘A’ to HID keycode + Shift.
- Send Ctrl+Alt+Del sequence via reports.
Solutions to the homework/exercises
- Use HID_KEY_A with Shift modifier.
- Set Ctrl+Alt modifiers and send Delete keycode.
2.2 Script Parsing and Macro Execution
Fundamentals
A rubber-ducky tool executes scripts like “STRING”, “ENTER”, and “DELAY”. The parser must read lines, interpret commands, and schedule key events with appropriate timing.
Deep Dive into the concept
A simple scripting language can be line-based: each line is a command with optional arguments. For example, STRING hello expands into a sequence of key presses; DELAY 500 pauses for 500 ms. Parsing is straightforward, but you must handle whitespace, comments, and quoted strings. Execution should be event-driven: parse the script into a sequence of actions, each with a timestamp or delay. The runtime then emits HID reports accordingly. For reliability, you should define a fixed delay between key presses (e.g., 5-10 ms) and allow configurable delays for slower hosts. The parser should also validate commands and report errors rather than executing unknown lines. For safety, you may require an “ARM” command before executing any keystrokes.
How this fits on projects
Script parsing defines §3.2 requirements and is tested in §3.7 demo.
Definitions & key terms
- Tokenizer -> splits a line into command and arguments
- Action queue -> list of timed events to execute
- Macro -> sequence of commands
Mental model diagram (ASCII)
Script -> Parse -> Actions -> Scheduler -> HID reports
How it works (step-by-step)
- Read script file line by line.
- Tokenize command and args.
- Append actions to queue with delays.
- Execute actions, emitting HID reports.
Minimal concrete example
STRING hello
ENTER
DELAY 500
Common misconceptions
- “Parsing is trivial” -> error handling and quoting matter.
- “Delays are optional” -> fast hosts may drop keys without delays.
Check-your-understanding questions
- Why separate parsing and execution?
- How do you handle unknown commands?
- Why is per-key delay needed?
Check-your-understanding answers
- It simplifies scheduling and error detection.
- Reject with a clear error message.
- USB and OS event processing need time between reports.
Real-world applications
- Macro keyboards, assistive automation, test harnesses.
Where you’ll apply it
- In this project: §3.2, §5.10 Phase 2.
- Also used in: P09-servo-robot-arm-motion-control.md for scripting patterns.
References
- DuckyScript reference (conceptual)
Key insights
A robust script parser turns a simple device into a flexible automation tool.
Summary
Parse commands into timed actions and execute them with predictable delays.
Homework/Exercises to practice the concept
- Add a REPEAT command to run the previous line N times.
- Implement comments starting with “#”.
Solutions to the homework/exercises
- Store last parsed action and duplicate N times.
- Ignore lines starting with ‘#’.
2.3 Safety, Arming, and Ethical Use
Fundamentals
A keyboard injection tool is powerful and potentially harmful. It must include safety features to prevent accidental or unauthorized execution. Ethical use requires explicit authorization and controlled environments.
Deep Dive into the concept
Safety mechanisms include an “arming” switch or button, a delay before execution, and a physical indicator (LED) when armed. The device should not run scripts immediately on power-up; require a deliberate action, such as holding a button for 2 seconds. You can also implement a “dry-run” mode that logs intended keystrokes to serial instead of sending them. Logging and versioning of scripts is important for auditability. From an ethical standpoint, you should only test devices you own or have explicit permission to test. This project is about building a tool for authorized security assessments and training, not unauthorized access. Include a disclaimer in documentation and enforce safeguards in firmware.
How this fits on projects
Safety controls are mandatory in §3.2 requirements and §3.7 demos.
Definitions & key terms
- Arming -> explicit action to enable execution
- Dry-run -> simulate without sending HID reports
- Authorized testing -> permission-based security assessment
Mental model diagram (ASCII)
Power on -> Safe idle -> Arm button -> Execute script
How it works (step-by-step)
- Boot into safe idle state.
- Require a physical arm action.
- Blink LED to indicate armed.
- Execute script; disarm on completion.
Minimal concrete example
if (arm_button_held_2s) armed = true;
Common misconceptions
- “Safety is optional” -> safety is required for responsible tools.
- “Dry-run is useless” -> it helps validate scripts without risk.
Check-your-understanding questions
- Why require an arming action?
- What is the purpose of dry-run mode?
- How would you prevent execution on boot?
Check-your-understanding answers
- To prevent accidental injection and require intent.
- To verify scripts without sending keystrokes.
- Default to safe mode until a button is held.
Real-world applications
- Security training labs, automated test rigs, accessibility tools.
Where you’ll apply it
- In this project: §3.2, §3.7, §5.10 Phase 3.
- Also used in: P01-blinky-on-steroids-multi-pattern-led-controller.md for debounce logic.
References
- Responsible disclosure and ethics guidelines (general)
Key insights
Safety features turn a risky tool into a responsible testing device.
Summary
Always require explicit arming and provide safe testing modes.
Homework/Exercises to practice the concept
- Implement a 2-second hold arming mechanism.
- Add a dry-run mode that prints keycodes.
Solutions to the homework/exercises
- Start a timer on press and arm only if held.
- Print HID report bytes to serial instead of sending.
3. Project Specification
3.1 What You Will Build
A USB HID keyboard emulator that reads a script file (from flash or serial), executes keystrokes with delays, and includes an arming safety mechanism.
3.2 Functional Requirements
- USB HID enumeration: device appears as a keyboard.
- Script parser: support STRING, DELAY, ENTER, and REPEAT.
- Scheduler: execute key events with precise delays.
- Safety: require arming button or switch.
- Diagnostics: serial log or dry-run mode.
3.3 Non-Functional Requirements
- Reliability: correct key sequence on Windows, macOS, Linux.
- Safety: no execution without explicit arming.
- Usability: clear error messages for script syntax.
3.4 Example Usage / Output
[ARM] hold button to arm
[ARM] armed
[SCRIPT] running 12 commands
3.5 Data Formats / Schemas / Protocols
- Script format: one command per line
- Command examples: STRING, ENTER, DELAY 500, REPEAT 3
3.6 Edge Cases
- Unknown command -> error and abort.
- Script too long -> stop with warning.
- USB disconnect mid-script -> pause and resume when reconnected.
3.7 Real World Outcome
Authorized test scripts execute reliably only when armed.
3.7.1 How to Run (Copy/Paste)
cmake .. && make -j4
picotool load -f ducky.uf2
# Hold ARM button, then connect to host
3.7.2 Golden Path Demo (Deterministic)
- Script:
STRING hello,ENTER,DELAY 500,STRING world. - Output: “hello” then newline, then “world”.
3.7.3 Failure Demo (Bad Input)
- Scenario: unknown command
FOO. - Expected result: log
[ERROR] unknown command FOOand abort.
3.7.4 If CLI: exact terminal transcript
$ minicom -b 115200 -o -D /dev/ttyACM0
[ARM] armed
[SCRIPT] line 3: DELAY 500
4. Solution Architecture
4.1 High-Level Design
Script -> Parser -> Action Queue -> Scheduler -> HID Reports -> Host
4.2 Key Components
| Component | Responsibility | Key Decisions | |———–|—————-|—————| | USB HID | Keyboard enumeration | TinyUSB HID class | | Parser | Script parsing | Line-based grammar | | Scheduler | Timing of events | Fixed per-key delay | | Safety | Arming logic | Button hold + LED | | Logger | Diagnostics | Serial or dry-run |
4.3 Data Structures (No Full Code)
typedef struct {
uint8_t keycode;
uint8_t modifier;
uint16_t delay_ms;
} action_t;
4.4 Algorithm Overview
Key Algorithm: Parse-Then-Execute
- Parse script into action list.
- Wait for arming condition.
- Execute actions with delays.
Complexity Analysis:
- Time: O(N) actions
- Space: O(N) action list
5. Implementation Guide
5.1 Development Environment Setup
# Pico SDK + TinyUSB
5.2 Project Structure
usb-ducky/
├── firmware/
│ ├── main.c
│ ├── hid.c
│ ├── script.c
│ └── scheduler.c
└── README.md
5.3 The Core Question You’re Answering
“How do you safely turn a microcontroller into a programmable USB keyboard?”
5.4 Concepts You Must Understand First
- HID report format
- Script parsing and timing
- Safety/arming mechanisms
5.5 Questions to Guide Your Design
- What is your minimum per-key delay?
- How will you store or load scripts?
- What safety indicator will you provide?
5.6 Thinking Exercise
Design a script that opens a text editor and types a message safely.
5.7 The Interview Questions They’ll Ask
- How do HID reports represent key presses?
- Why is an arming switch important?
- How do you ensure script timing is reliable?
5.8 Hints in Layers
Hint 1: Start with a fixed key press (e.g., “a”). Hint 2: Add parser for STRING and ENTER. Hint 3: Add delay command. Hint 4: Add arming and dry-run.
5.9 Books That Will Help
| Topic | Book | Chapter | |——-|——|———| | USB basics | “Making Embedded Systems” | Ch. 9 | | Parsing | “Crafting Interpreters” | Ch. 4 | | Secure design | “Security Engineering” | Ch. 1 |
5.10 Implementation Phases
Phase 1: Foundation (2-3 days)
- Enumerate as HID keyboard.
- Send a fixed key sequence. Checkpoint: OS receives keystrokes.
Phase 2: Core Functionality (4-6 days)
- Implement script parsing and scheduling. Checkpoint: script executes with delays.
Phase 3: Safety & UX (2-3 days)
- Add arming button, LED, and dry-run. Checkpoint: no execution without arming.
5.11 Key Implementation Decisions
| Decision | Options | Recommendation | Rationale | |———-|———|—————-|———–| | Script source | Flash vs serial | Flash | Deterministic for demos | | Key timing | Fixed vs variable | Fixed default + optional delay | Simplicity | | Safety | Auto-run vs armed | Armed | Prevent accidents |
6. Testing Strategy
6.1 Test Categories
| Category | Purpose | Examples | |———-|———|———-| | Unit Tests | Parser correctness | Parse STRING and DELAY | | Integration Tests | HID output | Verify keystrokes in OS | | Edge Case Tests | Script errors | Unknown command handling |
6.2 Critical Test Cases
- String typing: “hello” appears correctly.
- Delay: DELAY 500 pauses for 0.5 s.
- Arming: script does not run without arming.
6.3 Test Data
STRING test
ENTER
7. Common Pitfalls & Debugging
7.1 Frequent Mistakes
| Pitfall | Symptom | Solution | |———|———|———-| | Missing release report | Keys stuck | Always send empty report | | Timing too fast | Missing characters | Increase per-key delay | | Unsafe auto-run | Accidental execution | Require arming |
7.2 Debugging Strategies
- Use dry-run mode to log keycodes.
- Test on multiple OSes for timing differences.
7.3 Performance Traps
- Large scripts can exceed RAM; stream execution if needed.
8. Extensions & Challenges
8.1 Beginner Extensions
- Add LED status for script progress.
- Add a simple “STOP” command.
8.2 Intermediate Extensions
- Add support for function keys and media keys.
- Add script storage on SD card.
8.3 Advanced Extensions
- Add encrypted script storage with a passphrase.
- Add a configuration UI over USB CDC.
9. Real-World Connections
9.1 Industry Applications
- Security training and authorized penetration testing.
- Automation in controlled test environments.
9.2 Related Open Source Projects
- DuckyScript tools and parsers
9.3 Interview Relevance
- USB HID, input automation, and safety design are relevant topics.
10. Resources
10.1 Essential Reading
- USB HID specification
- TinyUSB HID docs
10.2 Video Resources
- HID keyboard emulation tutorials
10.3 Tools & Documentation
- HID keycode tables
10.4 Related Projects in This Series
- P03-midi-controller-music-from-motion.md for USB descriptors
- P01-blinky-on-steroids-multi-pattern-led-controller.md for debounce
11. Self-Assessment Checklist
11.1 Understanding
- I can explain HID report structure.
- I can parse a small command language.
- I can justify safety design choices.
11.2 Implementation
- Scripts execute reliably only when armed.
- Key sequences match expected output.
- Errors are reported clearly.
11.3 Growth
- I can extend the script language.
- I can add new HID usage pages.
12. Submission / Completion Criteria
Minimum Viable Completion:
- HID keyboard enumerates and types a fixed string.
Full Completion:
- Script parsing with delays and safe arming.
Excellence (Going Above & Beyond):
- Encrypted scripts and configuration UI.
13. Additional Content Rules
13.1 Determinism
Use a fixed script file and fixed per-key delay for demo. Log line numbers as executed.
13.2 Outcome Completeness
- Success demo: §3.7.2
- Failure demo: §3.7.3
- CLI exit codes: host script compiler returns
0success,2parse error,5unknown command.
13.3 Cross-Linking
Concept links in §2.x and related projects in §10.4.
13.4 No Placeholder Text
All sections are fully specified for this project.