From 975fab423425f4400005830fd733233cff1122a5 Mon Sep 17 00:00:00 2001 From: sergarsilla Date: Fri, 21 Aug 2026 21:14:33 +0200 Subject: [PATCH] Add DuckyScript DEFINE constant support to BadUSB parser --- sd_files/BadUSB and BlueDucky/Ducky-br.html | 1 + sd_files/BadUSB and BlueDucky/Ducky-en.html | 1 + .../DuckyScript_DEFINE_demo.txt | 13 +++++ src/modules/badusb_ble/ducky_typer.cpp | 49 +++++++++++++++++++ 4 files changed, 64 insertions(+) create mode 100644 sd_files/BadUSB and BlueDucky/DuckyScript_DEFINE_demo.txt diff --git a/sd_files/BadUSB and BlueDucky/Ducky-br.html b/sd_files/BadUSB and BlueDucky/Ducky-br.html index 1a603e7a24..93c1a11fe3 100644 --- a/sd_files/BadUSB and BlueDucky/Ducky-br.html +++ b/sd_files/BadUSB and BlueDucky/Ducky-br.html @@ -480,6 +480,7 @@
Comandos diversos:
REPEAT é usado para repetir o comando anterior. Ele espera o número de repetições adicionais como parâmetro.
+ DEFINE declara uma constante (ex.: DEFINE #NOME valor) que é substituída onde quer que apareça antes da execução do payload.
ALTCHAR é para exibir teclas ALT+Numpad individuais (no Windows), ou teclas ALT(ALT+SHIFT) (no macOS).
Clique
AQUI para uma lista completa de códigos disponíveis para caracteres ALT. diff --git a/sd_files/BadUSB and BlueDucky/Ducky-en.html b/sd_files/BadUSB and BlueDucky/Ducky-en.html index 87ce9b0a26..17bb4f6516 100644 --- a/sd_files/BadUSB and BlueDucky/Ducky-en.html +++ b/sd_files/BadUSB and BlueDucky/Ducky-en.html @@ -480,6 +480,7 @@
Misc. Commands:
REPEAT is used to repeat the previous command. It expects the number of additional repeats as parameter.
+ DEFINE declares a constant (e.g. DEFINE #NAME value) that is substituted wherever it appears before the payload runs.
ALTCHAR is to display single ALT+Numpad Keys (on Windows), or ALT(ALT+SHIFT) Keys (on macOS).
Click
HERE for a complete list of available codes for ALT characters. diff --git a/sd_files/BadUSB and BlueDucky/DuckyScript_DEFINE_demo.txt b/sd_files/BadUSB and BlueDucky/DuckyScript_DEFINE_demo.txt new file mode 100644 index 0000000000..d897edc595 --- /dev/null +++ b/sd_files/BadUSB and BlueDucky/DuckyScript_DEFINE_demo.txt @@ -0,0 +1,13 @@ +REM DuckyScript 3.0 DEFINE demo +REM Constants are declared with DEFINE and substituted before the payload runs. +REM Undefined names and whole tokens are left untouched (#NAME never matches #NAMES). +DEFINE #EDITOR notepad.exe +DEFINE #NAME Bruce +DEFINE #PAUSE 500 + +GUI r +DELAY #PAUSE +STRINGLN #EDITOR +DELAY 1000 +STRINGLN Hello, my name is #NAME +STRINGLN DEFINE keeps payloads readable and easy to retarget diff --git a/src/modules/badusb_ble/ducky_typer.cpp b/src/modules/badusb_ble/ducky_typer.cpp index c1a2f5ed73..f0f61cf822 100644 --- a/src/modules/badusb_ble/ducky_typer.cpp +++ b/src/modules/badusb_ble/ducky_typer.cpp @@ -8,6 +8,7 @@ #include "esp_mac.h" #include "modules/ble/ble_common.h" #include +#include #if defined(USB_as_HID) #include "tusb.h" #endif @@ -575,6 +576,49 @@ DuckyCombination *findDuckyCombination(const char *cmd) { return nullptr; } +// ============================================================================ +// DUCKYSCRIPT 3.0 DEFINE - Constant substitution +// ============================================================================ + +// '#' is a token char because DEFINE names are conventionally '#'-prefixed. +static bool isDuckyDefineTokenChar(char c) { return isAlphaNumeric(c) || c == '_' || c == '#'; } + +static bool parseDuckyDefine(const String &line, std::map &defines) { + if (!line.startsWith("DEFINE ")) return false; + + int nameStart = 7; // strlen("DEFINE ") + while (nameStart < (int)line.length() && line.charAt(nameStart) == ' ') nameStart++; + + int nameEnd = line.indexOf(' ', nameStart); + String name = (nameEnd < 0) ? line.substring(nameStart) : line.substring(nameStart, nameEnd); + if (name.length() == 0) return true; + + defines[name] = (nameEnd < 0) ? String("") : line.substring(nameEnd + 1); + return true; +} + +// Replaces whole-token matches only, so a name never matches inside a larger token. +static void applyDuckyDefines(String &line, const std::map &defines) { + for (const auto &entry : defines) { + const String &name = entry.first; + const String &value = entry.second; + int from = 0; + while (true) { + int pos = line.indexOf(name, from); + if (pos < 0) break; + int after = pos + name.length(); + bool leftOk = (pos == 0) || !isDuckyDefineTokenChar(line.charAt(pos - 1)); + bool rightOk = (after >= (int)line.length()) || !isDuckyDefineTokenChar(line.charAt(after)); + if (leftOk && rightOk) { + line = line.substring(0, pos) + value + line.substring(after); + from = pos + value.length(); + } else { + from = pos + 1; + } + } + } +} + // ============================================================================ // START KEYBOARD - Creates fresh BLE instance with new stack // ============================================================================ @@ -814,6 +858,7 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { char Cmd[25]; String Argument = ""; String RepeatTmp = ""; + std::map duckyDefines; static int nextStringDelay = -1; static int defaultStringDelay = bruceConfig.badUSBBLEKeyDelay; @@ -856,6 +901,10 @@ void key_input(FS fs, const String &bad_script, HIDInterface *_hid) { if (lineContent.length() == 0) continue; + // DEFINE stores a constant; every other line has its constants expanded first. + if (parseDuckyDefine(lineContent, duckyDefines)) continue; + if (!duckyDefines.empty()) applyDuckyDefines(lineContent, duckyDefines); + int spaceIndex = lineContent.indexOf(' '); if (spaceIndex > 0 && lineContent.substring(0, spaceIndex) == "REPEAT") {