Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added duckyScript 3.0 functionality #300

Merged
merged 10 commits into from
Jan 3, 2025
202 changes: 163 additions & 39 deletions duckyinpython.py
Original file line number Diff line number Diff line change
@@ -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
Expand All @@ -13,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
Expand All @@ -23,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,
Expand All @@ -35,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,
Expand All @@ -44,50 +56,114 @@
'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"):
Expand All @@ -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
Expand All @@ -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] = []
Expand All @@ -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
Expand All @@ -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)


Expand Down Expand Up @@ -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)

Expand Down