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:

  1. Implement USB HID keyboard descriptors.
  2. Parse a simple script language (DuckyScript-like).
  3. Translate scripts into timed key reports.
  4. Add safety mechanisms to prevent accidental execution.
  5. 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)

  1. Build a report with modifier and keycode.
  2. Send report (press).
  3. Send empty report (release).
  4. 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

  1. Why send an empty report after a key press?
  2. How do you type an uppercase letter?
  3. What is the max number of simultaneous keys in a report?

Check-your-understanding answers

  1. It signals key release to the host.
  2. Send the keycode with the Shift modifier set.
  3. Six non-modifier keys.

Real-world applications

  • USB keyboards, barcode scanners, accessibility devices.

Where you’ll apply it

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

  1. Map ASCII ‘A’ to HID keycode + Shift.
  2. Send Ctrl+Alt+Del sequence via reports.

Solutions to the homework/exercises

  1. Use HID_KEY_A with Shift modifier.
  2. 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)

  1. Read script file line by line.
  2. Tokenize command and args.
  3. Append actions to queue with delays.
  4. 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

  1. Why separate parsing and execution?
  2. How do you handle unknown commands?
  3. Why is per-key delay needed?

Check-your-understanding answers

  1. It simplifies scheduling and error detection.
  2. Reject with a clear error message.
  3. USB and OS event processing need time between reports.

Real-world applications

  • Macro keyboards, assistive automation, test harnesses.

Where you’ll apply it

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

  1. Add a REPEAT command to run the previous line N times.
  2. Implement comments starting with “#”.

Solutions to the homework/exercises

  1. Store last parsed action and duplicate N times.
  2. 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)

  1. Boot into safe idle state.
  2. Require a physical arm action.
  3. Blink LED to indicate armed.
  4. 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

  1. Why require an arming action?
  2. What is the purpose of dry-run mode?
  3. How would you prevent execution on boot?

Check-your-understanding answers

  1. To prevent accidental injection and require intent.
  2. To verify scripts without sending keystrokes.
  3. 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

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

  1. Implement a 2-second hold arming mechanism.
  2. Add a dry-run mode that prints keycodes.

Solutions to the homework/exercises

  1. Start a timer on press and arm only if held.
  2. 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

  1. USB HID enumeration: device appears as a keyboard.
  2. Script parser: support STRING, DELAY, ENTER, and REPEAT.
  3. Scheduler: execute key events with precise delays.
  4. Safety: require arming button or switch.
  5. 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 FOO and 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

  1. Parse script into action list.
  2. Wait for arming condition.
  3. 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

  1. HID report format
  2. Script parsing and timing
  3. Safety/arming mechanisms

5.5 Questions to Guide Your Design

  1. What is your minimum per-key delay?
  2. How will you store or load scripts?
  3. 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

  1. How do HID reports represent key presses?
  2. Why is an arming switch important?
  3. 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

  1. String typing: “hello” appears correctly.
  2. Delay: DELAY 500 pauses for 0.5 s.
  3. 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.
  • 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

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 0 success, 2 parse error, 5 unknown 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.