diff --git a/duckyinpython.py b/duckyinpython.py index 9a92cd6..a141bdd 100644 --- a/duckyinpython.py +++ b/duckyinpython.py @@ -1,9 +1,19 @@ # 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 +# Add jitter +# Add LED functionality import re import time +import random import digitalio from digitalio import DigitalInOut, Pull from adafruit_debouncer import Debouncer @@ -13,6 +23,8 @@ import pwmio 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 @@ -23,10 +35,10 @@ from adafruit_hid.keycode import Keycode #from keyboard_layout_win_LANG import KeyboardLayout as 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, @@ -35,7 +47,7 @@ duckyCommands = { '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, @@ -44,50 +56,114 @@ duckyCommands = { '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 = {} +variables = {"$_RANDOM_MIN": 0, "$_RANDOM_MAX": 65535} +defines = {} functions = {} +letters = "abcdefghijklmnopqrstuvwxyz" +numbers = "0123456789" +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) - kbd.release_all() + 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) def parseLine(line, script_lines): - global defaultDelay, variables, functions + global defaultDelay, variables, functions, defines + print(line) line = line.strip() - if(line[0:3] == "REM"): - # ignore ducky script comments + 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:] + 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.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"): time.sleep(float(line[6:])/1000) + 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) + 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"): @@ -103,6 +179,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 @@ -119,6 +206,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] = [] @@ -140,6 +233,29 @@ 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 == "RESET": + kbd.release_all() elif line in functions: updated_lines = [] inside_while_block = False @@ -157,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) @@ -196,22 +312,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.startswith("RESTART_PAYLOAD"): + restart = True + break + elif line.startswith("STOP_PAYLOAD"): + restart = False + break + else: + parseLine(line, script_lines) + previousLine = line + time.sleep(float(defaultDelay) / 1000) except OSError as e: print("Unable to open file", file)