From e3c8ad909c2012ac1b88b9dd89ba595871547c6a Mon Sep 17 00:00:00 2001 From: Tzur Soffer <103438808+TzurSoffer@users.noreply.github.com> Date: Mon, 23 Dec 2024 22:35:56 -0800 Subject: [PATCH 01/10] Added HOLD and RELEASE --- duckyinpython.py | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/duckyinpython.py b/duckyinpython.py index 11fb858..03bc092 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -50,6 +50,7 @@ variables = {} functions = {} +heldKeys = set() def convertLine(line): newline = [] @@ -86,6 +87,24 @@ def parseLine(line, script_lines): if(line[0:3] == "REM"): # ignore ducky script comments pass + elif line.startswith("HOLD"): + # HOLD command to press and hold a key + key = line[5:].strip().upper() + commandKeycode = duckyCommands.get(key, None) + if commandKeycode: + kbd.press(commandKeycode) + heldKeys.add(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 = duckyCommands.get(key, None) + if commandKeycode: + kbd.release(commandKeycode) + heldKeys.discard(commandKeycode) + else: + print(f"Unknown key to RELEASE: <{key}>") elif(line[0:5] == "DELAY"): time.sleep(float(line[6:])/1000) elif(line[0:6] == "STRING"): From 5ee509cb646391f830a55512df9e1f43e3ffd511 Mon Sep 17 00:00:00 2001 From: Tzur Soffer <103438808+TzurSoffer@users.noreply.github.com> Date: Tue, 24 Dec 2024 07:22:49 -0800 Subject: [PATCH 02/10] Update removed unnecessary heldKeys var --- duckyinpython.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/duckyinpython.py b/duckyinpython.py index 03bc092..f5ff605 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -50,7 +50,6 @@ variables = {} functions = {} -heldKeys = set() def convertLine(line): newline = [] @@ -93,7 +92,6 @@ def parseLine(line, script_lines): commandKeycode = duckyCommands.get(key, None) if commandKeycode: kbd.press(commandKeycode) - heldKeys.add(commandKeycode) else: print(f"Unknown key to HOLD: <{key}>") elif line.startswith("RELEASE"): @@ -102,7 +100,6 @@ def parseLine(line, script_lines): commandKeycode = duckyCommands.get(key, None) if commandKeycode: kbd.release(commandKeycode) - heldKeys.discard(commandKeycode) else: print(f"Unknown key to RELEASE: <{key}>") elif(line[0:5] == "DELAY"): From c6580d0f70f0a877d0c1c0daaf33314cdffd8453 Mon Sep 17 00:00:00 2001 From: Tzur Soffer <103438808+TzurSoffer@users.noreply.github.com> Date: Fri, 27 Dec 2024 18:59:38 -0800 Subject: [PATCH 03/10] Fixed HOLD being released after key press --- duckyinpython.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/duckyinpython.py b/duckyinpython.py index f5ff605..ba64423 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -76,7 +76,8 @@ def runScriptLine(line): line = convertLine(line) for k in line: kbd.press(k) - kbd.release_all() + for k in reversed(line): + kbd.release(k) def sendString(line): layout.write(line) From 30927b2b5c8695d4d1a97ee1bc4c832671c6458d Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Tue, 31 Dec 2024 07:01:35 -0800 Subject: [PATCH 04/10] added REM_BLOCK, STRING Block, and STRINGLN Block --- duckyinpython.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/duckyinpython.py b/duckyinpython.py index ba64423..ac87044 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -87,6 +87,10 @@ def parseLine(line, script_lines): if(line[0:3] == "REM"): # ignore ducky script comments pass + elif line[0:9] == "REM_BLOCK": ############################## + line = next(script_lines).strip() + while line != "END_REM": + pass elif line.startswith("HOLD"): # HOLD command to press and hold a key key = line[5:].strip().upper() @@ -105,8 +109,22 @@ def parseLine(line, script_lines): print(f"Unknown key to RELEASE: <{key}>") elif(line[0:5] == "DELAY"): time.sleep(float(line[6:])/1000) + elif line == "STRING": #< string block ############################## + line = next(script_lines) + while line != "END_STRING": + sendString(line) elif(line[0:6] == "STRING"): sendString(line[7:]) + elif line == "STRINGLN": #< stringLN block ############################## + line = next(script_lines) + while line != "END_STRINGLN": + sendString(line) + kbd.press(Keycode.ENTER) + kbd.release(Keycode.ENTER) + elif(line[0:8] == "STRINGLN"): ############################## + sendString(line[9:]) + kbd.press(Keycode.ENTER) + kbd.release(Keycode.ENTER) elif(line[0:5] == "PRINT"): print("[SCRIPT]: " + line[6:]) elif(line[0:6] == "IMPORT"): From 7f43064b99a254d88e10f12691e0622dcfe72b9d Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Tue, 31 Dec 2024 07:08:46 -0800 Subject: [PATCH 05/10] LED + INJECT_MOD --- duckyinpython.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/duckyinpython.py b/duckyinpython.py index ac87044..e2502df 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -84,6 +84,8 @@ def sendString(line): def parseLine(line, script_lines): global defaultDelay, variables, functions line = line.strip() + if line[:10] == "INJECT_MOD": + line = line[11:] if(line[0:3] == "REM"): # ignore ducky script comments pass @@ -138,6 +140,17 @@ def parseLine(line, script_lines): led.value = False else: 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 From 0dbc7b7a7be9c5b4f7cff57815b04b0666c0d377 Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Tue, 31 Dec 2024 07:15:14 -0800 Subject: [PATCH 06/10] add define --- duckyinpython.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/duckyinpython.py b/duckyinpython.py index e2502df..c267a56 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -49,6 +49,7 @@ } variables = {} +defines = {} functions = {} def convertLine(line): @@ -84,6 +85,8 @@ def sendString(line): def parseLine(line, script_lines): global defaultDelay, variables, functions line = line.strip() + for define, value in defines.items(): + line = line.replace(define, value) if line[:10] == "INJECT_MOD": line = line[11:] if(line[0:3] == "REM"): @@ -167,6 +170,12 @@ def parseLine(line, script_lines): elif line.startswith("VAR"): _, var, _, value = line.split() variables[var] = int(value) + 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"): func_name = line.split()[1] functions[func_name] = [] From 92d53510dea618edc42e4fe3ca9424182a72af3c Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Tue, 31 Dec 2024 07:41:19 -0800 Subject: [PATCH 07/10] added all randoms --- duckyinpython.py | 36 +++++++++++++++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/duckyinpython.py b/duckyinpython.py index c267a56..f2ed95a 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -1,9 +1,17 @@ # License : GPLv2.0 # copyright (c) 2023 Dave Bailey # Author: Dave Bailey (dbisu, @daveisu) +# +# TODO: ADD support for the following: +# MATH: = + - * / % ^ +# COMPARISON: == != < > <= >= +# ORDER OF OPERATIONS: () +# LOGICAL: && || +# IF THEN ELSE import re import time +import random import digitalio from digitalio import DigitalInOut, Pull from adafruit_debouncer import Debouncer @@ -48,10 +56,14 @@ } -variables = {} +variables = {"$_RANDOM_MIN": 0, "$_RANDOM_MAX": 65535} defines = {} functions = {} +letters = "abcdefghijklmnopqrstuvwxyz" +numbers = "0123456789" +specialChars = "!@#$%^&*()" + def convertLine(line): newline = [] # print(line) @@ -85,6 +97,7 @@ def sendString(line): def parseLine(line, script_lines): global defaultDelay, variables, functions line = line.strip() + line.replace("$_RANDOM_INT", random.randint(int(variables["$_RANDOM_MIN"]), int(variables["$_RANDOM_MAX"]))) for define, value in defines.items(): line = line.replace(define, value) if line[:10] == "INJECT_MOD": @@ -197,6 +210,27 @@ def parseLine(line, script_lines): for loop_line in loop_code: parseLine(loop_line, {}) variables[var_name] -= 1 + 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 in functions: updated_lines = [] inside_while_block = False From 4cbe136cb1b1ccaf56ce0538ff0d7531aadf5927 Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Tue, 31 Dec 2024 07:52:40 -0800 Subject: [PATCH 08/10] added stop, restart, reset --- duckyinpython.py | 40 +++++++++++++++++++++++++--------------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/duckyinpython.py b/duckyinpython.py index f2ed95a..5fbb2c3 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -231,6 +231,8 @@ def parseLine(line, script_lines): 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 @@ -287,22 +289,30 @@ def runScript(file): global defaultDelay duckyScriptPath = file + restart = True try: - with open(duckyScriptPath, "r", encoding='utf-8') as f: - script_lines = iter(f.readlines()) - previousLine = "" - for line in script_lines: - print(f"runScript: {line}") - - if(line[0:6] == "REPEAT"): - for i in range(int(line[7:])): - #repeat the last command - parseLine(previousLine, script_lines) - time.sleep(float(defaultDelay) / 1000) - else: - parseLine(line, script_lines) - previousLine = line - time.sleep(float(defaultDelay) / 1000) + while restart: + restart = False + with open(duckyScriptPath, "r", encoding='utf-8') as f: + script_lines = iter(f.readlines()) + previousLine = "" + for line in script_lines: + print(f"runScript: {line}") + if(line[0:6] == "REPEAT"): + for i in range(int(line[7:])): + #repeat the last command + parseLine(previousLine, script_lines) + time.sleep(float(defaultDelay) / 1000) + elif line == "RESTART_PAYLOAD": + restart = True + break + elif line == "STOP_PAYLOAD": + restart = False + break + else: + parseLine(line, script_lines) + previousLine = line + time.sleep(float(defaultDelay) / 1000) except OSError as e: print("Unable to open file", file) From c4aaf36b7e61c31e478f9fb4259cc86bfc3eece6 Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Tue, 31 Dec 2024 08:40:34 -0800 Subject: [PATCH 09/10] bugfixes and cleanup --- duckyinpython.py | 44 ++++++++++++++++++++++++-------------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/duckyinpython.py b/duckyinpython.py index 5fbb2c3..d668861 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -8,6 +8,8 @@ # ORDER OF OPERATIONS: () # LOGICAL: && || # IF THEN ELSE +# Add jitter +# Add LED functionality import re import time @@ -95,20 +97,20 @@ def sendString(line): layout.write(line) def parseLine(line, script_lines): - global defaultDelay, variables, functions + global defaultDelay, variables, functions, defines + print(line) line = line.strip() - line.replace("$_RANDOM_INT", random.randint(int(variables["$_RANDOM_MIN"]), int(variables["$_RANDOM_MAX"]))) + line = line.replace("$_RANDOM_INT", str(random.randint(int(variables.get("$_RANDOM_MIN", 0)), int(variables.get("$_RANDOM_MAX", 65535))))) for define, value in defines.items(): line = line.replace(define, value) if line[:10] == "INJECT_MOD": line = line[11:] - if(line[0:3] == "REM"): - # ignore ducky script comments + elif line.startswith("REM_BLOCK"): + while line.startswith("END_REM") == False: + line = next(script_lines).strip() + print(line) + elif(line[0:3] == "REM"): pass - elif line[0:9] == "REM_BLOCK": ############################## - line = next(script_lines).strip() - while line != "END_REM": - pass elif line.startswith("HOLD"): # HOLD command to press and hold a key key = line[5:].strip().upper() @@ -127,22 +129,24 @@ def parseLine(line, script_lines): print(f"Unknown key to RELEASE: <{key}>") elif(line[0:5] == "DELAY"): time.sleep(float(line[6:])/1000) - elif line == "STRING": #< string block ############################## - line = next(script_lines) - while line != "END_STRING": - sendString(line) - elif(line[0:6] == "STRING"): - sendString(line[7:]) - elif line == "STRINGLN": #< stringLN block ############################## - line = next(script_lines) - while line != "END_STRINGLN": + elif line == "STRINGLN": #< stringLN block + line = next(script_lines).strip() + while line.startswith("END_STRINGLN") == False: sendString(line) kbd.press(Keycode.ENTER) kbd.release(Keycode.ENTER) - elif(line[0:8] == "STRINGLN"): ############################## + line = next(script_lines).strip() + elif(line[0:8] == "STRINGLN"): sendString(line[9:]) kbd.press(Keycode.ENTER) kbd.release(Keycode.ENTER) + elif line == "STRING": #< string block + line = next(script_lines).strip() + while line.startswith("END_STRING") == False: + sendString(line) + line = next(script_lines).strip() + elif(line[0:6] == "STRING"): + sendString(line[7:]) elif(line[0:5] == "PRINT"): print("[SCRIPT]: " + line[6:]) elif(line[0:6] == "IMPORT"): @@ -303,10 +307,10 @@ def runScript(file): #repeat the last command parseLine(previousLine, script_lines) time.sleep(float(defaultDelay) / 1000) - elif line == "RESTART_PAYLOAD": + elif line.startswith("RESTART_PAYLOAD"): restart = True break - elif line == "STOP_PAYLOAD": + elif line.startswith("STOP_PAYLOAD"): restart = False break else: From c61d82dfb590e2d500b55e95a395d1d2ddc3634d Mon Sep 17 00:00:00 2001 From: Tzur Soffer Date: Wed, 1 Jan 2025 16:40:15 -0800 Subject: [PATCH 10/10] added missing keys --- duckyinpython.py | 65 +++++++++++++++++++++++++++++++----------------- 1 file changed, 42 insertions(+), 23 deletions(-) diff --git a/duckyinpython.py b/duckyinpython.py index d668861..8efe4f2 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -23,6 +23,8 @@ import asyncio import usb_hid 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 from adafruit_hid.keyboard_layout_us import KeyboardLayoutUS as KeyboardLayout @@ -33,10 +35,10 @@ #from keyboard_layout_win_LANG import KeyboardLayout #from keycode_win_LANG import Keycode -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, +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, 'LEFT': Keycode.LEFT_ARROW, 'RIGHTARROW': Keycode.RIGHT_ARROW, 'RIGHT': Keycode.RIGHT_ARROW, 'UPARROW': Keycode.UP_ARROW, 'UP': Keycode.UP_ARROW, 'BREAK': Keycode.PAUSE, @@ -45,7 +47,7 @@ '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, + 'BACKSPACE': Keycode.BACKSPACE, 'POWER': Keycode.POWER, '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, @@ -54,8 +56,15 @@ '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, - + '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} @@ -67,32 +76,42 @@ specialChars = "!@#$%^&*()" def convertLine(line): - newline = [] + commands = [] # print(line) # loop on each key - the filter removes empty values for key in filter(None, line.split(" ")): key = key.upper() # 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 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): # if it's in the Keycode module, use it (allows any valid keycode) - newline.append(getattr(Keycode, key)) + commands.append(getattr(Keycode, key)) else: # if it's not a known key name, show the error for diagnosis print(f"Unknown key: <{key}>") - # print(newline) - return newline + # print(commands) + return commands def runScriptLine(line): - if isinstance(line, str): - line = convertLine(line) - for k in line: - kbd.press(k) - for k in reversed(line): - kbd.release(k) + keys = convertLine(line) + for k in keys: + 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): layout.write(line) @@ -114,7 +133,7 @@ def parseLine(line, script_lines): elif line.startswith("HOLD"): # HOLD command to press and hold a key key = line[5:].strip().upper() - commandKeycode = duckyCommands.get(key, None) + commandKeycode = duckyKeys.get(key, None) if commandKeycode: kbd.press(commandKeycode) else: @@ -122,7 +141,7 @@ def parseLine(line, script_lines): elif line.startswith("RELEASE"): # RELEASE command to release a held key key = line[8:].strip().upper() - commandKeycode = duckyCommands.get(key, None) + commandKeycode = duckyKeys.get(key, None) if commandKeycode: kbd.release(commandKeycode) else: @@ -254,10 +273,10 @@ def parseLine(line, script_lines): elif not (func_line.startswith("END_WHILE") or func_line.startswith("WHILE")): parseLine(func_line, iter(functions[line])) else: - newScriptLine = convertLine(line) - runScriptLine(newScriptLine) + runScriptLine(line) kbd = Keyboard(usb_hid.devices) +consumerControl = ConsumerControl(usb_hid.devices) layout = KeyboardLayout(kbd)