27 Commits

Author SHA1 Message Date
Dave
58875cd1b6 Update for CircuitPython 10.x support 2026-01-10 15:40:24 -06:00
XtramCZ
04eeacac09 Small improvements (#195)
* fixed crashes on one-line scripts, auto .dd

* Delete button

* Additional crash fix

* Disable read-only

---------

Co-authored-by: Dave <dbisu@users.noreply.github.com>
2026-01-10 12:17:16 -06:00
IDoNotHaveAUsernam3
c3184a5390 Fix "The next four lines" to replace four with five (#338) 2026-01-10 12:01:10 -06:00
Tzur Soffer
fb55875e3c Variable and expression bugfixes (#325)
* Fix evaluating with Booleans

* replaced re with a function (this feature of re is not supported on circutpython)

* Fixed internalVar in expression + allows for +=, *=, etc in variable math

* Removed added +=, *=, etc operations as they are not in the official language

Removed added +=, *=, etc operations as they are not in the official language. I added them in a previous commit and removed them in this one.

* Fix duplicate lines
2026-01-10 12:00:15 -06:00
Linusx
986c919616 Update webapp.py / web ui (#144)
* Update webapp.py

PS: Font will work on most devices (preinstalled), else it doesn't really matter

* Update webapp.py
2026-01-10 11:57:08 -06:00
Dave
10bc8b9489 Moved pin definitions into pins.py to help support different board form factors 2026-01-10 11:11:19 -06:00
ooqe
5ac23fdaf0 Fix syntax error in await runScript call (#336) 2025-11-25 16:53:40 -06:00
Cheesy
d730a804e0 Adds Keystroke Reflection Exfoliation (#335)
* Adds Hak5's Keystroke Reflection Exfoliation

Adds a way to read in caps, scroll, and num lock so that you can export information from the attack into a loot.bin. Has some caveats on how it works

* Fixed programing status bug
2025-11-16 19:15:54 -06:00
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
13 changed files with 881 additions and 179 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-10.0.3.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-10.0.3.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-10.0.3.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-10.0.3.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 10.0.3
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 10.0.3
Download [CircuitPython for the Raspberry Pi Pico 2](https://circuitpython.org/board/raspberry_pi_pico2/). *Updated to 10.0.3
Download [CircuitPython for the Raspberry Pi Pico 2W](https://circuitpython.org/board/raspberry_pi_pico2_w/). *Updated to 10.0.3
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-10.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.
@@ -41,12 +105,12 @@ Install and have your USB Rubber Ducky working in less than 5 minutes.
10. Copy `boot.py` from your clone to the root of your Pico. 10. Copy `boot.py` from your clone to the root of your Pico.
11. Copy `duckyinpython.py`, `code.py`, `webapp.py`, `wsgiserver.py` to the root folder of the Pico. 11. Copy `duckyinpython.py`, `code.py`, `pins.py`, `webapp.py`, `wsgiserver.py` to the root folder of the Pico.
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,12 +243,12 @@ 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
[CircuitPython](https://circuitpython.readthedocs.io/en/6.3.x/README.html) [CircuitPython](https://docs.circuitpython.org/en/latest/README.html)
[CircuitPython HID](https://learn.adafruit.com/circuitpython-essentials/circuitpython-hid-keyboard-and-mouse) [CircuitPython HID](https://learn.adafruit.com/circuitpython-essentials/circuitpython-hid-keyboard-and-mouse)
@@ -197,3 +261,8 @@ 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)

23
boot.py
View File

@@ -7,7 +7,20 @@ from board import *
import board import board
import digitalio import digitalio
import storage import storage
import os
def is_exfil_enabled(payload_path="payload.dd"):
try:
with open(payload_path, "r") as f:
for line in f:
if "$_EXFIL_MODE_ENABLED" in line and "TRUE" in line.upper():
return True
except OSError:
pass
return False
exfil_enabled = is_exfil_enabled()
loot_exists = "loot.bin" in os.listdir("/")
noStorage = False noStorage = False
noStoragePin = digitalio.DigitalInOut(GP15) noStoragePin = digitalio.DigitalInOut(GP15)
noStoragePin.switch_to_input(pull=digitalio.Pull.UP) noStoragePin.switch_to_input(pull=digitalio.Pull.UP)
@@ -23,11 +36,13 @@ noStorageStatus = noStoragePin.value
# Pico W: # Pico W:
# 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 exfil_enabled:
if(board.board_id == 'raspberry_pi_pico'): if not loot_exists:
storage.disable_usb_drive()
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
@@ -39,3 +54,5 @@ if(noStorage == True):
else: else:
# normal boot # normal boot
print("USB drive enabled") print("USB drive enabled")

View File

@@ -0,0 +1,126 @@
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",
"pins.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:])

48
code.py
View File

@@ -5,14 +5,14 @@
import supervisor import supervisor
import os
import pwmio
import time import time
import digitalio 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,42 +41,48 @@ 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()
async def run_payload_on_startup():
progStatus = False
progStatus = getProgrammingStatus()
print("progStatus", progStatus)
if(progStatus == False):
print("Finding payload")
if "loot.bin" in os.listdir("/"):
print("loot.bin exists, skipping payload execution.")
else:
payload = selectPayload()
await asyncio.sleep(0.1)
print("Running")
await runScript(payload)
else:
print("Done")
progStatus = False
progStatus = getProgrammingStatus()
print("progStatus", progStatus)
if(progStatus == False):
print("Finding payload")
# not in setup mode, inject the payload
payload = selectPayload()
print("Running ", payload)
runScript(payload)
print("Done")
else:
print("Update your payload")
led_state = False led_state = False
async def main_loop(): 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'): payload_task = asyncio.create_task(run_payload_on_startup())
led_task = asyncio.create_task(monitor_led_changes())
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()
print("Starting Web Service") print("Starting Web Service")
webservice_task = asyncio.create_task(startWebService()) webservice_task = asyncio.create_task(startWebService())
await asyncio.gather(pico_led_task, button_task, webservice_task) await asyncio.gather(pico_led_task, button_task, webservice_task, payload_task, led_task)
else: else:
pico_led_task = asyncio.create_task(blink_pico_led(led)) pico_led_task = asyncio.create_task(blink_pico_led(led))
await asyncio.gather(pico_led_task, button_task) await asyncio.gather(pico_led_task, button_task, payload_task, led_task )
asyncio.run(main_loop()) asyncio.run(main_loop())

View File

@@ -1,18 +1,24 @@
# 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
import board import board
from board import * from board import *
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
from pins import *
# 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 +26,40 @@ 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)
def pressLock(key):
kbd.press(key)
kbd.release(key)
def SaveKeyboardLedState():
variables["$_INITIAL_SCROLLLOCK"] = _scrollOn()
variables["$_INITIAL_NUMLOCK"] = _numOn()
variables ["$_INITIAL_CAPSLOCK"] = _capsOn()
def RestoreKeyboardLedState():
if(variables["$_INITIAL_CAPSLOCK"] != _capsOn()):
pressLock(Keycode.CAPS_LOCK)
if(variables["$_INITIAL_NUMLOCK"] != _numOn()):
pressLock(Keycode.NUM_LOCK)
if(variables["$_INITIAL_SCROLLLOCK"] != _scrollOn()):
pressLock(Keycode.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 +77,255 @@ 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,"$_EXFIL_MODE_ENABLED": False,"$_EXFIL_LEDS_ENABLED": False,"$_INITIAL_SCROLLLOCK": False, "$_INITIAL_NUMLOCK": False, "$_INITIAL_CAPSLOCK": False}
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 replaceBooleans(text): #< fix capitalization mistakes in true and false (for evaluating with booleans)
# Replace any letter-by-letter match for "true" with the proper "True"
text = re.sub(r'[Tt][Rr][Uu][Ee]', 'True', text)
# Replace any letter-by-letter match for "false" with the proper "False"
text = re.sub(r'[Ff][Aa][Ll][Ss][Ee]', 'False', text)
return text
def evaluateExpression(expression):
"""Evaluates an expression with variables and returns the result."""
expression = replaceVariables(expression)
expression = replaceBooleans(expression) #< Cant use re due its limitation in circutpython
print(expression)
expression = expression.replace("^", "**") #< Replace ^ with ** for exponentiation
expression = expression.replace("&&", "and")
expression = expression.replace("||", "or")
expression = expression.replace("TRUE", "True")
expression = expression.replace("FALSE", "False")
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
async 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,71 +337,182 @@ 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"):
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 == "SAVE_HOST_KEYBOARD_LOCK_STATE":
SaveKeyboardLedState()
elif line == "RESTORE_HOST_KEYBOARD_LOCK_STATE":
RestoreKeyboardLedState()
elif line == "WAIT_FOR_SCROLL_CHANGE":
last_scroll_state = _scrollOn()
while True:
current_scroll_state = _scrollOn()
if current_scroll_state != last_scroll_state:
break
await asyncio.sleep(0.01)
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
button1_pin = DigitalInOut(GP22) # defaults to input
button1_pin.pull = Pull.UP # turn on internal pull-up resistor
button1 = Debouncer(button1_pin)
#init payload selection switch
payload1Pin = digitalio.DigitalInOut(GP4)
payload1Pin.switch_to_input(pull=digitalio.Pull.UP)
payload2Pin = digitalio.DigitalInOut(GP5)
payload2Pin.switch_to_input(pull=digitalio.Pull.UP)
payload3Pin = digitalio.DigitalInOut(GP10)
payload3Pin.switch_to_input(pull=digitalio.Pull.UP)
payload4Pin = digitalio.DigitalInOut(GP11)
payload4Pin.switch_to_input(pull=digitalio.Pull.UP)
def getProgrammingStatus(): def getProgrammingStatus():
# check GP0 for setup mode
# see setup mode for instructions # see setup mode for instructions
progStatusPin = digitalio.DigitalInOut(GP0)
progStatusPin.switch_to_input(pull=digitalio.Pull.UP)
progStatus = not progStatusPin.value progStatus = not progStatusPin.value
return(progStatus) return(progStatus)
defaultDelay = 0 defaultDelay = 0
def runScript(file): async 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:
await 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
payload = "payload.dd" payload = "payload.dd"
# check switch status # check switch status
# payload1 = GPIO4 to GND
# payload2 = GPIO5 to GND
# payload3 = GPIO10 to GND
# payload4 = GPIO11 to GND
payload1State = not payload1Pin.value payload1State = not payload1Pin.value
payload2State = not payload2Pin.value payload2State = not payload2Pin.value
payload3State = not payload3Pin.value payload3State = not payload3Pin.value
@@ -188,50 +539,58 @@ 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):
print("starting blink_pico_led") print("starting blink_pico_led")
led_state = False led_state = False
while True: while True:
if led_state: if(variables.get("$_EXFIL_LEDS_ENABLED")):
#led_pwm_up(led) led.duty_cycle = 65535
#print("led up")
for i in range(100):
# PWM LED up and down
if i < 50:
led.duty_cycle = int(i * 2 * 65535 / 100) # Up
await asyncio.sleep(0.01)
led_state = False
else: else:
#led_pwm_down(led) if led_state:
#print("led down") #led_pwm_up(led)
for i in range(100): #print("led up")
# PWM LED up and down for i in range(100):
if i >= 50: # PWM LED up and down
led.duty_cycle = 65535 - int((i - 50) * 2 * 65535 / 100) # Down if i < 50:
await asyncio.sleep(0.01) led.duty_cycle = int(i * 2 * 65535 / 100) # Up
led_state = True await asyncio.sleep(0.01)
led_state = False
else:
#led_pwm_down(led)
#print("led down")
for i in range(100):
# PWM LED up and down
if i >= 50:
led.duty_cycle = 65535 - int((i - 50) * 2 * 65535 / 100) # Down
await asyncio.sleep(0.01)
led_state = True
await asyncio.sleep(0) await asyncio.sleep(0)
async def blink_pico_w_led(led): async def blink_pico_w_led(led):
print("starting blink_pico_w_led") print("starting blink_pico_w_led")
led_state = False led_state = False
while True: while True:
if led_state: if(variables.get("$_EXFIL_LEDS_ENABLED")):
#print("led on")
led.value = 1 led.value = 1
else:
if led_state:
#print("led on")
led.value = 1
await asyncio.sleep(0.5)
led_state = False
else:
#print("led off")
led.value = 0
await asyncio.sleep(0.5)
led_state = True
await asyncio.sleep(0.5) await asyncio.sleep(0.5)
led_state = False
else:
#print("led off")
led.value = 0
await asyncio.sleep(0.5)
led_state = True
await asyncio.sleep(0.5)
async def monitor_buttons(button1): async def monitor_buttons(button1):
global inBlinkeyMode, inMenu, enableRandomBeep, enableSirenMode,pixel global inBlinkeyMode, inMenu, enableRandomBeep, enableSirenMode,pixel
@@ -257,8 +616,51 @@ async def monitor_buttons(button1):
# Run selected payload # Run selected payload
payload = selectPayload() payload = selectPayload()
print("Running ", payload) print("Running ", payload)
runScript(payload) await runScript(payload)
print("Done") print("Done")
button1Down = False button1Down = False
await asyncio.sleep(0) await asyncio.sleep(0)
async def monitor_led_changes():
print("starting monitor_led_changes")
while True:
if variables.get("$_EXFIL_MODE_ENABLED"):
try:
bit_list = []
last_caps_state = _capsOn()
last_num_state = _numOn()
last_scroll_state = _scrollOn()
with open("loot.bin", "ab") as file:
while variables.get("$_EXFIL_MODE_ENABLED"):
caps_state = _capsOn()
num_state = _numOn()
scroll_state = _scrollOn()
if caps_state != last_caps_state:
bit_list.append(0)
last_caps_state = caps_state
elif num_state != last_num_state:
bit_list.append(1)
last_num_state = num_state
if len(bit_list) == 8:
byte = 0
for b in bit_list:
byte = (byte << 1) | b
file.write(bytes([byte]))
bit_list = []
if scroll_state != last_scroll_state:
variables["$_EXFIL_LEDS_ENABLED"] = False
break
await asyncio.sleep(0.001)
except Exception as e:
print(f"Error occurred: {e}")
await asyncio.sleep(0.0)

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

@@ -1,6 +1,6 @@
REM The next four lines open Notepad in Windows and type "Hello World!" REM The next five lines open Notepad in Windows and type "Hello World!"
GUI r GUI r
STRING notepad STRING notepad
ENTER ENTER
DELAY 250 DELAY 250
STRING Hello World! STRING Hello World!

29
pins.py Normal file
View File

@@ -0,0 +1,29 @@
import digitalio
from digitalio import DigitalInOut, Pull
from board import *
from adafruit_debouncer import Debouncer
#init button
button1_pin = DigitalInOut(GP22) # defaults to input
button1_pin.pull = Pull.UP # turn on internal pull-up resistor
button1 = Debouncer(button1_pin)
# payload1 = GPIO4 to GND
# payload2 = GPIO5 to GND
# payload3 = GPIO10 to GND
# payload4 = GPIO11 to GND
#init payload selection switch
payload1Pin = digitalio.DigitalInOut(GP4)
payload1Pin.switch_to_input(pull=digitalio.Pull.UP)
payload2Pin = digitalio.DigitalInOut(GP5)
payload2Pin.switch_to_input(pull=digitalio.Pull.UP)
payload3Pin = digitalio.DigitalInOut(GP10)
payload3Pin.switch_to_input(pull=digitalio.Pull.UP)
payload4Pin = digitalio.DigitalInOut(GP11)
payload4Pin.switch_to_input(pull=digitalio.Pull.UP)
# check GP0 for setup mode
progStatusPin = digitalio.DigitalInOut(GP0)
progStatusPin.switch_to_input(pull=digitalio.Pull.UP)

112
webapp.py
View File

@@ -14,66 +14,75 @@ import wifi
from duckyinpython import * from duckyinpython import *
payload_html = """<!DOCTYPE html> payload_html = """<html>
<html> <head>
<head> <title>Pico W Ducky</title> </head> <title>Pico W Ducky</title>
<body> <h1>Pico W Ducky</h1> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<table border="1"> <tr><th>Payload</th><th>Actions</th></tr> {} </table> <style>button{{margin:0.2em}}html{{font-family:'Open Sans', sans-serif;margin:2%}}table{{width:30%;max-width:20vh;margin-bottom:1em;border-collapse:collapse}}</style>
<br> </head>
<a href="/new">New Script</a> <body>
<h1>Pico W Ducky</h1>
<table border="1"><tr><th>Payload</th><th>Actions</th></tr>{}</table><br>
<a href="/new"><button>New Script</button></a>
</body> </body>
</html> </html>
""" """
edit_html = """<!DOCTYPE html> edit_html = """<!DOCTYPE html>
<html> <html>
<head> <head>
<title>Script Editor</title> <title>Script Editor</title>
</head> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<body> <style>button{{margin-top:1em}}.main{{font-family:'Open Sans', sans-serif;margin:2%}}textarea{{width:100%;max-width:80vh;margin-bottom:1em;height:50vh}}</style>
<form action="/write/{}" method="POST"> </head>
<textarea rows="5" cols="60" name="scriptData">{}</textarea> <body>
<br/> <form action="/write/{}" method="POST">
<input type="submit" value="submit"/> <textarea rows="5" name="scriptData">{}</textarea><br/>
</form> <input type="submit" value="submit"/>
<br> </form>
<a href="/ducky">Home</a> <br>
</body> <a href="/ducky"><button>Home</button></a>
</body>
</html> </html>
""" """
new_html = """<!DOCTYPE html> new_html = """<!DOCTYPE html>
<html> <html>
<head> <head>
<title>New Script</title> <title>New Script</title>
</head> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<body> <style>button{margin-top:1em}.main{font-family:'Open Sans', sans-serif;margin:2%}textarea{width:100%;max-width:80vh;margin-bottom:1em}#ducky-input{height:50vh}</style>
<form action="/new" method="POST"> </head>
Script Name<br> <body>
<textarea rows="1" cols="60" name="scriptName"></textarea> <div class="main">
Script<br> <form action="/new" method="POST">
<textarea rows="5" cols="60" name="scriptData"></textarea> <p>New Script:</p>
<br/> <textarea rows="1" name="scriptName" placeholder="script name"></textarea><br>
<input type="submit" value="submit"/> <textarea id="ducky-input" rows="5" name="scriptData" placeholder="script"></textarea>
</form> <br><input type="submit" value="Submit"/>
<br> </form>
<a href="/ducky">Home</a> <a href="/ducky"><button>Go Back</button></a>
</body> </div>
</body>
</html> </html>
""" """
response_html = """<!DOCTYPE html> response_html = """<!DOCTYPE html>
<html> <html>
<head> <title>Pico W Ducky</title> </head> <head>
<body> <h1>Pico W Ducky</h1> <title>Pico W Ducky</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>button{{margin-top:1em}}body{{font-family:'Open Sans', sans-serif;margin:2%}}</style>
</head>
<body>
<h1>Pico W Ducky</h1>
{} {}
<br> <br><a href="/ducky"><button>Home</button></a>
<a href="/ducky">Home</a>
</body> </body>
</html> </html>
""" """
newrow_html = "<tr><td>{}</td><td><a href='/edit/{}'>Edit</a> / <a href='/run/{}'>Run</a></tr>" newrow_html = "<tr><td>{}</td><td><a href='/edit/{}'>Edit</a> / <a href='/delete/{}'>Delete</a> / <a href='/run/{}'>Run</a></tr>"
def setPayload(payload_number): def setPayload(payload_number):
if(payload_number == 1): if(payload_number == 1):
@@ -94,7 +103,7 @@ def ducky_main(request):
for f in files: for f in files:
if ('.dd' in f) == True: if ('.dd' in f) == True:
payloads.append(f) payloads.append(f)
newrow = newrow_html.format(f,f,f) newrow = newrow_html.format(f,f,f,f)
#print(newrow) #print(newrow)
rows = rows + newrow rows = rows + newrow
@@ -172,8 +181,8 @@ def write_script(request, filename):
textbuffer = form_data['scriptData'] textbuffer = form_data['scriptData']
textbuffer = cleanup_text(textbuffer) textbuffer = cleanup_text(textbuffer)
#print(textbuffer) #print(textbuffer)
for line in textbuffer: for line in textbuffer.splitlines():
f.write(line) f.write(line + '\n')
f.close() f.close()
storage.remount("/",readonly=True) storage.remount("/",readonly=True)
response = response_html.format("Wrote script " + filename) response = response_html.format("Wrote script " + filename)
@@ -193,17 +202,28 @@ def write_new_script(request):
form_data[key] = value form_data[key] = value
#print(form_data) #print(form_data)
filename = form_data['scriptName'] filename = form_data['scriptName']
if ".dd" not in filename:
filename = filename + ".dd"
textbuffer = form_data['scriptData'] textbuffer = form_data['scriptData']
textbuffer = cleanup_text(textbuffer) textbuffer = cleanup_text(textbuffer)
storage.remount("/",readonly=False) storage.remount("/",readonly=False)
f = open(filename,"w",encoding='utf-8') f = open(filename,"w",encoding='utf-8')
for line in textbuffer: for line in textbuffer.splitlines():
f.write(line) f.write(line + '\n')
f.close() f.close()
storage.remount("/",readonly=True) storage.remount("/",readonly=True)
response = response_html.format("Wrote script " + filename) response = response_html.format("Wrote script " + filename)
return("200 OK",[('Content-Type', 'text/html')], response) return("200 OK",[('Content-Type', 'text/html')], response)
@web_app.route("/delete/<filename>")
def delete(request, filename):
print("Deleting ", filename)
storage.remount("/",readonly=False)
os.remove(filename)
response = response_html.format("Deleted script " + filename)
storage.remount("/",readonly=True)
return("200 OK",[('Content-Type', 'text/html')], response)
@web_app.route("/run/<filename>") @web_app.route("/run/<filename>")
def run_script(request, filename): def run_script(request, filename):
print("run_script ", filename) print("run_script ", filename)
@@ -232,7 +252,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 +260,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)