Skip to content

Commit b99ddf3

Browse files
authored
Pico W Support (#137)
* Refactor code to support Pico W board * Updates for Pico W support * Update based on changes in CircuitPython v8.0.0 beta 6 * Updated bundle download to 8.x, fixed a few typos * Updating documentation for web interface * Update support for CircuitPython 8.0.0, fix many bugs * Document multiple payload options * Update copyright dates * Fixed typo
1 parent 99900ed commit b99ddf3

File tree

6 files changed

+703
-80
lines changed

6 files changed

+703
-80
lines changed

README.md

+47-14
Original file line numberDiff line numberDiff line change
@@ -22,49 +22,82 @@ Install and have your USB Rubber Ducky working in less than 5 minutes.
2222

2323
1. Clone the repo to get a local copy of the files. `git clone https://github.com/dbisu/pico-ducky.git`
2424

25-
2. Download [CircuitPython for the Raspberry Pi Pico](https://circuitpython.org/board/raspberry_pi_pico/). *Updated to 7.0.0
25+
2. Download [CircuitPython for the Raspberry Pi Pico](https://circuitpython.org/board/raspberry_pi_pico/). *Updated to 8.0.0
26+
Download [CircuitPython for the Raspberry Pi Pico W](https://circuitpython.org/board/raspberry_pi_pico_w/). *Updated to 8.0.0
2627

2728
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`.
2829

2930
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`.
3031

31-
5. Download `adafruit-circuitpython-bundle-7.x-mpy-YYYYMMDD.zip` [here](https://github.com/adafruit/Adafruit_CircuitPython_Bundle/releases/latest) and extract it outside the device.
32+
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.
3233

3334
6. Navigate to `lib` in the recently extracted folder and copy `adafruit_hid` to the `lib` folder on your Raspberry Pi Pico.
3435

3536
7. Copy `adafruit_debouncer.mpy` and `adafruit_ticks.mpy` to the `lib` folder on your Raspberry Pi Pico.
3637

3738
8. Copy `asyncio` to the `lib` folder on your Pico.
3839

39-
9. Copy `boot.py` from your clone to the root of your Pico.
40+
9. Copy `adafruit_wsgi` to the `lib` folder on your Pico.
4041

41-
10. Copy `duckyinpython.py` as `code.py` in the root of the Raspberry Pi Pico, overwriting the previous file.
42-
Linux: `cp duckyinpython.py </path/to/pico/code.py`
42+
10. Copy `boot.py` from your clone to the root of your Pico.
4343

44-
11. 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/duckyscript-tm-quick-reference) and save it as `payload.dd` in the Pico.
44+
11. Copy `duckyinpython.py`, `code.py`, `webapp.py`, `wsgiserver.py` to the root folder of the Pico.
4545

46-
12. 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.
46+
12. 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 DuckScript 1.0, not 3.0.
47+
48+
13. 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.
49+
50+
14. **Please note:** by default Pico W will not show as a USB drive
51+
52+
### Pico W Web Service
53+
The Pico W AP defaults to ip address `192.168.4.1`. You should be able to find the webservice at `http://192.168.4.1:80`
54+
55+
The following endpoints are available on the webservice:
56+
```
57+
/
58+
/new
59+
/ducky
60+
/edit/<filename>
61+
/write/<filename>
62+
/run/<filename>
63+
```
64+
65+
API endpoints
66+
```
67+
/api/run/<filenumber>
68+
```
4769

4870
### Setup mode
4971

5072
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.
51-
The easiest way to so is by using a jumper wire between those pins as seen bellow.
73+
The easiest way to do so is by using a jumper wire between those pins as seen bellow.
5274

5375
![Setup mode with a jumper](images/setup-mode.png)
5476

5577
### USB enable/disable mode
5678

5779
If you need the pico-ducky to not show up as a USB mass storage device for stealth, follow these instructions.
58-
Enter setup mode.
59-
Copy your payload script to the pico-ducky.
60-
Disconnect the pico from your host PC.
61-
Connect a jumper wire between pin 18 (`GND`) and pin 20 (`GPIO15`).
80+
- Enter setup mode.
81+
- Copy your payload script to the pico-ducky.
82+
- Disconnect the pico from your host PC.
83+
- Connect a jumper wire between pin 18 (`GND`) and pin 20 (`GPIO15`).
6284
This will prevent the pico-ducky from showing up as a USB drive when plugged into the target computer.
63-
Remove the jumper and reconnect to your PC to reprogram.
64-
The default mode is USB mass storage enabled.
85+
- Remove the jumper and reconnect to your PC to reprogram.
86+
87+
Pico: The default mode is USB mass storage enabled.
88+
Pico W: The default mode is USB mass storage **disabled**
6589

6690
![USB enable/disable mode](images/usb-boot-mode.png)
6791

92+
### Multiple payloads
93+
94+
Multiple payloads can be stored on the Pico and Pico W.
95+
To select a payload, ground one of these pins:
96+
- GP4 - payload.dd
97+
- GP5 - payload2.dd
98+
- GP10 - payload3.dd
99+
- GP11 - payload4.dd
100+
68101
### Changing Keyboard Layouts
69102

70103
Copied from [Neradoc/Circuitpython_Keyboard_Layouts](https://github.com/Neradoc/Circuitpython_Keyboard_Layouts/blob/main/PICODUCKY.md)

boot.py

+28-3
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,38 @@
1+
# License : GPLv2.0
2+
# copyright (c) 2023 Dave Bailey
3+
# Author: Dave Bailey (dbisu, @daveisu)
4+
# Pico and Pico W board support
5+
16
from board import *
7+
import board
28
import digitalio
39
import storage
410

5-
noStorageStatus = False
11+
noStorage = False
612
noStoragePin = digitalio.DigitalInOut(GP15)
713
noStoragePin.switch_to_input(pull=digitalio.Pull.UP)
8-
noStorageStatus = not noStoragePin.value
14+
noStorageStatus = noStoragePin.value
15+
16+
# If GP15 is not connected, it will default to being pulled high (True)
17+
# If GP is connected to GND, it will be low (False)
18+
19+
# Pico:
20+
# GP15 not connected == USB visible
21+
# GP15 connected to GND == USB not visible
22+
23+
# Pico W:
24+
# GP15 not connected == USB NOT visible
25+
# GP15 connected to GND == USB visible
26+
27+
if(board.board_id == 'raspberry_pi_pico'):
28+
# On Pi Pico, default to USB visible
29+
noStorage = not noStorageStatus
30+
elif(board.board_id == 'raspberry_pi_pico_w'):
31+
# on Pi Pico W, default to USB hidden by default
32+
# so webapp can access storage
33+
noStorage = noStorageStatus
934

10-
if(noStorageStatus == True):
35+
if(noStorage == True):
1136
# don't show USB drive to host PC
1237
storage.disable_usb_drive()
1338
print("Disabling USB drive")

code.py

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
# License : GPLv2.0
2+
# copyright (c) 2023 Dave Bailey
3+
# Author: Dave Bailey (dbisu, @daveisu)
4+
# Pico and Pico W board support
5+
6+
7+
import usb_hid
8+
from adafruit_hid.keyboard import Keyboard
9+
10+
# comment out these lines for non_US keyboards
11+
from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout
12+
from adafruit_hid.keycode import Keycode
13+
14+
# uncomment these lines for non_US keyboards
15+
# replace LANG with appropriate language
16+
#from keyboard_layout_win_LANG import KeyboardLayout
17+
#from keycode_win_LANG import Keycode
18+
19+
import supervisor
20+
21+
22+
import time
23+
import digitalio
24+
from board import *
25+
import board
26+
from duckyinpython import *
27+
if(board.board_id == 'raspberry_pi_pico_w'):
28+
import wifi
29+
from webapp import *
30+
31+
32+
# sleep at the start to allow the device to be recognized by the host computer
33+
time.sleep(.5)
34+
35+
def startWiFi():
36+
import ipaddress
37+
# Get wifi details and more from a secrets.py file
38+
try:
39+
from secrets import secrets
40+
except ImportError:
41+
print("WiFi secrets are kept in secrets.py, please add them there!")
42+
raise
43+
44+
print("Connect wifi")
45+
#wifi.radio.connect(secrets['ssid'],secrets['password'])
46+
wifi.radio.start_ap(secrets['ssid'],secrets['password'])
47+
48+
HOST = repr(wifi.radio.ipv4_address_ap)
49+
PORT = 80 # Port to listen on
50+
print(HOST,PORT)
51+
52+
# turn off automatically reloading when files are written to the pico
53+
#supervisor.disable_autoreload()
54+
supervisor.runtime.autoreload = False
55+
56+
if(board.board_id == 'raspberry_pi_pico'):
57+
led = pwmio.PWMOut(board.LED, frequency=5000, duty_cycle=0)
58+
elif(board.board_id == 'raspberry_pi_pico_w'):
59+
led = digitalio.DigitalInOut(board.LED)
60+
led.switch_to_output()
61+
62+
63+
progStatus = False
64+
progStatus = getProgrammingStatus()
65+
print("progStatus", progStatus)
66+
if(progStatus == False):
67+
print("Finding payload")
68+
# not in setup mode, inject the payload
69+
payload = selectPayload()
70+
print("Running ", payload)
71+
runScript(payload)
72+
73+
print("Done")
74+
else:
75+
print("Update your payload")
76+
77+
led_state = False
78+
79+
async def main_loop():
80+
global led,button1
81+
82+
button_task = asyncio.create_task(monitor_buttons(button1))
83+
if(board.board_id == 'raspberry_pi_pico_w'):
84+
pico_led_task = asyncio.create_task(blink_pico_w_led(led))
85+
print("Starting Wifi")
86+
startWiFi()
87+
print("Starting Web Service")
88+
webservice_task = asyncio.create_task(startWebService())
89+
await asyncio.gather(pico_led_task, button_task, webservice_task)
90+
else:
91+
pico_led_task = asyncio.create_task(blink_pico_led(led))
92+
await asyncio.gather(pico_led_task, button_task)
93+
94+
asyncio.run(main_loop())

duckyinpython.py

+34-63
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
# License : GPLv2.0
2-
# copyright (c) 2021 Dave Bailey
2+
# copyright (c) 2023 Dave Bailey
33
# Author: Dave Bailey (dbisu, @daveisu)
44

5+
6+
import time
7+
import digitalio
8+
from digitalio import DigitalInOut, Pull
9+
from adafruit_debouncer import Debouncer
10+
import board
11+
from board import *
12+
import pwmio
13+
import asyncio
514
import usb_hid
615
from adafruit_hid.keyboard import Keyboard
716

@@ -14,34 +23,6 @@
1423
#from keyboard_layout_win_LANG import KeyboardLayout
1524
#from keycode_win_LANG import Keycode
1625

17-
import supervisor
18-
19-
import time
20-
import digitalio
21-
from digitalio import DigitalInOut, Pull
22-
from adafruit_debouncer import Debouncer
23-
from board import *
24-
import pwmio
25-
import asyncio
26-
27-
led = pwmio.PWMOut(LED, frequency=5000, duty_cycle=0)
28-
29-
def led_pwm_up(led):
30-
for i in range(100):
31-
# PWM LED up and down
32-
if i < 50:
33-
led.duty_cycle = int(i * 2 * 65535 / 100) # Up
34-
time.sleep(0.01)
35-
def led_pwm_down(led):
36-
for i in range(100):
37-
# PWM LED up and down
38-
if i >= 50:
39-
led.duty_cycle = 65535 - int((i - 50) * 2 * 65535 / 100) # Down
40-
time.sleep(0.01)
41-
42-
# led = digitalio.DigitalInOut(LED)
43-
# led.direction = digitalio.Direction.OUTPUT
44-
4526
duckyCommands = {
4627
'WINDOWS': Keycode.WINDOWS, 'GUI': Keycode.GUI,
4728
'APP': Keycode.APPLICATION, 'MENU': Keycode.APPLICATION, 'SHIFT': Keycode.SHIFT,
@@ -123,13 +104,8 @@ def parseLine(line):
123104
kbd = Keyboard(usb_hid.devices)
124105
layout = KeyboardLayout(kbd)
125106

126-
# turn off automatically reloading when files are written to the pico
127-
supervisor.disable_autoreload()
128107

129-
# sleep at the start to allow the device to be recognized by the host computer
130-
time.sleep(.5)
131108

132-
led_pwm_up(led)
133109

134110
#init button
135111
button1_pin = DigitalInOut(GP22) # defaults to input
@@ -191,7 +167,6 @@ def selectPayload():
191167
payload3State = not payload3Pin.value
192168
payload4State = not payload4Pin.value
193169

194-
195170
if(payload1State == True):
196171
payload = "payload.dd"
197172

@@ -209,17 +184,22 @@ def selectPayload():
209184
# default to payload1
210185
payload = "payload.dd"
211186

212-
213187
return payload
214188

189+
async def blink_led(led):
190+
print("Blink")
191+
if(board.board_id == 'raspberry_pi_pico'):
192+
blink_pico_led(led)
193+
elif(board.board_id == 'raspberry_pi_pico_w'):
194+
blink_pico_w_led(led)
215195

216196
async def blink_pico_led(led):
217197
print("starting blink_pico_led")
218198
led_state = False
219199
while True:
220200
if led_state:
221201
#led_pwm_up(led)
222-
print("led up")
202+
#print("led up")
223203
for i in range(100):
224204
# PWM LED up and down
225205
if i < 50:
@@ -228,7 +208,7 @@ async def blink_pico_led(led):
228208
led_state = False
229209
else:
230210
#led_pwm_down(led)
231-
print("led down")
211+
#print("led down")
232212
for i in range(100):
233213
# PWM LED up and down
234214
if i >= 50:
@@ -237,6 +217,22 @@ async def blink_pico_led(led):
237217
led_state = True
238218
await asyncio.sleep(0)
239219

220+
async def blink_pico_w_led(led):
221+
print("starting blink_pico_w_led")
222+
led_state = False
223+
while True:
224+
if led_state:
225+
#print("led on")
226+
led.value = 1
227+
await asyncio.sleep(0.5)
228+
led_state = False
229+
else:
230+
#print("led off")
231+
led.value = 0
232+
await asyncio.sleep(0.5)
233+
led_state = True
234+
await asyncio.sleep(0.5)
235+
240236
async def monitor_buttons(button1):
241237
global inBlinkeyMode, inMenu, enableRandomBeep, enableSirenMode,pixel
242238
print("starting monitor_buttons")
@@ -266,28 +262,3 @@ async def monitor_buttons(button1):
266262
button1Down = False
267263

268264
await asyncio.sleep(0)
269-
270-
271-
272-
progStatus = False
273-
progStatus = getProgrammingStatus()
274-
275-
if(progStatus == False):
276-
# not in setup mode, inject the payload
277-
payload = selectPayload()
278-
print("Running ", payload)
279-
runScript(payload)
280-
281-
print("Done")
282-
else:
283-
print("Update your payload")
284-
285-
led_state = False
286-
287-
async def main_loop():
288-
global led,button1
289-
pico_led_task = asyncio.create_task(blink_pico_led(led))
290-
button_task = asyncio.create_task(monitor_buttons(button1))
291-
await asyncio.gather(pico_led_task, button_task)
292-
293-
asyncio.run(main_loop())

0 commit comments

Comments
 (0)