19 Commits
v2.0 ... v3.2

Author SHA1 Message Date
Dave
ca88c6c159 Fix which issue templates are available (#329)
Co-authored-by: Dave <dbisu>
2025-03-14 22:16:04 -05:00
Tzur Soffer
ab753989d5 add internal variables (#321)
* add internal variables

* remove extra spacing
2025-02-01 20:28:15 -06:00
Tzur Soffer
522e640c26 While loops and conditional statements (#316)
* added math to variables as well as the ability to use them in DELAY and PRINT/PRINTLN

* add comparisons

* fix typo

* Added While loops and Conditional statements

* If and While loops working
2025-01-26 17:26:45 -06:00
Tzur Soffer
e61d232b46 Variable MATH (#304)
* added math to variables as well as the ability to use them in DELAY and PRINT/PRINTLN

* add comparisons

* fix typo
2025-01-23 19:58:59 -06:00
Dave
6f9cbc3d80 Change link to new PicoDuckyBuilder script (#315)
Co-authored-by: Dave <dbisu>
2025-01-16 19:09:27 -06:00
Dave
fda4617e33 Fix build script to find the correct keyboard_ lines (#311)
Co-authored-by: Dave <dbisu>
2025-01-15 21:01:30 -06:00
Tzur Soffer
90a76bcada remove power key due to keyboard layout issues (#310) 2025-01-15 20:56:12 -06:00
Tzur Soffer
8b9822151f Added duckyScript 3.0 functionality (#300)
* Added HOLD and RELEASE

* Update removed unnecessary heldKeys var

* Fixed HOLD being released after key press

* added REM_BLOCK, STRING Block, and STRINGLN Block

* LED + INJECT_MOD

* add define

* added all randoms

* added stop, restart, reset

* bugfixes and cleanup

* added missing keys
2025-01-03 14:40:40 -06:00
Dave
2dea346723 Fix bug importing keyboard layouts (#302) 2024-12-29 13:48:06 -06:00
Dave
8b11882c52 Pico2 support (#299)
* Add support for Pico2/2W boards

* Update documentation to include Pico2/2W

* update language support
2024-12-21 12:00:23 -06:00
Dave
8bcd2aa456 Add bundle script 2024-12-20 23:41:27 -06:00
Tony
f1636c3e45 Add functions, while loops, and vars support (#264)
* Add functions, while loops, and vars support

* Add back REPEAT

* Add support for nested WHILE loops in function

---------

Co-authored-by: Tony Le <tonyle@coxautoinc.com>
2024-09-21 12:18:16 -05:00
Dave
5dd0783886 Add debugging instructions 2024-03-12 19:54:10 -05:00
William
3caf2ceb98 added new wait_for_button_press functionality (#234) 2024-02-13 20:16:43 -06:00
Dave
70eb2cd8b0 Add link to defcon31-ducky project 2024-01-16 19:18:05 -06:00
Piotr Ginał
0113b0e004 Fix typo (#197) 2023-09-17 15:59:29 -05:00
Dave
74ef11770b Clarify getting latest release 2023-06-24 13:07:19 -05:00
Dave
a7992c2fb3 Update installation instructions 2023-06-24 12:58:58 -05:00
Dave
49e81125c1 Updated issue template 2023-06-24 10:01:39 -05:00
11 changed files with 622 additions and 63 deletions

1
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1 @@
blank_issues_enabled: false

View File

@@ -19,5 +19,11 @@ A clear and concise description of what you expected to happen.
**Screenshots** **Screenshots**
If applicable, add screenshots to help explain your problem. If applicable, add screenshots to help explain your problem.
**Debug info**
If possible, include debug serial data.
On Windows, use PuTTY, connect to the debug serial port (commonly COM3, but could vary)
On Linux, use minicom (minicom -b 115200 -o -D /dev/ttyACM0, where ttyACM0 corresponds to your device)
**Additional context** **Additional context**
Add any other context about the problem here. Add any other context about the problem here.

6
DEBUG.md Normal file
View File

@@ -0,0 +1,6 @@
# Instructions on how to collect debug logs
* On Windows, use [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) to connect to the debug serial port (commonly COM3, but could vary on your machine).
* On Linux, use minicom (minicom -b 115200 -o -D /dev/ttyACM0, where ttyACM0 corresponds to your device).
* On Ubuntu: `sudo apt install minicom`

View File

@@ -16,20 +16,84 @@
<br /> <br />
## Install ## Quick Start Guide
Install and have your USB Rubber Ducky working in less than 5 minutes.
1. Download the latest release from the [Releases](https://github.com/dbisu/pico-ducky/releases) page.
2. Plug the device into a USB port while holding the boot button. It will show up as a removable media device named RPI-RP2.
3. Install CircutlPython on the Pico or Pico W
If using a Pico board:
Copy the adafruit-circuitpython-raspberry_pi_pico-en_US-9.2.1.uf2 file to the root of the Pico (RPI-RP2). The device will reboot and after a second or so, it will reconnect as CIRCUITPY.
If using a Pico W board:
Copy the adafruit-circuitpython-raspberry_pi_pico_w-en_US-9.2.1.uf2 file to the root of the Pico (RPI-RP2). The device will reboot and after a second or so, it will reconnect as CIRCUITPY.
If using a Pico 2 board:
Copy the adafruit-circuitpython-raspberry_pi_pico2-en_US-9.2.1.uf2 file to the root of the Pico (RPI-RP2). The device will reboot and after a second or so, it will reconnect as CIRCUITPY.
If using a Pico 2W board:
Copy the adafruit-circuitpython-raspberry_pi_pico2_w-en_US-9.2.1.uf2 file to the root of the Pico (RPI-RP2). The device will reboot and after a second or so, it will reconnect as CIRCUITPY.
4. Copy the lib folder to the root of the CIRCUITPY
5. Copy *.py to the root of the CIRCUITPY
6. Follow the instructions in README.md to enter setup mode
7. Copy your payload as payload.dd to the root of the CIRCUITPY
8. Unplug the device from the USB port and remove the setup jumper.
Enjoy your Pico-Ducky.
## Setup mode
To edit the payload, enter setup mode by connecting the pin 1 (`GP0`) to pin 3 (`GND`), this will stop the pico-ducky from injecting the payload in your own machine.
The easiest way to do so is by using a jumper wire between those pins as seen bellow.
![Setup mode with a jumper](images/setup-mode.png)
## USB enable/disable mode
If you need the pico-ducky to not show up as a USB mass storage device for stealth, follow these instructions.
- Enter setup mode.
- Copy your payload script to the pico-ducky.
- Disconnect the pico from your host PC.
- Connect a jumper wire between pin 18 (`GND`) and pin 20 (`GPIO15`).
This will prevent the pico-ducky from showing up as a USB drive when plugged into the target computer.
- Remove the jumper and reconnect to your PC to reprogram.
Pico: The default mode is USB mass storage enabled.
Pico W: The default mode is USB mass storage **disabled**
![USB enable/disable mode](images/usb-boot-mode.png)
-----
# Full Install Instructions
Install and have your USB Rubber Ducky working in less than 5 minutes. Install and have your USB Rubber Ducky working in less than 5 minutes.
1. Clone the repo to get a local copy of the files. `git clone https://github.com/dbisu/pico-ducky.git` 1. Clone the repo to get a local copy of the files. `git clone https://github.com/dbisu/pico-ducky.git`
2. Download [CircuitPython for the Raspberry Pi Pico](https://circuitpython.org/board/raspberry_pi_pico/). *Updated to 8.0.0 2. Download [CircuitPython for the Raspberry Pi Pico](https://circuitpython.org/board/raspberry_pi_pico/). *Updated to 9.2.1
Download [CircuitPython for the Raspberry Pi Pico W](https://circuitpython.org/board/raspberry_pi_pico_w/). *Updated to 8.0.0 Download [CircuitPython for the Raspberry Pi Pico W](https://circuitpython.org/board/raspberry_pi_pico_w/). *Updated to 9.2.1
Download [CircuitPython for the Raspberry Pi Pico 2](https://circuitpython.org/board/raspberry_pi_pico2/). *Updated to 9.2.1
Download [CircuitPython for the Raspberry Pi Pico 2W](https://circuitpython.org/board/raspberry_pi_pico2_w/). *Updated to 9.2.1
3. Plug the device into a USB port while holding the boot button. It will show up as a removable media device named `RPI-RP2`. 3. Plug the device into a USB port while holding the boot button. It will show up as a removable media device named `RPI-RP2`.
4. Copy the downloaded `.uf2` file to the root of the Pico (`RPI-RP2`). The device will reboot and after a second or so, it will reconnect as `CIRCUITPY`. 4. Copy the downloaded `.uf2` file to the root of the Pico (`RPI-RP2`). The device will reboot and after a second or so, it will reconnect as `CIRCUITPY`.
5. Download `adafruit-circuitpython-bundle-8.x-mpy-YYYYMMDD.zip` [here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/latest) and extract it outside the device. 5. Download `adafruit-circuitpython-bundle-9.x-mpy-YYYYMMDD.zip` [here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/latest) and extract it outside the device.
6. Navigate to `lib` in the recently extracted folder and copy `adafruit_hid` to the `lib` folder on your Raspberry Pi Pico. 6. Navigate to `lib` in the recently extracted folder and copy `adafruit_hid` to the `lib` folder on your Raspberry Pi Pico.
@@ -46,7 +110,7 @@ Install and have your USB Rubber Ducky working in less than 5 minutes.
12. *For Pico W Only* Create the file `secrets.py` in the root of the Pico W. This contains the AP name and password to be created by the Pico W. 12. *For Pico W Only* Create the file `secrets.py` in the root of the Pico W. This contains the AP name and password to be created by the Pico W.
`secrets = { 'ssid' : "BadAPName", 'password' : "badpassword" }` `secrets = { 'ssid' : "BadAPName", 'password' : "badpassword" }`
13. Find a script [here](https://github.com/hak5/usbrubberducky-payloads) or [create your own one using Ducky Script](https://docs.hak5.org/hak5-usb-rubber-ducky/ducky-script-basics/hello-world) and save it as `payload.dd` in the Pico. Currently, pico-ducky only supports DuckyScript 1.0, not 3.0. 13. Find a script [here](https://github.com/hak5/usbrubberducky-payloads) or [create your own one using Ducky Script](https://docs.hak5.org/hak5-usb-rubber-ducky/ducky-script-basics/hello-world) and save it as `payload.dd` in the Pico. Currently, pico-ducky only supports DuckyScript 1.0, and some of 3.0.
14. Be careful, if your device isn't in [setup mode](#setup-mode), the device will reboot and after half a second, the script will run. 14. Be careful, if your device isn't in [setup mode](#setup-mode), the device will reboot and after half a second, the script will run.
@@ -70,14 +134,14 @@ API endpoints
/api/run/<filenumber> /api/run/<filenumber>
``` ```
### Setup mode ## Setup mode
To edit the payload, enter setup mode by connecting the pin 1 (`GP0`) to pin 3 (`GND`), this will stop the pico-ducky from injecting the payload in your own machine. To edit the payload, enter setup mode by connecting the pin 1 (`GP0`) to pin 3 (`GND`), this will stop the pico-ducky from injecting the payload in your own machine.
The easiest way to do so is by using a jumper wire between those pins as seen bellow. The easiest way to do so is by using a jumper wire between those pins as seen bellow.
![Setup mode with a jumper](images/setup-mode.png) ![Setup mode with a jumper](images/setup-mode.png)
### USB enable/disable mode ## USB enable/disable mode
If you need the pico-ducky to not show up as a USB mass storage device for stealth, follow these instructions. If you need the pico-ducky to not show up as a USB mass storage device for stealth, follow these instructions.
- Enter setup mode. - Enter setup mode.
@@ -92,7 +156,7 @@ Pico W: The default mode is USB mass storage **disabled**
![USB enable/disable mode](images/usb-boot-mode.png) ![USB enable/disable mode](images/usb-boot-mode.png)
### Multiple payloads ## Multiple payloads
Multiple payloads can be stored on the Pico and Pico W. Multiple payloads can be stored on the Pico and Pico W.
To select a payload, ground one of these pins: To select a payload, ground one of these pins:
@@ -101,7 +165,7 @@ To select a payload, ground one of these pins:
- GP10 - payload3.dd - GP10 - payload3.dd
- GP11 - payload4.dd - GP11 - payload4.dd
### Changing Keyboard Layouts ## Changing Keyboard Layouts
Copied from [Neradoc/Circuitpython_Keyboard_Layouts](https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/blob/main/PICODUCKY.md) Copied from [Neradoc/Circuitpython_Keyboard_Layouts](https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/blob/main/PICODUCKY.md)
@@ -179,8 +243,8 @@ keycode_win_de.mpy
### Installation Tool ### Installation Tool
[raspberrydeveloper](https://github.com/raspberrydeveloper) Created a tool to convert a blank RPi Pico to a ducky. [ryo-yamada](https://github.com/ryo-yamada) Created a tool to convert a blank RPi Pico to a ducky.
You can find the tool [here](https://github.com/raspberrydeveloper/pyducky) You can find the tool [here](https://github.com/ryo-yamada/PicoDuckyBuilder)
### Docs ### Docs
@@ -197,3 +261,9 @@ You can find the tool [here](https://github.com/raspberrydeveloper/pyducky)
[USB Rubber Ducky playlist by **Hak5**](https://www.youtube.com/playlist?list=PLW5y1tjAOzI0YaJslcjcI4zKI366tMBYk) [USB Rubber Ducky playlist by **Hak5**](https://www.youtube.com/playlist?list=PLW5y1tjAOzI0YaJslcjcI4zKI366tMBYk)
[CircuitPython tutorial on the Raspberry Pi Pico by **DroneBot Workshop**](https://www.youtube.com/watch?v=07vG-_CcDG0) [CircuitPython tutorial on the Raspberry Pi Pico by **DroneBot Workshop**](https://www.youtube.com/watch?v=07vG-_CcDG0)
## Related Projects
[Defcon31-ducky](https://github.com/iot-pwn/defcon31-ducky)
There are still a few of these available to purchase, US only.

View File

@@ -24,10 +24,10 @@ noStorageStatus = noStoragePin.value
# GP15 not connected == USB NOT visible # GP15 not connected == USB NOT visible
# GP15 connected to GND == USB visible # GP15 connected to GND == USB visible
if(board.board_id == 'raspberry_pi_pico'): if(board.board_id == 'raspberry_pi_pico' or board.board_id == 'raspberry_pi_pico2'):
# On Pi Pico, default to USB visible # On Pi Pico, default to USB visible
noStorage = not noStorageStatus noStorage = not noStorageStatus
elif(board.board_id == 'raspberry_pi_pico_w'): elif(board.board_id == 'raspberry_pi_pico_w' or board.board_id == 'raspberry_pi_pico2_w'):
# on Pi Pico W, default to USB hidden by default # on Pi Pico W, default to USB hidden by default
# so webapp can access storage # so webapp can access storage
noStorage = noStorageStatus noStorage = noStorageStatus

View File

@@ -0,0 +1,125 @@
import os
import shutil
import re
import sys
import zipfile
languages = [ "MAC_FR",
"US_DVO",
"WIN_BR",
"WIN_CZ",
"WIN_CZ1",
"WIN_DA",
"WIN_DE",
"WIN_ES",
"WIN_FR",
"WIN_HU",
"WIN_IT",
"WIN_PO",
"WIN_SW",
"WIN_TR",
"WIN_UK" ]
supported_boards = ["raspberry_pi_pico",
"raspberry_pi_pico_w",
"raspberry_pi_pico2",
"raspberry_pi_pico2_w"]
files_to_bundle = ["boot.py",
"code.py",
"duckyinpython.py",
"wsgiserver.py",
"webapp.py",
"secrets.py",
"payload.dd",
"payload2.dd",
"payload3.dd",
"payload4.dd",
"INSTALL.txt"]
dirs_to_bundle = ["lib"]
def bundle_files_to_zip(source_dir, destination_dir, file_list, target_file, replacement_dict, version):
"""
Bundles files from a source directory into a new directory with a unique name.
Args:
source_dir: Path to the source directory containing the files.
destination_dir: Path to the destination directory where bundles will be created.
file_list: List of filenames to be included in the bundle.
target_file: Filename of the file to be modified.
replacement_dict: Dictionary containing key-value pairs for text replacements.
Returns:
None
"""
if not os.path.exists(destination_dir):
os.makedirs(destination_dir)
# Generate a unique bundle name (e.g., using a timestamp)
bundle_name = f"pico-ducky-{version}-{destination_dir}.zip"
bundle_path = os.path.join(destination_dir, bundle_name)
# Create a temporary directory for the bundle contents
temp_dir = os.path.join(destination_dir, "temp_bundle")
os.makedirs(temp_dir)
for filename in file_list:
source_file = os.path.join(source_dir, filename)
destination_file = os.path.join(temp_dir, filename)
if filename == target_file:
with open(source_file, 'r') as f:
file_content = f.read()
for key, value in replacement_dict.items():
file_content = re.sub(key, value, file_content)
with open(destination_file, 'w') as f:
f.write(file_content)
else:
shutil.copy2(source_file, destination_file)
for dir in dirs_to_bundle:
shutil.copytree(os.path.join(source_dir,dir),os.path.join(temp_dir,dir))
#find uf2 files for supported boards
for root, dirs, files in os.walk(source_dir):
for file in files:
for board in supported_boards:
if '-'+board+'-' in file:
source_file = os.path.join(source_dir, file)
destination_file = os.path.join(temp_dir, file)
shutil.copy2(source_file, destination_file)
# Create the ZIP archive
with zipfile.ZipFile(bundle_path, 'w', zipfile.ZIP_DEFLATED) as zipf:
for root, _, files in os.walk(temp_dir):
for file in files:
file_path = os.path.join(root, file)
archive_path = os.path.relpath(file_path, temp_dir)
zipf.write(file_path, archive_path)
# Remove the temporary directory
shutil.rmtree(temp_dir)
def main(argv):
version = argv[0]
for dest_dir in languages:
source_directory = "US"
target_file_to_modify = "duckyinpython.py"
replacements = {
"#from keyboard_layout_win_LANG": "from keyboard_layout_"+dest_dir.lower(),
"#from keycode_win_LANG": "from keycode_"+dest_dir.lower(),
"from adafruit_hid.keyboard_": "#from adafruit_hid.keyboard_",
"from adafruit_hid.keycode": "#from adafruit_hid.keycode"
}
bundle_files_to_zip(source_directory, dest_dir, files_to_bundle,
target_file_to_modify, replacements, version)
if __name__ == "__main__":
main(sys.argv[1:])

View File

@@ -12,7 +12,7 @@ import digitalio
from board import * from board import *
import board import board
from duckyinpython import * from duckyinpython import *
if(board.board_id == 'raspberry_pi_pico_w'): if(board.board_id == 'raspberry_pi_pico_w' or board.board_id == 'raspberry_pi_pico2_w'):
import wifi import wifi
from webapp import * from webapp import *
@@ -41,9 +41,9 @@ def startWiFi():
#supervisor.disable_autoreload() #supervisor.disable_autoreload()
supervisor.runtime.autoreload = False supervisor.runtime.autoreload = False
if(board.board_id == 'raspberry_pi_pico'): if(board.board_id == 'raspberry_pi_pico' or board.board_id == 'raspberry_pi_pico2'):
led = pwmio.PWMOut(board.LED, frequency=5000, duty_cycle=0) led = pwmio.PWMOut(board.LED, frequency=5000, duty_cycle=0)
elif(board.board_id == 'raspberry_pi_pico_w'): elif(board.board_id == 'raspberry_pi_pico_w' or board.board_id == 'raspberry_pi_pico2_w'):
led = digitalio.DigitalInOut(board.LED) led = digitalio.DigitalInOut(board.LED)
led.switch_to_output() led.switch_to_output()
@@ -68,7 +68,7 @@ async def main_loop():
global led,button1 global led,button1
button_task = asyncio.create_task(monitor_buttons(button1)) button_task = asyncio.create_task(monitor_buttons(button1))
if(board.board_id == 'raspberry_pi_pico_w'): if(board.board_id == 'raspberry_pi_pico_w' or board.board_id == 'raspberry_pi_pico2_w'):
pico_led_task = asyncio.create_task(blink_pico_w_led(led)) pico_led_task = asyncio.create_task(blink_pico_w_led(led))
print("Starting Wifi") print("Starting Wifi")
startWiFi() startWiFi()

View File

@@ -1,9 +1,14 @@
# License : GPLv2.0 # License : GPLv2.0
# copyright (c) 2023 Dave Bailey # copyright (c) 2023 Dave Bailey
# Author: Dave Bailey (dbisu, @daveisu) # Author: Dave Bailey (dbisu, @daveisu)
#
# TODO: ADD support for the following:
# Add jitter
# Add LED functionality
import re
import time import time
import random
import digitalio import digitalio
from digitalio import DigitalInOut, Pull from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer from adafruit_debouncer import Debouncer
@@ -13,6 +18,8 @@ import pwmio
import asyncio import asyncio
import usb_hid import usb_hid
from adafruit_hid.keyboard import Keyboard from adafruit_hid.keyboard import Keyboard
from adafruit_hid.consumer_control import ConsumerControl
from adafruit_hid.consumer_control_code import ConsumerControlCode
# comment out these lines for non_US keyboards # comment out these lines for non_US keyboards
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout
@@ -20,13 +27,22 @@ from adafruit_hid.keycode import Keycode
# uncomment these lines for non_US keyboards # uncomment these lines for non_US keyboards
# replace LANG with appropriate language # replace LANG with appropriate language
#from keyboard_layout_win_LANG import KeyboardLayout #from keyboard_layout_win_LANG import KeyboardLayout as KeyboardLayout
#from keycode_win_LANG import Keycode #from keycode_win_LANG import Keycode
duckyCommands = { def _capsOn():
'WINDOWS': Keycode.WINDOWS, 'GUI': Keycode.GUI, return kbd.led_on(Keyboard.LED_CAPS_LOCK)
'APP': Keycode.APPLICATION, 'MENU': Keycode.APPLICATION, 'SHIFT': Keycode.SHIFT,
'ALT': Keycode.ALT, 'CONTROL': Keycode.CONTROL, 'CTRL': Keycode.CONTROL, def _numOn():
return kbd.led_on(Keyboard.LED_NUM_LOCK)
def _scrollOn():
return kbd.led_on(Keyboard.LED_SCROLL_LOCK)
duckyKeys = {
'WINDOWS': Keycode.GUI, 'RWINDOWS': Keycode.RIGHT_GUI, 'GUI': Keycode.GUI, 'RGUI': Keycode.RIGHT_GUI, 'COMMAND': Keycode.GUI, 'RCOMMAND': Keycode.RIGHT_GUI,
'APP': Keycode.APPLICATION, 'MENU': Keycode.APPLICATION, 'SHIFT': Keycode.SHIFT, 'RSHIFT': Keycode.RIGHT_SHIFT,
'ALT': Keycode.ALT, 'RALT': Keycode.RIGHT_ALT, 'OPTION': Keycode.ALT, 'ROPTION': Keycode.RIGHT_ALT, 'CONTROL': Keycode.CONTROL, 'CTRL': Keycode.CONTROL, 'RCTRL': Keycode.RIGHT_CONTROL,
'DOWNARROW': Keycode.DOWN_ARROW, 'DOWN': Keycode.DOWN_ARROW, 'LEFTARROW': Keycode.LEFT_ARROW, 'DOWNARROW': Keycode.DOWN_ARROW, 'DOWN': Keycode.DOWN_ARROW, 'LEFTARROW': Keycode.LEFT_ARROW,
'LEFT': Keycode.LEFT_ARROW, 'RIGHTARROW': Keycode.RIGHT_ARROW, 'RIGHT': Keycode.RIGHT_ARROW, 'LEFT': Keycode.LEFT_ARROW, 'RIGHTARROW': Keycode.RIGHT_ARROW, 'RIGHT': Keycode.RIGHT_ARROW,
'UPARROW': Keycode.UP_ARROW, 'UP': Keycode.UP_ARROW, 'BREAK': Keycode.PAUSE, 'UPARROW': Keycode.UP_ARROW, 'UP': Keycode.UP_ARROW, 'BREAK': Keycode.PAUSE,
@@ -44,48 +60,243 @@ duckyCommands = {
'Z': Keycode.Z, 'F1': Keycode.F1, 'F2': Keycode.F2, 'F3': Keycode.F3, 'Z': Keycode.Z, 'F1': Keycode.F1, 'F2': Keycode.F2, 'F3': Keycode.F3,
'F4': Keycode.F4, 'F5': Keycode.F5, 'F6': Keycode.F6, 'F7': Keycode.F7, 'F4': Keycode.F4, 'F5': Keycode.F5, 'F6': Keycode.F6, 'F7': Keycode.F7,
'F8': Keycode.F8, 'F9': Keycode.F9, 'F10': Keycode.F10, 'F11': Keycode.F11, 'F8': Keycode.F8, 'F9': Keycode.F9, 'F10': Keycode.F10, 'F11': Keycode.F11,
'F12': Keycode.F12, 'F12': Keycode.F12, 'F13': Keycode.F13, 'F14': Keycode.F14, 'F15': Keycode.F15,
'F16': Keycode.F16, 'F17': Keycode.F17, 'F18': Keycode.F18, 'F19': Keycode.F19,
'F20': Keycode.F20, 'F21': Keycode.F21, 'F22': Keycode.F22, 'F23': Keycode.F23,
'F24': Keycode.F24
} }
duckyConsumerKeys = {
'MK_VOLUP': ConsumerControlCode.VOLUME_INCREMENT, 'MK_VOLDOWN': ConsumerControlCode.VOLUME_DECREMENT, 'MK_MUTE': ConsumerControlCode.MUTE,
'MK_NEXT': ConsumerControlCode.SCAN_NEXT_TRACK, 'MK_PREV': ConsumerControlCode.SCAN_PREVIOUS_TRACK,
'MK_PP': ConsumerControlCode.PLAY_PAUSE, 'MK_STOP': ConsumerControlCode.STOP
}
variables = {"$_RANDOM_MIN": 0, "$_RANDOM_MAX": 65535}
internalVariables = {"$_CAPSLOCK_ON": _capsOn, "$_NUMLOCK_ON": _numOn, "$_SCROLLLOCK_ON": _scrollOn}
defines = {}
functions = {}
letters = "abcdefghijklmnopqrstuvwxyz"
numbers = "0123456789"
specialChars = "!@#$%^&*()"
class IF:
def __init__(self, condition, codeIter):
self.condition = condition
self.codeIter = list(codeIter)
self.lastIfResult = None
def _exitIf(self):
_depth = 0
for line in self.codeIter:
line = self.codeIter.pop(0)
line = line.strip()
if line.upper().startswith("END_IF"):
_depth -= 1
elif line.upper().startswith("IF"):
_depth += 1
if _depth < 0:
print("No else, exiting" + str(list(self.codeIter)))
break
return(self.codeIter)
def runIf(self):
if isinstance(self.condition, str):
self.lastIfResult = evaluateExpression(self.condition)
elif isinstance(self.condition, bool):
self.lastIfResult = self.condition
else:
raise ValueError("Invalid condition type")
# print(f"condition {self.condition} result is {self.lastIfResult} since \"$VAR\" is {variables["$VAR"]}, code is {self.codeIter}")
depth = 0
for line in self.codeIter:
line = self.codeIter.pop(0)
line = line.strip()
if line == "":
continue
# print(line)
if line.startswith("IF"):
depth += 1
elif line.startswith("END_IF"):
if depth == 0:
return(self.codeIter, -1)
depth -=1
elif line.startswith("ELSE") and depth == 0:
# print(f"ELSE LINE {line}, lastIfResult: {self.lastIfResult}")
if self.lastIfResult is False:
line = line[4:].strip() # Remove 'ELSE' and strip whitespace
if line.startswith("IF"):
nestedCondition = _getIfCondition(line)
# print(f"nested IF {nestedCondition}")
self.codeIter, self.lastIfResult = IF(nestedCondition, self.codeIter).runIf()
if self.lastIfResult == -1 or self.lastIfResult == True:
# print(f"self.lastIfResult {self.lastIfResult}")
return(self.codeIter, True)
else:
return IF(True, self.codeIter).runIf() #< Regular ELSE block
else:
self._exitIf()
break
# Process regular lines
elif self.lastIfResult:
# print(f"running line {line}")
self.codeIter = list(parseLine(line, self.codeIter))
# print("end of if")
return(self.codeIter, self.lastIfResult)
def _getIfCondition(line):
return str(line)[2:-4].strip()
def _isCodeBlock(line):
line = line.upper().strip()
if line.startswith("IF") or line.startswith("WHILE"):
return True
return False
def _getCodeBlock(linesIter):
"""Returns the code block starting at the given line."""
code = []
depth = 1
for line in linesIter:
line = line.strip()
if line.upper().startswith("END_"):
depth -= 1
elif _isCodeBlock(line):
depth += 1
if depth <= 0:
break
code.append(line)
return code
def evaluateExpression(expression):
"""Evaluates an expression with variables and returns the result."""
# Replace variables (e.g., $FOO) in the expression with their values
expression = re.sub(r"\$(\w+)", lambda m: str(variables.get(f"${m.group(1)}", 0)), expression)
expression = expression.replace("^", "**") #< Replace ^ with ** for exponentiation
expression = expression.replace("&&", "and")
expression = expression.replace("||", "or")
return eval(expression, {}, variables)
def deepcopy(List):
return(List[:])
def convertLine(line): def convertLine(line):
newline = [] commands = []
# print(line) # print(line)
# loop on each key - the filter removes empty values # loop on each key - the filter removes empty values
for key in filter(None, line.split(" ")): for key in filter(None, line.split(" ")):
key = key.upper() key = key.upper()
# find the keycode for the command in the list # find the keycode for the command in the list
command_keycode = duckyCommands.get(key, None) command_keycode = duckyKeys.get(key, None)
command_consumer_keycode = duckyConsumerKeys.get(key, None)
if command_keycode is not None: if command_keycode is not None:
# if it exists in the list, use it # if it exists in the list, use it
newline.append(command_keycode) commands.append(command_keycode)
elif command_consumer_keycode is not None:
# if it exists in the list, use it
commands.append(1000+command_consumer_keycode)
elif hasattr(Keycode, key): elif hasattr(Keycode, key):
# if it's in the Keycode module, use it (allows any valid keycode) # if it's in the Keycode module, use it (allows any valid keycode)
newline.append(getattr(Keycode, key)) commands.append(getattr(Keycode, key))
else: else:
# if it's not a known key name, show the error for diagnosis # if it's not a known key name, show the error for diagnosis
print(f"Unknown key: <{key}>") print(f"Unknown key: <{key}>")
# print(newline) # print(commands)
return newline return commands
def runScriptLine(line): def runScriptLine(line):
for k in line: keys = convertLine(line)
kbd.press(k) for k in keys:
kbd.release_all() if k > 1000:
consumerControl.press(int(k-1000))
else:
kbd.press(k)
for k in reversed(keys):
if k > 1000:
consumerControl.release()
else:
kbd.release(k)
def sendString(line): def sendString(line):
layout.write(line) layout.write(line)
def parseLine(line): def replaceVariables(line):
global defaultDelay for var in variables:
if(line[0:3] == "REM"): line = line.replace(var, str(variables[var]))
# ignore ducky script comments for var in internalVariables:
line = line.replace(var, str(internalVariables[var]()))
return line
def replaceDefines(line):
for define, value in defines.items():
line = line.replace(define, value)
return line
def parseLine(line, script_lines):
global defaultDelay, variables, functions, defines
line = line.strip()
line = line.replace("$_RANDOM_INT", str(random.randint(int(variables.get("$_RANDOM_MIN", 0)), int(variables.get("$_RANDOM_MAX", 65535)))))
line = replaceDefines(line)
if line[:10] == "INJECT_MOD":
line = line[11:]
elif line.startswith("REM_BLOCK"):
while line.startswith("END_REM") == False:
line = next(script_lines).strip()
# print(line)
elif(line[0:3] == "REM"):
pass pass
elif line.startswith("HOLD"):
# HOLD command to press and hold a key
key = line[5:].strip().upper()
commandKeycode = duckyKeys.get(key, None)
if commandKeycode:
kbd.press(commandKeycode)
else:
print(f"Unknown key to HOLD: <{key}>")
elif line.startswith("RELEASE"):
# RELEASE command to release a held key
key = line[8:].strip().upper()
commandKeycode = duckyKeys.get(key, None)
if commandKeycode:
kbd.release(commandKeycode)
else:
print(f"Unknown key to RELEASE: <{key}>")
elif(line[0:5] == "DELAY"): elif(line[0:5] == "DELAY"):
line = replaceVariables(line)
time.sleep(float(line[6:])/1000) time.sleep(float(line[6:])/1000)
elif line == "STRINGLN": #< stringLN block
line = next(script_lines).strip()
line = replaceVariables(line)
while line.startswith("END_STRINGLN") == False:
sendString(line)
kbd.press(Keycode.ENTER)
kbd.release(Keycode.ENTER)
line = next(script_lines).strip()
line = replaceVariables(line)
line = replaceDefines(line)
elif(line[0:8] == "STRINGLN"):
sendString(replaceVariables(line[9:]))
kbd.press(Keycode.ENTER)
kbd.release(Keycode.ENTER)
elif line == "STRING": #< string block
line = next(script_lines).strip()
line = replaceVariables(line)
while line.startswith("END_STRING") == False:
sendString(line)
line = next(script_lines).strip()
line = replaceVariables(line)
line = replaceDefines(line)
elif(line[0:6] == "STRING"): elif(line[0:6] == "STRING"):
sendString(line[7:]) sendString(replaceVariables(line[7:]))
elif(line[0:5] == "PRINT"): elif(line[0:5] == "PRINT"):
print("[SCRIPT]: " + line[6:]) line = replaceVariables(line[6:])
print("[SCRIPT]: " + line)
elif(line[0:6] == "IMPORT"): elif(line[0:6] == "IMPORT"):
runScript(line[7:]) runScript(line[7:])
elif(line[0:13] == "DEFAULT_DELAY"): elif(line[0:13] == "DEFAULT_DELAY"):
@@ -97,16 +308,126 @@ def parseLine(line):
led.value = False led.value = False
else: else:
led.value = True led.value = True
elif(line[0:3] == "LED"):
if(led.value == True):
led.value = False
else:
led.value = True
elif(line[:7] == "LED_OFF"):
led.value = False
elif(line[:5] == "LED_R"):
led.value = True
elif(line[:5] == "LED_G"):
led.value = True
elif(line[0:21] == "WAIT_FOR_BUTTON_PRESS"):
button_pressed = False
# NOTE: we don't use assincio in this case because we want to block code execution
while not button_pressed:
button1.update()
button1Pushed = button1.fell
button1Released = button1.rose
button1Held = not button1.value
if(button1Pushed):
print("Button 1 pushed")
button_pressed = True
elif line.startswith("VAR"):
match = re.match(r"VAR\s+\$(\w+)\s*=\s*(.+)", line)
if match:
varName = f"${match.group(1)}"
value = evaluateExpression(match.group(2))
variables[varName] = value
else:
raise SyntaxError(f"Invalid variable declaration: {line}")
elif line.startswith("$"):
match = re.match(r"\$(\w+)\s*=\s*(.+)", line)
if match:
varName = f"${match.group(1)}"
expression = match.group(2)
value = evaluateExpression(expression)
variables[varName] = value
else:
raise SyntaxError(f"Invalid variable update, declare variable first: {line}")
elif line.startswith("DEFINE"):
defineLocation = line.find(" ")
valueLocation = line.find(" ", defineLocation + 1)
defineName = line[defineLocation+1:valueLocation]
defineValue = line[valueLocation+1:]
defines[defineName] = defineValue
elif line.startswith("FUNCTION"):
# print("ENTER FUNCTION")
func_name = line.split()[1]
functions[func_name] = []
line = next(script_lines).strip()
while line != "END_FUNCTION":
functions[func_name].append(line)
line = next(script_lines).strip()
elif line.startswith("WHILE"):
# print("ENTER WHILE LOOP")
condition = line[5:].strip()
loopCode = list(_getCodeBlock(script_lines))
while evaluateExpression(condition) == True:
currentIterCode = deepcopy(loopCode)
print(loopCode)
while currentIterCode:
loopLine = currentIterCode.pop(0)
currentIterCode = list(parseLine(loopLine, iter(currentIterCode))) #< very inefficient, should be replaced later.
elif line.upper().startswith("IF"):
# print("ENTER IF")
script_lines, ret = IF(_getIfCondition(line), script_lines).runIf()
print(f"IF returned {ret} code")
elif line.upper().startswith("END_IF"):
pass
elif line == "RANDOM_LOWERCASE_LETTER":
sendString(random.choice(letters))
elif line == "RANDOM_UPPERCASE_LETTER":
sendString(random.choice(letters.upper()))
elif line == "RANDOM_LETTER":
sendString(random.choice(letters + letters.upper()))
elif line == "RANDOM_NUMBER":
sendString(random.choice(numbers))
elif line == "RANDOM_SPECIAL":
sendString(random.choice(specialChars))
elif line == "RANDOM_CHAR":
sendString(random.choice(letters + letters.upper() + numbers + specialChars))
elif line == "VID_RANDOM" or line == "PID_RANDOM":
for _ in range(4):
sendString(random.choice("0123456789ABCDEF"))
elif line == "MAN_RANDOM" or line == "PROD_RANDOM":
for _ in range(12):
sendString(random.choice(letters + letters.upper() + numbers))
elif line == "SERIAL_RANDOM":
for _ in range(12):
sendString(random.choice(letters + letters.upper() + numbers + specialChars))
elif line == "RESET":
kbd.release_all()
elif line in functions:
updated_lines = []
inside_while_block = False
for func_line in functions[line]:
if func_line.startswith("WHILE"):
inside_while_block = True # Start skipping lines
updated_lines.append(func_line)
elif func_line.startswith("END_WHILE"):
inside_while_block = False # Stop skipping lines
updated_lines.append(func_line)
parseLine(updated_lines[0], iter(updated_lines))
updated_lines = [] # Clear updated_lines after parsing
elif inside_while_block:
updated_lines.append(func_line)
elif not (func_line.startswith("END_WHILE") or func_line.startswith("WHILE")):
parseLine(func_line, iter(functions[line]))
else: else:
newScriptLine = convertLine(line) runScriptLine(line)
runScriptLine(newScriptLine)
return(script_lines)
kbd = Keyboard(usb_hid.devices) kbd = Keyboard(usb_hid.devices)
consumerControl = ConsumerControl(usb_hid.devices)
layout = KeyboardLayout(kbd) layout = KeyboardLayout(kbd)
#init button #init button
button1_pin = DigitalInOut(GP22) # defaults to input button1_pin = DigitalInOut(GP22) # defaults to input
button1_pin.pull = Pull.UP # turn on internal pull-up resistor button1_pin.pull = Pull.UP # turn on internal pull-up resistor
@@ -137,22 +458,32 @@ def runScript(file):
global defaultDelay global defaultDelay
duckyScriptPath = file duckyScriptPath = file
restart = True
try: try:
f = open(duckyScriptPath,"r",encoding='utf-8') while restart:
previousLine = "" restart = False
for line in f: with open(duckyScriptPath, "r", encoding='utf-8') as f:
line = line.rstrip() script_lines = iter(f.readlines())
if(line[0:6] == "REPEAT"): previousLine = ""
for i in range(int(line[7:])): for line in script_lines:
#repeat the last command print(f"runScript: {line}")
parseLine(previousLine) if(line[0:6] == "REPEAT"):
time.sleep(float(defaultDelay)/1000) for i in range(int(line[7:])):
else: #repeat the last command
parseLine(line) parseLine(previousLine, script_lines)
previousLine = line time.sleep(float(defaultDelay) / 1000)
time.sleep(float(defaultDelay)/1000) elif line.startswith("RESTART_PAYLOAD"):
restart = True
break
elif line.startswith("STOP_PAYLOAD"):
restart = False
break
else:
parseLine(line, script_lines)
previousLine = line
time.sleep(float(defaultDelay) / 1000)
except OSError as e: except OSError as e:
print("Unable to open file ", file) print("Unable to open file", file)
def selectPayload(): def selectPayload():
global payload1Pin, payload2Pin, payload3Pin, payload4Pin global payload1Pin, payload2Pin, payload3Pin, payload4Pin
@@ -188,9 +519,9 @@ def selectPayload():
async def blink_led(led): async def blink_led(led):
print("Blink") print("Blink")
if(board.board_id == 'raspberry_pi_pico'): if(board.board_id == 'raspberry_pi_pico' or board.board_id == 'raspberry_pi_pico2'):
blink_pico_led(led) blink_pico_led(led)
elif(board.board_id == 'raspberry_pi_pico_w'): elif(board.board_id == 'raspberry_pi_pico_w' or board.board_id == 'raspberry_pi_pico2_w'):
blink_pico_w_led(led) blink_pico_w_led(led)
async def blink_pico_led(led): async def blink_pico_led(led):

13
examples/functions.dd Normal file
View File

@@ -0,0 +1,13 @@
REM Example Function
FUNCTION COUNTDOWN()
REM The next four lines open Notepad in Windows and type "Hello World!"
GUI r
DELAY 1000
STRING notepad
ENTER
DELAY 2000
STRING Hello World!
ENTER
END_FUNCTION
COUNTDOWN()

7
examples/while_loops.dd Normal file
View File

@@ -0,0 +1,7 @@
VAR $TIME = 3
WHILE ($TIME > 0)
STRING .
DELAY 500
STRING While Looop!!
ENTER
END_WHILE

View File

@@ -232,7 +232,7 @@ async def startWebService():
PORT = 80 # Port to listen on PORT = 80 # Port to listen on
print(HOST,PORT) print(HOST,PORT)
wsgiServer = server.WSGIServer(80, application=web_app) wsgiServer = server.WSGIServer(PORT, application=web_app)
print(f"open this IP in your browser: http://{HOST}:{PORT}/") print(f"open this IP in your browser: http://{HOST}:{PORT}/")
@@ -240,4 +240,4 @@ async def startWebService():
wsgiServer.start() wsgiServer.start()
while True: while True:
wsgiServer.update_poll() wsgiServer.update_poll()
await asyncio.sleep(0) await asyncio.sleep(0)