1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
|
import os
import subprocess
from shutil import which
from termcolor import colored
import sys
import urllib.request
import zipfile
# Needed for parser
class Keycode:A=4;B=5;C=6;D=7;E=8;F=9;G=10;H=11;I=12;J=13;K=14;L=15;M=16;N=17;O=18;P=19;Q=20;R=21;S=22;T=23;U=24;V=25;W=26;X=27;Y=28;Z=29;ONE=30;TWO=31;THREE=32;FOUR=33;FIVE=34;SIX=35;SEVEN=36;EIGHT=37;NINE=38;ZERO=39;ENTER=40;RETURN=ENTER;ESCAPE=41;BACKSPACE=42;TAB=43;SPACEBAR=44;SPACE=SPACEBAR;MINUS=45;EQUALS=46;LEFT_BRACKET=47;RIGHT_BRACKET=48;BACKSLASH=49;POUND=50;SEMICOLON=51;QUOTE=52;GRAVE_ACCENT=53;COMMA=54;PERIOD=55;FORWARD_SLASH=56;CAPS_LOCK=57;F1=58;F2=59;F3=60;F4=61;F5=62;F6=63;F7=64;F8=65;F9=66;F10=67;F11=68;F12=69;PRINT_SCREEN=70;SCROLL_LOCK=71;PAUSE=72;INSERT=73;HOME=74;PAGE_UP=75;DELETE=76;END=77;PAGE_DOWN=78;RIGHT_ARROW=79;LEFT_ARROW=80;DOWN_ARROW=81;UP_ARROW=82;KEYPAD_NUMLOCK=83;KEYPAD_FORWARD_SLASH=84;KEYPAD_ASTERISK=85;KEYPAD_MINUS=86;KEYPAD_PLUS=87;KEYPAD_ENTER=88;KEYPAD_ONE=89;KEYPAD_TWO=90;KEYPAD_THREE=91;KEYPAD_FOUR=92;KEYPAD_FIVE=93;KEYPAD_SIX=94;KEYPAD_SEVEN=95;KEYPAD_EIGHT=96;KEYPAD_NINE=97;KEYPAD_ZERO=98;KEYPAD_PERIOD=99;KEYPAD_BACKSLASH=100;APPLICATION=101;POWER=102;KEYPAD_EQUALS=103;F13=104;F14=105;F15=106;F16=107;F17=108;F18=109;F19=110;F20=111;F21=112;F22=113;F23=114;F24=115;LEFT_CONTROL=224;CONTROL=LEFT_CONTROL;LEFT_SHIFT=225;SHIFT=LEFT_SHIFT;LEFT_ALT=226;ALT=LEFT_ALT;OPTION=ALT;LEFT_GUI=227;GUI=LEFT_GUI;WINDOWS=GUI;COMMAND=GUI;RIGHT_CONTROL=228;RIGHT_SHIFT=229;RIGHT_ALT=230;RIGHT_GUI=231
duckyCommands={'WINDOWS':Keycode.WINDOWS,'GUI':Keycode.GUI,'APP':Keycode.APPLICATION,'MENU':Keycode.APPLICATION,'SHIFT':Keycode.SHIFT,'ALT':Keycode.ALT,'CONTROL':Keycode.CONTROL,'CTRL':Keycode.CONTROL,'DOWNARROW':Keycode.DOWN_ARROW,'DOWN':Keycode.DOWN_ARROW,'LEFTARROW':Keycode.LEFT_ARROW,'LEFT':Keycode.LEFT_ARROW,'RIGHTARROW':Keycode.RIGHT_ARROW,'RIGHT':Keycode.RIGHT_ARROW,'UPARROW':Keycode.UP_ARROW,'UP':Keycode.UP_ARROW,'BREAK':Keycode.PAUSE,'PAUSE':Keycode.PAUSE,'CAPSLOCK':Keycode.CAPS_LOCK,'DELETE':Keycode.DELETE,'END':Keycode.END,'ESC':Keycode.ESCAPE,'ESCAPE':Keycode.ESCAPE,'HOME':Keycode.HOME,'INSERT':Keycode.INSERT,'NUMLOCK':Keycode.KEYPAD_NUMLOCK,'PAGEUP':Keycode.PAGE_UP,'PAGEDOWN':Keycode.PAGE_DOWN,'PRINTSCREEN':Keycode.PRINT_SCREEN,'ENTER':Keycode.ENTER,'SCROLLLOCK':Keycode.SCROLL_LOCK,'SPACE':Keycode.SPACE,'TAB':Keycode.TAB,'BACKSPACE':Keycode.BACKSPACE,'A':Keycode.A,'B':Keycode.B,'C':Keycode.C,'D':Keycode.D,'E':Keycode.E,'F':Keycode.F,'G':Keycode.G,'H':Keycode.H,'I':Keycode.I,'J':Keycode.J,'K':Keycode.K,'L':Keycode.L,'M':Keycode.M,'N':Keycode.N,'O':Keycode.O,'P':Keycode.P,'Q':Keycode.Q,'R':Keycode.R,'S':Keycode.S,'T':Keycode.T,'U':Keycode.U,'V':Keycode.V,'W':Keycode.W,'X':Keycode.X,'Y':Keycode.Y,'Z':Keycode.Z,'F1':Keycode.F1,'F2':Keycode.F2,'F3':Keycode.F3,'F4':Keycode.F4,'F5':Keycode.F5,'F6':Keycode.F6,'F7':Keycode.F7,'F8':Keycode.F8,'F9':Keycode.F9,'F10':Keycode.F10,'F11':Keycode.F11,'F12':Keycode.F12}
final = []
def init():
if os.path.isdir('circuitpython'):
pass
else:
print(colored('[+]> It looks like the circuitpython code is not downloaded yet, I will download it for you.', 'green'))
urllib.request.urlretrieve('https://github.com/compromyse/picoDucky/releases/latest/download/circuitpython.zip', 'circuitpython.zip')
with zipfile.ZipFile('circuitpython.zip', 'r') as h:
h.extractall('.')
# Dependancy check
def depcheck():
checklist = {}
# Returns None if docker is not present
checklist['docker'] = which('docker')
checklist['circuitpython'] = os.path.isdir('circuitpython')
return checklist
# Build image
def buildimage():
# Get ready to build
os.chdir('circuitpython')
# Build the image
print(colored('[+]> Building image, this may take a few minutes..', 'green'))
subprocess.call('docker build -t picoducky .', shell=True)
print("Done!")
# Check if image already exists
def imagecheck():
if subprocess.check_output('docker images -q picoducky 2> /dev/null', shell=True):
return True
else:
return False
def convertLine(line):
newline = []
for key in filter(None, line.split(" ")):
key = key.upper()
command_keycode = duckyCommands.get(key, None)
if command_keycode is not None:
newline.append(command_keycode)
elif hasattr(Keycode, key):
newline.append(getattr(Keycode, key))
else:
print(colored(f"Unknown key: \"{key}\"", 'red'))
sys.exit(1)
return newline
def runScriptLine(line):
for k in line:
final.append(f"kbd.press({k})")
final.append("kbd.release_all()")
def parseLine(line):
global defaultDelay
if(line[0:3] == "REM"):
# ignore ducky script comments
pass
elif(line[0:5] == "DELAY"):
final.append(f'time.sleep({int(line[6:])})')
elif(line[0:6] == "STRING"):
final.append(f'layout.write(\'{line[7:]}\')')
else:
newScriptLine = convertLine(line)
runScriptLine(newScriptLine)
# Final docker section
def dockersection():
# Build image, skip build if image is already built
if imagecheck():
print(colored("[+]> Looks like the Docker image is already built, I will skip that step.", 'green'))
else:
if os.path.isdir('circuitpython'):
print(colored('[+]> Circuitpython directory is present, build can continue.', 'green'))
else:
print(colored('[-]> Circuitpython directory is absent, build cannot continue. Exitting...', 'red'))
sys.exit(1)
buildimage()
# Run container and start build
subprocess.call("docker rm picoduckyrun >/dev/null 2>/dev/null", shell=True)
print(colored('[+]> Everything went fine, starting firmware build..', 'green'))
subprocess.call('docker run --name picoduckyrun --mount type=bind,source="$(pwd)",dst="/mounted" picoducky', shell=True)
# Copy UF2 file
print(colored('[+]> Looks like the build succeeded, copying the file here...','green'))
subprocess.call("docker cp picoduckyrun:/circuitpython/firmware.uf2 firmware.uf2", shell=True)
# Remove container before exitting
subprocess.call("docker rm picoduckyrun >/dev/null 2>/dev/null", shell=True)
def duckyscriptsection():
try:
with open('payload.ds', 'r') as h:
lines = [line.rstrip() for line in h]
except FileNotFoundError:
print(colored('[-]> File "payload.ds" not found, please create it before running the script.', 'red'))
sys.exit(1)
for line in lines:
parseLine(line.replace("'", "\\'"))
print(colored('[+]> Creation of payload successful, script can continue.', 'green'))
with open('code.py', 'w') as h:
h.write('# Made by compromyse: https://compromyse.tk/\n')
h.write('import usb_hid\n')
h.write('from adafruit_hid.keyboard import Keyboard\n')
h.write('from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout\n')
h.write('import time\n')
h.write('import board\n')
h.write('import digitalio\n')
h.write('led = digitalio.DigitalInOut(board.LED)\n')
h.write('led.direction = digitalio.Direction.OUTPUT\n')
h.write('kbd = Keyboard(usb_hid.devices)\n')
h.write('layout = KeyboardLayout(kbd)\n')
h.write('time.sleep(1)\n')
h.write('led.value = True\n')
for line in final:
h.write(f'{line}\n')
h.write('led.value = False')
def cleanup():
try:
os.remove('code.py')
except FileNotFoundError:
pass
def main():
init()
duckyscriptsection()
dockersection()
cleanup()
if __name__ == "__main__":
main()
|