diff --git a/README.md b/README.md index 4e95dcb..3c8401c 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,15 @@

DuckHunter

Prevent RubberDucky (or other keystroke injection) attacks

-
+

Try Out the new setup GUI it helps you to setup the software and we have just released a new feature that allows you to run the script every time your computer starts automatically

-**Read this program's postmortem at my [blog](http://konukoii.com/blog/2016/10/26/duckhunting-stopping-automated-keystroke-injection-attacks/)** -

Intro

+ +![](https://raw.githubusercontent.com/kai9987kai/kai9987kai.github.io/master/screenshot.PNG) + + +**Read this program's postmortem at my [blog](http://konukoii.com/blog/2016/10/26/duckhunting-stopping-automated-keystroke-injection-attacks/)** +

Intro

[Rubberduckies](https://hakshop.myshopify.com/products/usb-rubber-ducky-deluxe) are small usb devices that pretend to be usb keyboards and can type on their own at very high speeds. Because most -if not all- OS trust keyboards automatically, it is hard to protect oneself from these attacks. **DuckHunt** is a small efficient script that acts as a daemon consistently monitoring your keyboard usage (right now, speed and selected window) that can catch and prevent a rubber ducky attack. (Technically it helps prevent any type of automated keystroke injection attack, so things like Mousejack injections are also covered.) @@ -44,13 +48,16 @@ - [PyWin32](http://starship.python.net/~skippy/win32/Downloads.html) - [PyHook](https://sourceforge.net/projects/pyhook/) - [Py2Exe](http://py2exe.org/) +- [webbrowser](https://docs.python.org/2/library/webbrowser.html) + +

Advanced Setup

- Step 1. Customize duckhunt.conf variables to your desire - You can customize the password, speed threshold, privacy, etc. -- Step 2. Turn the duckhunt**.py** to a duckhunt**.pyw** so that the console doesn't show up when you run the program +- Step 2. Turn the duckhunt-configurable**.py** to a duckhunt-configurable**.pyw** so that the console doesn't show up when you run the program - Step 3. (opt) Use Py2Exe to create an executable. - Step 4. Run the program. You are now protected from RubberDuckies! diff --git a/duckhunt-configurable.py b/duckhunt-configurable.py new file mode 100644 index 0000000..7ec0fb5 --- /dev/null +++ b/duckhunt-configurable.py @@ -0,0 +1,172 @@ +###################################################### +# DuckHunter # +# Pedro M. Sosa # +# Tool to prevent getting attacked by a rubberducky! # +###################################################### + +from ctypes import * +import pythoncom +import pyHook +import win32clipboard +import win32ui +import os +import shutil +from time import gmtime, strftime +from sys import stdout +import imp +duckhunt = imp.load_source('duckhunt', 'duckhunt.conf') + +##### NOTES ##### +# +# 1. Undestanding Protection Policy: +# - Paranoid: When an attack is detected, lock down any further keypresses until the correct password is entered. (set password in .conf file). Attack will also be logged. +# - Normal : When an attack is detected, keyboard input will temporarily be disallowed. (After it is deemed that the treat is over, keyboard input will be allowed again). Attack will also be logged. +# - Sneaky: When an attacks is detected, a few keys will be dropped (enough to break any attack, make it look as if the attacker messed up.) Attack will also be logged. +# - LogOnly: When an attack is detected, simply log the attack and in no way stop it. + +# 2. How To Use +# - Modify the user configurable vars below. (particularly policy and password) +# - Turn the program into a .pyw to run it as windowless script. +# - (Opt) Use py2exe to build an .exe +# +################# + + + + + +threshold = duckhunt.threshold # Speed Threshold +size = duckhunt.size # Size of history array +policy = duckhunt.policy.lower() # Designate Policy Type +password = duckhunt.password # Password used in Paranoid Mode +allow_auto_type_software = duckhunt.allow_auto_type_software #Allow AutoType Software (eg. KeyPass or LastPass) +################################################################################ +pcounter = 0 # Password Counter (If using password) +speed = 0 # Current Average Keystroke Speed +prevTime = -1 # Previous Keypress Timestamp +i = 0 # History Array Timeslot +intrusion = False # Boolean Flag to be raised in case of intrusion detection +history = [threshold+1] * size # Array for keeping track of average speeds across the last n keypresses +randdrop = duckhunt.randdrop # How often should one drop a letter (in Sneaky mode) +prevWindow = [] # What was the previous window +filename = duckhunt.filename # Filename to save attacks +blacklist = duckhunt.blacklist # Program Blacklist + + + +#Logging the Attack +def log(event): + global prevWindow + + x = open(filename,"a+") + if (prevWindow != event.WindowName): + x.write ("\n[ %s ]\n" % (event.WindowName)) + prevWindow =event.WindowName + if event.Ascii > 32 and event.Ascii < 127: + x.write(chr(event.Ascii)) + else: + x.write("[%s]" % event.Key) + x.close() + return + + +def caught(event): + global intrusion, policy, randdrop + print "Quack! Quack! -- Time to go Duckhunting!" + intrusion = True; + + + #Paranoid Policy + if (policy == "paranoid"): + win32ui.MessageBox("Someone might be trying to inject keystrokes into your computer.\nPlease check your ports or any strange programs running.\nEnter your Password to unlock keyboard.", "KeyInjection Detected",4096) # MB_SYSTEMMODAL = 4096 -- Always on top. + return False; + #Sneaky Policy + elif (policy == "sneaky"): + randdrop += 1 + #Drop every 5th letter + if (randdrop==7): + randdrop = 0; + return False; + else: + return True; + + #Logging Only Policy + elif (policy == "log"): + log(event) + return True; + + + #Normal Policy + log(event) + return False + + +#This is triggered every time a key is pressed +def KeyStroke(event): + + global threshold, policy, password, pcounter + global speed, prevTime, i, history, intrusion,blacklist + + print event.Key; + print event.Message; + print "Injected",event.Injected; + + if (event.Injected != 0 and allow_auto_type_software): + print "Injected by Software" + return True; + + + #If an intrusion was detected and we are password protecting + #Then lockdown any keystroke and until password is entered + if (policy == "paranoid" and intrusion): + print event.Key; + log(event); + if (password[pcounter] == chr(event.Ascii)): + pcounter += 1; + if (pcounter == len(password)): + win32ui.MessageBox("Correct Password!", "KeyInjection Detected",4096) # MB_SYSTEMMODAL = 4096 -- Always on top. + intrusion = False + pcounter = 0 + else: + pcounter = 0 + + return False + + + #Initial Condition + if (prevTime == -1): + prevTime = event.Time; + return True + + + if (i >= len(history)): i = 0; + + #TypeSpeed = NewKeyTime - OldKeyTime + history[i] = event.Time - prevTime + print event.Time,"-",prevTime,"=",history[i] + prevTime = event.Time + speed = sum(history) / float(len(history)) + i=i+1 + + print "\rAverage Speed:",speed + + #Blacklisting + for window in blacklist.split(","): + if window in event.WindowName: + return caught(event) + + #Intrusion detected + if (speed < threshold): + return caught(event) + else: + intrusion = False + # pass execution to next hook registered + return True + +# create and register a hook manager +kl = pyHook.HookManager() +kl.KeyDown = KeyStroke + +# register the hook and execute forever +kl.HookKeyboard() +pythoncom.PumpMessages() diff --git a/duckhunt.py b/duckhunt.py index 9b180ef..149e859 100644 --- a/duckhunt.py +++ b/duckhunt.py @@ -6,16 +6,21 @@ from ctypes import * import pythoncom -import pyHook +import pyHook import win32clipboard import win32ui import os import shutil from time import gmtime, strftime from sys import stdout +from Tkinter import * +from ttk import * import imp -duckhunt = imp.load_source('duckhunt', 'duckhunt.conf') +import webbrowser +import getpass + +duckhunt = imp.load_source('duckhunt', 'duckhunt.conf') ##### NOTES ##### # # 1. Undestanding Protection Policy: @@ -32,36 +37,32 @@ ################# - - - -threshold = duckhunt.threshold # Speed Threshold -size = duckhunt.size # Size of history array -policy = duckhunt.policy.lower() # Designate Policy Type -password = duckhunt.password # Password used in Paranoid Mode -allow_auto_type_software = duckhunt.allow_auto_type_software #Allow AutoType Software (eg. KeyPass or LastPass) +threshold = duckhunt.threshold # Speed Threshold +size = duckhunt.size # Size of history array +policy = duckhunt.policy.lower() # Designate Policy Type +password = duckhunt.password # Password used in Paranoid Mode +allow_auto_type_software = duckhunt.allow_auto_type_software # Allow AutoType Software (eg. KeyPass or LastPass) ################################################################################ -pcounter = 0 # Password Counter (If using password) -speed = 0 # Current Average Keystroke Speed -prevTime = -1 # Previous Keypress Timestamp -i = 0 # History Array Timeslot -intrusion = False # Boolean Flag to be raised in case of intrusion detection -history = [threshold+1] * size # Array for keeping track of average speeds across the last n keypresses -randdrop = duckhunt.randdrop # How often should one drop a letter (in Sneaky mode) -prevWindow = [] # What was the previous window -filename = duckhunt.filename # Filename to save attacks -blacklist = duckhunt.blacklist # Program Blacklist - - - -#Logging the Attack +pcounter = 0 # Password Counter (If using password) +speed = 0 # Current Average Keystroke Speed +prevTime = -1 # Previous Keypress Timestamp +i = 0 # History Array Timeslot +intrusion = False # Boolean Flag to be raised in case of intrusion detection +history = [threshold + 1] * size # Array for keeping track of average speeds across the last n keypresses +randdrop = duckhunt.randdrop # How often should one drop a letter (in Sneaky mode) +prevWindow = [] # What was the previous window +filename = duckhunt.filename # Filename to save attacks +blacklist = duckhunt.blacklist # Program Blacklist + + +# Logging the Attack def log(event): global prevWindow - x = open(filename,"a+") + x = open(filename, "a+") if (prevWindow != event.WindowName): - x.write ("\n[ %s ]\n" % (event.WindowName)) - prevWindow =event.WindowName + x.write("\n[ %s ]\n" % (event.WindowName)) + prevWindow = event.WindowName if event.Ascii > 32 and event.Ascii < 127: x.write(chr(event.Ascii)) else: @@ -72,59 +73,58 @@ def log(event): def caught(event): global intrusion, policy, randdrop - print "Quack! Quack! -- Time to go Duckhunting!" + print("Quack! Quack! -- Time to go Duckhunting!") intrusion = True; - - #Paranoid Policy + # Paranoid Policy if (policy == "paranoid"): - win32ui.MessageBox("Someone might be trying to inject keystrokes into your computer.\nPlease check your ports or any strange programs running.\nEnter your Password to unlock keyboard.", "KeyInjection Detected",4096) # MB_SYSTEMMODAL = 4096 -- Always on top. + win32ui.MessageBox( + "Someone might be trying to inject keystrokes into your computer.\nPlease check your ports or any strange programs running.\nEnter your Password to unlock keyboard.", + "KeyInjection Detected", 4096) # MB_SYSTEMMODAL = 4096 -- Always on top. return False; - #Sneaky Policy + # Sneaky Policy elif (policy == "sneaky"): - randdrop += 1 - #Drop every 5th letter - if (randdrop==7): + randdrop += 1 + # Drop every 5th letter + if (randdrop == 7): randdrop = 0; return False; else: return True; - #Logging Only Policy + # Logging Only Policy elif (policy == "log"): log(event) return True; - - #Normal Policy + # Normal Policy log(event) return False -#This is triggered every time a key is pressed +# This is triggered every time a key is pressed def KeyStroke(event): - global threshold, policy, password, pcounter - global speed, prevTime, i, history, intrusion,blacklist + global speed, prevTime, i, history, intrusion, blacklist + + print(event.Key) + print(event.Message) + print("Injected", event.Injected) - print event.Key; - print event.Message; - print "Injected",event.Injected; - if (event.Injected != 0 and allow_auto_type_software): - print "Injected by Software" + print("Injected by Software") return True; - - - #If an intrusion was detected and we are password protecting - #Then lockdown any keystroke and until password is entered - if (policy == "paranoid" and intrusion): - print event.Key; + + # If an intrusion was detected and we are password protecting + # Then lockdown any keystroke and until password is entered + if (policy == "paranoid" and intrusion): + print(event.Key) log(event); if (password[pcounter] == chr(event.Ascii)): pcounter += 1; if (pcounter == len(password)): - win32ui.MessageBox("Correct Password!", "KeyInjection Detected",4096) # MB_SYSTEMMODAL = 4096 -- Always on top. + win32ui.MessageBox("Correct Password!", "KeyInjection Detected", + 4096) # MB_SYSTEMMODAL = 4096 -- Always on top. intrusion = False pcounter = 0 else: @@ -132,42 +132,166 @@ def KeyStroke(event): return False - - #Initial Condition + # Initial Condition if (prevTime == -1): prevTime = event.Time; return True - if (i >= len(history)): i = 0; - #TypeSpeed = NewKeyTime - OldKeyTime + # TypeSpeed = NewKeyTime - OldKeyTime history[i] = event.Time - prevTime - print event.Time,"-",prevTime,"=",history[i] + print(event.Time, "-", prevTime, "=", history[i]) prevTime = event.Time speed = sum(history) / float(len(history)) - i=i+1 + i = i + 1 - print "\rAverage Speed:",speed - - #Blacklisting + print("\rAverage Speed:", speed) + + # Blacklisting for window in blacklist.split(","): if window in event.WindowName: return caught(event) - #Intrusion detected + # Intrusion detected if (speed < threshold): return caught(event) else: intrusion = False - # pass execution to next hook registered + # pass execution to next hook registered return True -# create and register a hook manager -kl = pyHook.HookManager() + +# create and register a hook manager +kl = pyHook.HookManager() kl.KeyDown = KeyStroke -# register the hook and execute forever -kl.HookKeyboard() -pythoncom.PumpMessages() + +def window(): + window = Tk() + + def StopScript(): + exit(0) + + def About(): + webbrowser.open_new(r"https://github.com/pmsosa/duckhunt/blob/master/README.md") + def WindowStarted(): + def HideWindow(): + window1.destroy() + def add_to_startup(file_path=dir_path): + if file_path == "": + file_path = os.path.dirname(os.path.realpath(__file__)) + bat_path = r'C:\Users\%s\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup' % USER_NAME + with open(bat_path + '\\' + "duckhunt.bat", "w+") as bat_file: + bat_file.write(r'start "" %s''\builds\duckhunt.0.9.exe' % file_path) + + + def FullScreen(): + window1.attributes('-fullscreen', True) + window1.bind('', lambda e: root.destroy()) + + + def HideTitleBar(): + window1.overrideredirect(True) + + + window1 = Tk() + window1.title("DuckHunter") + window1.iconbitmap('favicon.ico') + window1.geometry('310x45') + window1.resizable(False, False) + window1.geometry("+300+300") + window1.attributes("-topmost", True) + menu = Menu(window1) + new_item = Menu(menu) + new_item.add_command(label='STOP SCRIPT', command =StopScript) + new_item.add_command(label='CLOSE WINDOW', command =HideWindow) + new_item.add_separator() + new_item.add_command(label='ABOUT', command =About) + menu.add_cascade(label='Menu', menu=new_item) + window1.config(menu=menu) + btn = Button(window1, text="Stop Script", command=StopScript) + btn1 = Button(window1, text="Close Window", command=HideWindow) + btn2 = Button(window1, text="RUN SCRIPT ON STARTUP", command=add_to_startup) + new_item2 = Menu(menu) + new_item2.add_command(label='RUN SCRIPT ON STARTUP', command =add_to_startup) + new_item2.add_command(label='FULLSCREEN', command =FullScreen) + new_item2.add_command(label='HIDE TITLE BAR', command =HideTitleBar) + menu.add_cascade(label='Settings', menu=new_item2) + btn2.grid(column=3, row=0) + btn.grid(column=1, row=0) + btn1.grid(column=2, row=0) + + window1.mainloop() + + + + def start(): + window.destroy() + WindowStarted() + kl.HookKeyboard() + pythoncom.PumpMessages() + USER_NAME = getpass.getuser() + dir_path = os.path.dirname(os.path.realpath(__file__)) + + + def FullScreen(): + window.attributes('-fullscreen', True) + window.bind('', lambda e: root.destroy()) + def HideTitleBar(): + window.overrideredirect(True) + + + + + def add_to_startup(file_path=dir_path): + if file_path == "": + file_path = os.path.dirname(os.path.realpath(__file__)) + bat_path = r'C:\Users\%s\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup' % USER_NAME + with open(bat_path + '\\' + "duckhunt.bat", "w+") as bat_file: + bat_file.write(r'start "" %s''\AutoRunDuckHunt.exe' % file_path) + + + + + + + + window.title("DuckHunter") + window.iconbitmap('favicon.ico') + window.resizable(False, False) + window.geometry('300x45') + window.geometry("+300+300") + window.attributes("-topmost", True) + menu = Menu(window) + new_item = Menu(menu) + new_item.add_command(label='START', command =start) + new_item.add_command(label='CLOSE', command =StopScript) + new_item.add_separator() + new_item.add_command(label='ABOUT', command =About) + menu.add_cascade(label='Menu', menu=new_item) + new_item2 = Menu(menu) + new_item2.add_command(label='RUN SCRIPT ON STARTUP', command =add_to_startup) + new_item2.add_command(label='FULLSCREEN', command =FullScreen) + new_item2.add_command(label='HIDE TITLE BAR', command =HideTitleBar) + menu.add_cascade(label='Settings', menu=new_item2) + window.config(menu=menu) + btn = Button(window, text="Start", command=start) + btn.grid(column=1, row=0) + btn = Button(window, text="Close", command=StopScript) + btn.grid(column=2, row=0) + btn = Button(window, text="RUN SCRIPT ON STARTUP", command=add_to_startup) + btn.grid(column=3, row=0) + + + window.mainloop() + + + + # register the hook and execute forever + + +window() + + diff --git a/favicon.ico b/favicon.ico new file mode 100644 index 0000000..e68af9d Binary files /dev/null and b/favicon.ico differ diff --git a/setup.py b/setup.py index d8f23d6..cd26b8e 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,6 @@ from distutils.core import setup import py2exe, sys, os - +#This to change the script to an exe edit the variables inside the duckhunt-configurable.py file then run this script sys.argv.append('py2exe') setup( @@ -9,7 +9,7 @@ description = 'duckhunt-', options = {'py2exe': {'bundle_files': 1, 'compressed': True}}, - windows = [{'script': "duckhunt.py"}], + windows = [{'script': "duckhunt-configurable.py"}], zipfile = None, )