DuckyScript 101: The Payload Language Behind Bad-BT
DuckyScript is a simple scripting language for HID keyboard automation. Write a sequence of commands, load them onto a device that emulates a keyboard, and those keystrokes execute on the target computer just as if a person typed them - but in milliseconds rather than seconds. The language was created by Hak5 for the USB Rubber Ducky and has since been adopted by multiple HID injection tools, including the BLEShark Nano's Bad-BT feature.
You don't need a programming background to write DuckyScript payloads. The syntax is intentionally simple - it's closer to a recipe list than code. But understanding what's happening at each step, and knowing the practical limits of HID injection, is what separates a useful payload from one that fails silently on the target.
Table of Contents
History: Hak5 and the USB Rubber Ducky
Hak5 launched the USB Rubber Ducky in 2010. It looked like a USB flash drive but acted as a USB keyboard. Plug it into a computer, and it immediately started executing keystrokes from a payload stored on an embedded SD card. No driver needed, no permission prompt - the OS sees a keyboard and trusts it immediately.
The scripting language they created for it was DuckyScript. The first version was deliberately minimal: STRING (type this text), DELAY (wait this many milliseconds), and a handful of key names. That minimal set was enough to write practical payloads because most automation tasks are just "open a program, type some commands, press enter."
The USB Rubber Ducky went through several hardware revisions. DuckyScript 3.0 added more sophisticated control flow - variables, conditionals, loops, extensions for platform-specific behavior. The community has extended the language further with platform-specific constructs and payload libraries.
The BLEShark Nano uses a DuckyScript-compatible syntax for its Bad-BT wireless HID injection feature. The wireless delivery via Bluetooth is what Bad-BT adds - you don't need physical access to the target machine's USB ports. But the payload language and the fundamentals of what gets executed are similar to what Hak5 pioneered.
How DuckyScript Works at the HID Level
When a DuckyScript payload runs on an HID injection device, it translates each command into USB or Bluetooth HID reports. An HID keyboard report is an 8-byte structure: one byte for modifier keys (Ctrl, Alt, Shift, Win/Cmd, etc.) and six bytes for simultaneous key codes. The OS receives these reports and processes them exactly as if a real keyboard sent them.
sequenceDiagram
participant U as User/Attacker
participant B as BLEShark Nano
participant T as Target Device
participant OS as Target OS
U->>B: Write DuckyScript payload
U->>B: Initiate Bad-BT
B->>T: BLE HID pairing request
T->>B: Pairing accepted
Note over B,T: BLE HID connection established
B->>T: HID keyboard report (keystroke)
T->>OS: USB HID interrupt transfer
OS->>OS: Keystroke processed by input stack
OS->>OS: Command executed in active window
B->>T: Next keystroke (with DELAY)
Note over B: Repeats for each line in payload
B->>T: Final keystroke
Note over T,OS: Payload execution complete
DuckyScript execution flow - from payload to target OS keystroke injection via BLE HID
The key insight: there is no authentication on HID input. The OS doesn't verify that the device sending keyboard reports is a legitimate keyboard belonging to the user. Any USB or Bluetooth device that presents itself as a keyboard gets treated as one, with full access to keyboard input. This is by design - keyboards need to work without user interaction, which means they can't require user authorization to function.
STRING command works by iterating over each character in the string, looking up which HID key code produces that character (accounting for the current keyboard layout), and sending the appropriate HID report for each character. This is why keyboard layout matters - a STRING "Hello" will produce "Hello" on a US QWERTY keyboard but different characters on a French AZERTY or German QWERTZ layout.
Special key commands (ENTER, TAB, F1, etc.) send a single HID report with the corresponding key code and release it. Key combination commands like CTRL+ALT+DELETE send a single report with the modifier bits set plus the DEL key code, then release.
Core Commands
These are the commands you'll use in virtually every payload:
graph TD
subgraph "Payload Structure"
START[Payload Start] --> REM[REM - Comments]
REM --> DELAY1[DELAY - Initial Wait]
end
subgraph "Input Commands"
DELAY1 --> STRING[STRING - Type Text]
DELAY1 --> KEYS[Individual Keys]
STRING --> DELAY2[DELAY - Between Steps]
KEYS --> DELAY2
end
subgraph "Key Types"
KEYS --> MOD[Modifiers: GUI, ALT, CTRL, SHIFT]
KEYS --> SPECIAL[Special: ENTER, TAB, ESC, SPACE]
KEYS --> ARROW[Navigation: UP, DOWN, LEFT, RIGHT]
KEYS --> FKEYS[Function: F1-F12]
end
subgraph "Combinations"
MOD --> COMBO[GUI r - Open Run Dialog]
MOD --> COMBO2[CTRL ALT t - Open Terminal]
MOD --> COMBO3[ALT F4 - Close Window]
end
DELAY2 --> STRING
DELAY2 --> KEYS
DuckyScript command structure - how payload commands combine into executable sequences
STRING text - Types the specified text. Characters are sent one at a time.
STRING Hello World
ENTER - Sends the Enter key.
ENTER
DELAY milliseconds - Waits before executing the next command.
DELAY 1000
This waits 1 second. Delays are critical - they let the OS and applications catch up between commands.
KEY - Sends a single key press (use for special keys).
KEY F1 KEY DELETE KEY TAB
MOD key - Holds a modifier key while pressing another key.
MOD WIN MOD CTRL-ALT-DELETE
REM text - A comment. Ignored during execution.
REM This payload opens Notepad
DEFAULT_DELAY milliseconds (or DEFAULTDELAY) - Sets a default delay between all commands. Useful if the target machine is slow and you need to slow down the entire payload without adding DELAY after every line.
DEFAULT_DELAY 200
Key Names Reference
Special keys are referenced by name. Common key names used in DuckyScript payloads:
- ENTER, SPACE, TAB, BACKSPACE, DELETE, ESCAPE
- UP, DOWN, LEFT, RIGHT (arrow keys)
- HOME, END, INSERT, PAGE_UP, PAGE_DOWN
- F1 through F12
- CAPS_LOCK, NUM_LOCK, SCROLL_LOCK
- PRINTSCREEN, PAUSE
Modifier keys:
- CTRL (or CONTROL)
- SHIFT
- ALT
- GUI (Windows key on Windows, Command key on Mac)
- WIN (alias for GUI)
BLEShark follows the same key naming conventions as Hak5 DuckyScript for compatibility with existing payload libraries.
Modifier Keys and Combinations
Key combinations are expressed with dashes between modifier and key:
REM Open Run dialog on Windows GUI r REM Close a window ALT F4 REM Select all text CTRL a REM Copy CTRL c REM Paste CTRL v REM Open Task Manager CTRL SHIFT ESCAPE REM Lock screen GUI l
Note: when a modifier is combined with another key, both are pressed simultaneously in a single HID report. The timing is a true simultaneous keypress, not sequential.
Platform differences matter here. GUI key is the Windows key on Windows and the Command key on Mac. GUI+R opens the Run dialog on Windows but does nothing equivalent on Mac. Well-written payloads either target a specific OS or include platform detection logic.
Delays and Timing
Delays are the most common source of payload failure. Every application takes time to open. Every window takes time to focus. If you send keystrokes before the application has opened and focused, those keystrokes go nowhere - or worse, they land in the wrong window.
General guidelines:
- After pressing GUI+R (open Run dialog): wait 500-1000ms for the dialog to appear
- After pressing ENTER to confirm a dialog: wait 500ms for the result to process
- After opening a new application: wait 1500-2000ms for it to fully load
- After a keyboard shortcut that triggers a system prompt: wait 1000ms
Faster machines need shorter delays. Slower machines (underpowered laptops, systems under load) need longer ones. If a payload works on your test machine but fails in the field, increase delays. It's better to have a slightly slower payload that works reliably than a fast payload that fails half the time.
DEFAULT_DELAY is useful for production payloads: set it to 100-200ms as a baseline and add explicit DELAY commands for operations that need more time.
Your First Payload
A simple, safe, and demonstrable payload: open Notepad and type a message. This demonstrates that HID injection worked without doing anything harmful.
REM Open Notepad and type a message REM Platform: Windows DELAY 500 GUI r DELAY 700 STRING notepad ENTER DELAY 1500 STRING HID injection is working. ENTER STRING This message was typed by the BLEShark Nano.
What this does, step by step:
- Wait 500ms (giving any active OS activity time to settle)
- Press Windows+R to open the Run dialog
- Wait 700ms for the Run dialog to appear
- Type "notepad" in the Run dialog
- Press Enter to open Notepad
- Wait 1500ms for Notepad to load and focus
- Type the first line of the message
- Press Enter for a new line
- Type the second line
This payload is useful as a demonstration because the result is immediately visible to anyone watching the screen. There's no ambiguity about whether the injection worked - you can see the text appear character by character in Notepad.
It's also completely benign: Notepad with text in it doesn't harm the system, doesn't exfiltrate anything, and closes with a simple close or discard. This makes it the right payload for demonstrating the attack surface to non-technical stakeholders or for testing that your BLEShark is paired and executing correctly.
BLEShark Differences From Standard DuckyScript
The BLEShark Nano implements a DuckyScript-compatible syntax but there are some differences from Hak5's full DuckyScript 3.0 specification:
The BLEShark payload editor supports core DuckyScript commands: STRING, DELAY, DEFAULT_DELAY, ENTER, KEY combinations, modifier keys, REM comments, and common special keys. Advanced DuckyScript 3.0 features like DEFINE, VAR, IF/ELSE, WHILE loops, and extensions may not be available depending on firmware version.
The wireless delivery introduces a constraint that USB Rubber Ducky doesn't have: Bluetooth pairing. Bad-BT requires the target device to have the BLEShark paired as a Bluetooth keyboard before the payload can execute. This is both a limitation (you need prior physical proximity for pairing) and a characteristic of how Bluetooth HID works - the OS must recognize and accept the pairing. Once paired, subsequent payload execution doesn't require re-pairing unless the pairing is deleted.
When Bad-BT is active, the BLEShark's BLE radio maintains a persistent Bluetooth keyboard connection. This means the device fully disconnects from any Shiver mesh connection while Bad-BT is running - the ESP32-C3 has a single 2.4GHz radio, so BLE keyboard mode and ESP-NOW mesh mode are mutually exclusive.
On-Device DuckyScript Editor
The BLEShark Nano includes an on-device DuckyScript editor, accessible through the device menu. You can write and edit payloads directly on the device without needing a computer.
For more convenient editing, use the file portal. When the BLEShark is in WiFi AP mode, connect to its network and navigate to the file portal in your browser. You can upload .txt files containing DuckyScript payloads, edit them in your browser, and they'll be immediately available on the device.
The file portal workflow is typically more convenient for longer payloads: write the payload in a text editor on your computer, save as .txt, upload via file portal, and select it from the Bad-BT menu. Multiple payloads can be stored and selected by name.
Practical Tips for Reliable Payloads
Test on your own machine first. Write the payload, load it, pair your own device, trigger it, and verify the output. This catches timing issues, typos in STRING commands, and key name errors before you're in a demonstration environment.
Use generous delays. An extra 500ms delay costs half a second of execution time and prevents hard-to-debug failures. Optimize for reliability, not speed.
Be explicit about keyboard layout. If you're demonstrating on a non-US keyboard, STRING commands will produce different characters. Know your target's keyboard layout and either test with it or use key codes instead of STRING for non-alphanumeric characters.
Keep payloads focused. A payload that does one specific thing is easier to debug and more reliable than one that tries to do five things in sequence. Start simple, test thoroughly, build complexity incrementally.
Add a clear REM header. Comment every payload with what it does, what OS it targets, and what it requires. When you come back to a payload file six months later, you'll thank yourself.
DuckyScript payloads should only be used on systems you own or have explicit written authorization to test. Unauthorized HID injection is a crime in most jurisdictions.