The Problem
Operators need a low‑cost, Wi‑Fi‑controlled BadUSB that can be programmed on‑the‑fly without moving SD cards or reinstalling firmware. Existing BadUSB kits require manual flashing after each script change, which slows testing and deployment in red‑team exercises.
What This Does
WiFi Duck turns an ATmega32u4 or ESP8266 board into a wireless HID keyboard. The firmware (see atmega_duck/atmega_duck.ino and esp_duck/esp_duck.ino) runs a small web server (esp_duck/webserver.cpp) that serves the UI in web/. Scripts written in the DuckScript language are stored in the ESP’s SPIFFS (esp_duck/spiffs.cpp) and executed by the parser (esp_duck/duckscript.cpp). The same source tree also contains a desktop‑side converter (webconverter.py) that translates raw scripts into the binary format the device expects.
How It Is Wired
Entry points
atmega_duck/atmega_duck.ino– Arduino sketch for ATmega32u4 boards.esp_duck/esp_duck.ino– Arduino sketch for ESP8266 boards.web/index.html/web/index.js– browser UI loaded from the device.
Core flow (ESP variant, the most feature‑complete)
- Boot –
esp_duck.inocallssetup()→WebServer::begin()(inesp_duck/webserver.cpp). - Web UI –
webserver.cppregisters HTTP handlers that serve files fromweb/(e.g.,index.html,script.js). - Script upload – UI posts a script to
/upload. The handler invokesSpiffs::writeFile()(esp_duck/spiffs.cpp) to persist the script in SPIFFS. - Execution request – UI triggers
/run. Handler callsDuckScript::run()(esp_duck/duckscript.cpp). - Parsing –
DuckScript::run()creates aDuckParser(esp_duck/duckscript.cppincludesduckparser.cppfrom the ATmega side) which tokenises the script and maps tokens to HID reports using tables inusb_hid_keys.h. - Output – Parsed commands are sent to the USB HID stack via
Keyboard::press()calls (implemented inatmega_duck/keyboard.cppfor ATmega and mirrored in ESP code).
Responsibility map
| File | Primary role |
|---|---|
esp_duck/webserver.cpp | HTTP server, routes UI actions |
esp_duck/spiffs.cpp | Persistent storage of scripts |
esp_duck/duckscript.cpp | Script interpreter entry point |
atmega_duck/duckparser.cpp | Tokenisation and command mapping |
atmega_duck/keyboard.cpp | Low‑level HID report generation |
web/*.js & web/*.html | Front‑end UI, JSON API calls |
webconverter.py | Offline conversion of raw scripts to binary payload |
The import graph shows no circular dependencies; each module is a leaf or a single‑direction consumer, so changes in webserver.cpp affect only the HTTP layer, while duckparser.cpp is the widest‑impact module (used by both ATmega and ESP builds).
How To Use It
# 1. Clone the repo
git clone https://github.com/moses-y/WiFiDuck.git
cd WiFiDuck
# 2. Open the appropriate sketch in the Arduino IDE
# – ATmega32u4 board: atmega_duck/atmega_duck.ino
# – ESP8266 board: esp_duck/esp_duck.ino
# Select the correct board type and upload.
# 3. After flashing, the device creates Wi‑Fi AP “wifiduck” (default password “wifiduck”).
# Connect a client PC to that network.
# 4. Open a browser to http://192.168.4.1 .
# The UI loads from web/*.html and web/*.js served by the firmware.
# 5. Write a DuckScript in the “Script” pane, click **Save** → it is stored via SPIFFS.
# Click **Run** to execute the keystrokes on the host computer.
# Optional: Convert a raw script to binary with the supplied tool
python3 webconverter.py input.duck output.bin
Configuration files are hard‑coded in the sketches (esp_duck/config.h and atmega_duck/config.h). Changing SSID/password requires editing esp_duck/settings.cpp and re‑flashing, or using the UI’s Settings page which writes to SPIFFS and updates the runtime config.
Real‑World Use
A penetration tester deploys a DSTIKE ESP8266 board on a target’s USB hub. After a quick flash, they walk to the target site, connect a laptop to the AP, load a credential‑harvesting script via the browser UI, and trigger it when the victim plugs in the hub. The entire workflow takes minutes and leaves no visible storage media on the target.
Code Health & Issues
- HIGH – No test suite – 20 source files, zero test files.
- HIGH – No CI pipeline – No GitHub Actions or other build automation present.
- LOW – Missing convention files – No
.editorconfig,.gitattributes, or formatter config. - HIGH – Deep nesting –
esp_duck/com.cpp,atmega_duck/duckparser.cpp,esp_duck/cli.cppeach have indentation depth 8, making logic hard to follow. - HIGH – Duplicated code – Repeated 6‑line blocks across 25 files (e.g.,
Adafruit_DotStar.*,NeoPixel.*,com.*). - MEDIUM – Resource safety –
webconverter.pyopens files without a context manager, risking leaks on exception. - MEDIUM – High branching density –
atmega_duck/Adafruit_DotStar.cpp,atmega_duck/duckparser.cpp,atmega_duck/parser.ccontain >100 branch points in <400 lines.
The Bottom Line
WiFi Duck provides a functional, Wi‑Fi‑controlled BadUSB platform with a ready‑to‑use web UI and clear separation between firmware, storage, and parsing. The codebase suffers from maintainability problems (deep nesting, duplicated logic) and lacks basic engineering scaffolding (tests, CI, formatting rules). It is suitable for security researchers comfortable with Arduino tooling who can tolerate ad‑hoc code quality, but teams looking for a production‑grade, maintainable BadUSB framework will need to invest in refactoring and test infrastructure.