Firmware for the CJMCU-32 (ATMEGA32U4) board that turns it into a programmable USB keyboard emulator (HID). Once plugged into a computer, the device replays keystroke sequences stored on a microSD card using a syntax similar to DuckyScript (Rubber Ducky).
The project is intended for authorized security testing, red-team exercises, education and research — see the Legal disclaimer.
- BadUSB — ATMEGA32U4 keyboard emulator
| Component | Description |
|---|---|
| Microcontroller | CJMCU-32 / ATMEGA32U4 (native USB HID) |
| microSD reader | SPI module |
chipSelect |
pin 4 (SPI CS of the SD reader) |
| Status LED | pin 8 (led2) — payload/error signalling |
| Built-in LED | LED_BUILTIN |
The SD module is connected over SPI (MISO/MOSI/SCK) plus the CS line on pin 4.
If needed, change const int chipSelect in badusb.ino.
Standard libraries from the Arduino AVR Core:
Keyboard.hSPI.hSD.h
Note: the
SDlibrary supports 8.3 file names and is case-insensitive. Keep file names short (e.g.PAYLOAD.txt).
Important: Arduino requires the main sketch file name to match its folder name. The main sketch is
BadUSB.ino, so keep it inside a folder namedBadUSB/(this is how the repository is laid out). If you rename one, rename the other to match, otherwise the IDE /arduino-cliwill refuse to open the sketch.
- Install Arduino AVR Boards.
- Select an ATMEGA32U4-compatible board (e.g. Arduino Leonardo / Micro).
- Open
BadUSB.ino, compile and upload.
# Point at the sketch folder (recommended) or at BadUSB/BadUSB.ino
arduino-cli compile --fqbn arduino:avr:leonardo BadUSB
arduino-cli upload --fqbn arduino:avr:leonardo -p COM3 BadUSBVerified with arduino:avr core 1.8.6 (arduino-cli 1.5.1): compiles clean,
~77% flash and ~72% RAM on the ATMEGA32U4.
If, right after flashing, the board immediately starts sending keystrokes and makes reprogramming difficult, set
MODE.cfgtom(management mode) before inserting the card, or upload while triggering the bootloader (double reset).
| File | Description |
|---|---|
BadUSB.ino |
Main sketch: setup/loop, management mode, payload parser & dispatch. |
keys.h |
HID key-code constants (#ifndef-guarded against Keyboard.h). |
keymap.h |
Public interface of the character-map module. |
keymap.cpp |
LANG.cfg loader and byte translation (loadKeymap, convertLangChar). |
SD/ |
Ready-to-copy SD card contents (config files and payloads). |
SD/examples/ |
Extra per-OS example payloads (WIN.txt, LINUX.txt, MAC.txt). |
tools/ |
payload_lint.py — offline payload validator (see below). |
All four source files live in the sketch folder, so Arduino IDE / arduino-cli
compile them together automatically — no extra configuration needed.
The root of the microSD card (FAT/FAT32) should contain:
| File | Required | Description |
|---|---|---|
MODE.cfg |
yes | Operating mode: c, a or m (see below). |
EXEC.cfg |
yes | Name of the payload file to run (e.g. PAYLOAD.txt). |
LANG.cfg |
no | Optional character map for non-US layouts. |
| payload | yes | Text file with the script (name given in EXEC.cfg). |
A ready-to-copy set is provided in the SD/ directory — copy its
contents to the root of the card. The default PAYLOAD.txt is a parser
feature showcase.
Extra per-OS payloads live in SD/examples/: WIN.txt
(Windows), LINUX.txt (Linux) and MAC.txt (macOS). To run one, copy it into
the card root as PAYLOAD.txt (replacing the default), so EXEC.cfg keeps
pointing at the payload next to it.
MODE.cfg contains a single character:
| Value | Mode | Behavior |
|---|---|---|
c |
continuous | Replays the payload on every connection. |
a |
auto-disarm | Replays the payload once, then automatically switches MODE.cfg to m. |
m |
management | Does not replay the payload — starts a configuration menu over serial. |
Mode a is convenient for safe testing: the device "disarms" itself after the
first run and waits for configuration on the next connection.
A payload is a text file, one command per line. All common line endings are
supported — LF (\n), CRLF (\r\n) and legacy CR (\r). Empty
lines are ignored.
| Command | Description |
|---|---|
DELAY <ms> |
Pause in milliseconds. |
DEFAULT_DELAY <ms> |
Fixed pause before every subsequent command (alias DEFAULTDELAY). |
REM <text> |
Comment — the line is ignored. |
| Command | Description |
|---|---|
STRING <text> |
Types the given string (no trailing Enter). |
STRINGLN <text> |
Types the given string, then presses Enter. |
Modifiers can be chained and are followed by an optional final key, which may be a single character or a named key (see the special-key list below).
| Modifier | Sends |
|---|---|
CTRL / CONTROL |
Left Ctrl |
ALT |
Left Alt |
SHIFT |
Left Shift |
GUI / WINDOWS |
Left Super (Windows key) |
CTRLALT |
Left Ctrl + Left Alt (legacy shortcut for CTRL ALT) |
Examples:
GUI r # Win + R
CTRL c # copy
ALT F4 # close window (named key as the final key)
CTRLALT t # open a terminal (Linux)
CTRL ALT DELETE # chained modifiers + named key
GUI # tap the Windows key alone (no argument)
ENTER, MENU/APP, SPACE, TAB, ESC/ESCAPE, CAPSLOCK, DELETE,
INSERT, HOME, END, PAGEUP, PAGEDOWN, NUMLOCK, SCROLLLOCK,
PRINTSCREEN, BREAK/PAUSE, UP/UPARROW, DOWN/DOWNARROW,
LEFT/LEFTARROW, RIGHT/RIGHTARROW, F1–F12.
DELAY 2000
GUI r
DELAY 500
STRING notepad
ENTER
DELAY 800
STRING Hello from BadUSB!
The maximum command-name length is 16 characters. Unrecognized commands are skipped and turn on the error LED in management mode.
tools/payload_lint.py checks a payload on your PC before you copy it to
the SD card. It reproduces the firmware's token grammar (dispatchCommand,
delivery and runCombo in BadUSB.ino), so a payload that
lints clean will not trip the device's unknown-command error flag.
python tools/payload_lint.py SD/PAYLOAD.txt
# check several at once:
python tools/payload_lint.py SD/PAYLOAD.txt SD/examples/*.txtIt reports three levels and exits non-zero on any ERROR (handy in CI):
- ERROR — the firmware would reject the line (unknown command, a bare single character used as a command, an invalid chord key).
- WARN — the line runs but likely not as intended (command name over 16
chars gets truncated, non-integer
DELAYargument, tokens ignored after a chord's final key, non-ASCIISTRINGbytes). - note — harmless, informational.
Needs only Python 3 (no external packages).
When MODE.cfg = m, after insertion the device waits for a serial port to be
opened (9600 baud). Once connected, the serial monitor shows:
- a list of available files (payloads) on the card,
- the available modes,
- the current mode and payload,
- a prompt to enter a new mode (
c/a/m), saved toMODE.cfg, - a prompt to enter the name of a new payload, saved to
EXEC.cfg.
This lets you reconfigure the device without removing the SD card.
By default the firmware sends characters as if the host used the US layout.
For other layouts you can remap individual bytes in the LANG.cfg file.
Format: one entry per line, three decimal numbers separated by commas:
in,modifier,out
in— input byte code (0–255), as in the payload (e.g. fromSTRING),modifier— key pressed together without(0= none; e.g.134= Right Alt / AltGr),out— byte/key code actually sent to the host.
Modifier codes:
| Code | Key | Code | Key |
|---|---|---|---|
| 128 | KEY_LEFT_CTRL | 132 | KEY_RIGHT_CTRL |
| 129 | KEY_LEFT_SHIFT | 133 | KEY_RIGHT_SHIFT |
| 130 | KEY_LEFT_ALT | 134 | KEY_RIGHT_ALT (AltGr) |
| 131 | KEY_LEFT_GUI | 135 | KEY_RIGHT_GUI |
Rules:
- empty lines and lines starting with
#are skipped, - up to 64 active entries,
- missing file or no entries ⇒ passthrough mode (no remapping).
Limitation: mapping only works on single bytes (0–255). Multi-byte UTF-8 characters (e.g. Polish
ą=0xC4 0x85) will not work without code changes — keep payloads in ASCII.
See the example: SD/LANG.cfg.
- After a payload finishes, the LED on pin 8 lights up for ~0.5 s.
- In management mode the LED stays on while waiting for the serial port; blinking (200 ms) indicates a parsing error detected in the previous payload.
| Symptom | Possible cause / fix |
|---|---|
Card failed, or not present |
Wrong CS line, missing/damaged card, wrong format (use FAT32). |
No payload configured |
Empty/missing EXEC.cfg. |
| Payload misbehaves | Check the host keyboard layout and LANG.cfg; keep payloads ASCII. |
| Cannot reflash the firmware | Set mode m or use the bootloader (double reset). |
| Wrong character instead of expected one | Keyboard layout mismatch — configure LANG.cfg. |
This is a dual-use tool provided solely for lawful purposes: authorized penetration testing, red-team exercises, security research and education.
Use it only on hardware you own or with the explicit written permission of the owner. Unauthorized access to computer systems and interception of data are illegal in most jurisdictions (in Poland, among others, Art. 267 and 269b of the Penal Code). The author accepts no liability for any damage or misuse of this software. All responsibility rests entirely with the user.
Released under the MIT license — see the LICENSE file.
© 2026 Mateusz "Maku" Mączewski
