From 255b2f6ae09a58045938c522508d919b5bce4d3d Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 9 May 2015 12:10:00 +0000 Subject: [PATCH 01/77] Major change, see description, will update readme This release is inspired by many, especially @blaa - added configparser, now has proper config. - added custom kill commands - added sync?yes/no if no sync then small sleep - added remove logs&settings - devmode always overwrites /etc/ settings with projectfolder settings.ini --- usbkill.py | 158 ++++++++++++++++++++++++++++++----------------------- 1 file changed, 90 insertions(+), 68 deletions(-) diff --git a/usbkill.py b/usbkill.py index 5b32eef..3608ffb 100644 --- a/usbkill.py +++ b/usbkill.py @@ -26,7 +26,11 @@ import subprocess import platform import os, sys, signal -from time import time, sleep +from time import sleep +from datetime import datetime + +import configparser +from json import loads as jsonloads # Get the current platform CURRENT_PLATFORM = platform.system().upper() @@ -39,8 +43,7 @@ DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] # Set the settings filename here -SETTINGS_FILE = '/etc/usbkill/settings'; - +SETTINGS_FILE = '/etc/usbkill/settings.ini' help_message = """ usbkill is a simple program with one goal: quickly shutdown the computer when a usb is inserted or removed. @@ -51,26 +54,44 @@ In order to be able to shutdown the computer, this program needs to run as root. """ -def log(msg): - logfile = "/var/log/usbkill/usbkill.log" - with open(logfile, 'a+') as log: - contents = '\n{0} {1}\nCurrent state:'.format(str(time()), msg) +def log(settings, msg): + log_file = settings['log_file'] + + contents = '\n{0} {1}\nCurrent state:'.format(str(datetime.now()), msg) + with open(log_file, 'a+') as log: log.write(contents) # Log current USB state if CURRENT_PLATFORM.startswith("DARWIN"): - os.system("system_profiler SPUSBDataType >> " + logfile) + os.system("system_profiler SPUSBDataType >> " + log_file) else: - os.system("lsusb >> " + logfile) + os.system("lsusb >> " + log_file) -def kill_computer(): +def kill_computer(settings): # Log what is happening: - log("Detected a USB change. Dumping the list of connected devices and killing the computer...") + log(settings, "Detected a USB change. Dumping the list of connected devices and killing the computer...") - # Sync the filesystem so that the recent log entry does not get lost. - os.system("sync") + # Execute kill commands in order. + for command in settings['kill_commands']: + os.system(command) + + # Remove logs and settings + if settings['remove_logs_and_settings']: + # This should be implemented with a propper wipe + os.remove(settings['log_file']) + os.remove(SETTINGS_FILE) + os.rmdir(os.path.dirname(settings['log_file'])) + os.rmdir(os.path.dirname(SETTINGS_FILE)) + + if settings['do_sync']: + # Sync the filesystem to save recent changes + os.system("sync") + else: + # If syncing is risky because it might take too long, then sleep for 5ms. + # This will still allow for syncing in most cases. + sleep(0.05) - # Poweroff computer immediately + # Finally poweroff computer immediately if CURRENT_PLATFORM.startswith("DARWIN"): # OS X (Darwin) - Will halt ungracefully, without signaling apps os.system("killall Finder && killall loginwindow && halt -q") @@ -108,7 +129,7 @@ def check_inside(result): # Append to the list of devices devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) # debug: devices.append(result["vendor_id"] + ':' + result["product_id"]) - except AssertionError: {} + except KeyError: {} # Check if there is items inside try: @@ -128,53 +149,31 @@ def check_inside(result): # Use lsusb on linux and bsd return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) -def settings_template(filename): - # Make sure there is the settings folder - if not os.path.isdir("/etc/usbkill/"): - os.mkdir("/etc/usbkill/") - - # Make sure there is a settings file - if not os.path.isfile(filename): - # Pre-populate the settings file if it does not exist yet - with open(filename, 'w') as f: - f.write("# whitelist command lists the usb ids that you want whitelisted\n") - f.write("# find the correct usbid for your trusted usb using the command 'lsusb'\n") - f.write("# usbid looks something line 0123:9abc\n") - f.write("# Be warned! other parties can copy your trusted usbid to another usb device!\n") - f.write("# use whitelist command and single space separation as follows:\n") - f.write("# whitelist usbid1 usbid2 etc\n") - f.write("whitelist \n\n") - f.write("# allow for a certain amount of sleep time between checks, e.g. 0.25 seconds:\n") - f.write("sleep 0.25\n") - def load_settings(filename): # read all lines of settings file - with open(filename, 'r') as f: - lines = f.readlines() - - # Find the only two supported settings - devices = None - sleep_time = None - for line in lines: - if line[:10] == "whitelist ": - devices = line.replace("\n","").replace(" "," ").split(" ")[1:] - if line[:6] == "sleep ": - sleep_time = float(line.replace("\n","").replace(" "," ").split(" ").pop()) + config = configparser.ConfigParser() + config.read(filename) + section = config['config'] + settings = dict({ + 'sleep_time' : float(section['sleep']), + 'whitelist': jsonloads(section['whitelist']), + 'kill_commands': jsonloads(section['kill_commands']), + 'log_file': section['log_file'], + 'remove_logs_and_settings' : section.getboolean('remove_logs_and_settings'), + 'do_sync' : section.getboolean('do_sync') }) - assert not None in [devices, sleep_time], "[ERROR] Please set the 'sleep' and 'whitelist' parameters in '/etc/usbkill/settings'!" - assert sleep_time > 0.0, "[ERROR] Please allow for positive non-zero 'sleep' delay between USB checks!" - return devices, sleep_time + return settings -def loop(whitelisted_devices, sleep_time, killer): +def loop(settings): # Main loop that checks every 'sleep_time' seconds if computer should be killed. # Allows only whitelisted usb devices to connect! # Does not allow usb device that was present during program start to disconnect! start_devices = lsusb() - acceptable_devices = set(start_devices + whitelisted_devices) + acceptable_devices = set(start_devices + settings['whitelist']) # Write to logs that loop is starting: - msg = "[INFO] Started patrolling the USB ports every " + str(sleep_time) + " seconds..." - log(msg) + msg = "[INFO] Started patrolling the USB ports every " + str(settings['sleep_time']) + " seconds..." + log(settings, msg) print(msg) # Main loop @@ -183,29 +182,29 @@ def loop(whitelisted_devices, sleep_time, killer): current_devices = lsusb() # Check that no usbids are connected twice. - # Two devices with same usbid implied a usbid copy attack + # Two devices with same usbid implies a usbid copy attack if not len(current_devices) == len(set(current_devices)): - killer() + settings['killer'](settings) # Check that all current devices are in the set of acceptable devices for device in current_devices: if device not in acceptable_devices: - killer() + settings['killer'](settings) # Check that all start devices are still present in current devices # Prevent multiple devices with the same Vendor/Product ID to be connected for device in start_devices: if device not in current_devices: - killer() + settings['killer'](settings) - sleep(sleep_time) + sleep(settings['sleep_time']) def exit_handler(signum, frame): print("\n[INFO] Exiting because exit signal was received\n") log("[INFO] Exiting because exit signal was received") sys.exit(0) -if __name__=="__main__": +def startup_checks(): # Splash print(" _ _ _ _ _ \n" + " | | | | (_) | | \n" + @@ -223,10 +222,12 @@ def exit_handler(signum, frame): # Check if dev mode killer = kill_computer + dev = False if '--dev' in args: print("[NOTICE] Running in dev-mode.") - killer = lambda : sys.exit("Dev-mode, kill overwritten and exiting.") + killer = lambda _ : sys.exit("Dev-mode, kill overwritten and exiting.") args.remove('--dev') + dev = True # Check all other args if len(args) > 0: @@ -242,19 +243,40 @@ def exit_handler(signum, frame): if subprocess.check_output("fdesetup isactive", shell=True).strip() != "true": print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") + if not os.path.isdir("/etc/usbkill/"): + os.mkdir("/etc/usbkill/") + + # The following does: + # 1 if no settings in /etc/, then copy settings to /etc/ and remove setting.ini in current folder + # 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder + if not os.path.isfile(SETTINGS_FILE) or dev: + if not os.path.isfile("settings.ini"): + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + os.getcwd() + "/\n") + os.system("cp settings.ini " + SETTINGS_FILE) + if not dev: + os.remove("settings.ini") + + # Load settings + settings = load_settings(SETTINGS_FILE) + settings['killer'] = killer + # Make sure there is a logging folder - if not os.path.isdir("/var/log/usbkill/"): - os.mkdir("/var/log/usbkill/") + log_folder = settings['log_file'].split("/")[:-1] + log_folder = "".join([x + "/" for x in log_folder])[:-1] + if not os.path.isdir(log_folder): + os.mkdir(log_folder) + + return settings - # Register handlers for clean exit of loop +if __name__=="__main__": + # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) - - # Make sure settings file is available - settings_template(SETTINGS_FILE) - # Load settings - whitelisted_devices, sleep_time = load_settings(SETTINGS_FILE) + # Run startup checks and load settings: + settings = startup_checks() # Start main loop - loop(whitelisted_devices, sleep_time, killer) + loop(settings) + + From b47e2703a93c98e43e8db07cd37ec01d1e900e57 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 9 May 2015 12:10:57 +0000 Subject: [PATCH 02/77] Create settings.ini --- settings.ini | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 settings.ini diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..b37a52d --- /dev/null +++ b/settings.ini @@ -0,0 +1,33 @@ +[config] + +# whitelist command lists the usb ids that you want whitelisted +# find the correct usbid for your trusted usb using the command 'lsusb' +# usbid looks something line 0123:9abc +# Be warned! other parties can copy your trusted usbid to another usb device! +# use whitelist command and single space separation as follows: +# whitelist = ['usbid1', 'usbid2'] +whitelist = [] + +# allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: +sleep = 0.25 + +# Custom kill commands. +# This is where you want to release volumes, etc. +# If you don't know what to place here, just make sure you use full disk encryption +# Commands will run in order and as root +kill_commands = [] + +# Log file location: +log_file = /var/log/usbkill/usbkill.log + +# Remove log (folder) and settings (folder) upon kill? +# This might be usefull if you only encrypt portions of your disk (home folder or volumes). +# Make sure to sync the system (using do_sync=True) if you want to remove logs and settings +# (True/False) +# [[[ Currently not supported yet ]]] +remove_logs_and_settings = False + +# Should usbkill sync the file system for you? +# This should not be a problem on most computers. +# Sync will save some of your work to disk before killing your computer. +do_sync = True From b3193d294ea3c13802cd9b94232505b51d918a98 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 9 May 2015 12:30:49 +0000 Subject: [PATCH 03/77] Update README.md --- README.md | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index e32c05b..8cbd591 100644 --- a/README.md +++ b/README.md @@ -15,10 +15,10 @@ sudo python usbkill.py There are 3 reasons (maybe more?) to use this tool: - In case the police or other thugs come busting in (or steal your laptop from you when you are at a public library as happened to Ross). The police commonly uses a « [mouse jiggler](http://www.amazon.com/Cru-dataport-Jiggler-Automatic-keyboard-Activity/dp/B00MTZY7Y4/ref=pd_bxgy_pc_text_y/190-3944818-7671348) » to keep the screensaver and sleep mode from activating. -- You don't want someone to install backdoors or malware on your computer or to retrieve documents from your computer via USB. +- You don't want someone retrieve documents from your computer via USB or install malware or backdoors. - You want to improve the security of your (Full Disk Encrypted) home or corporate server (e.g. Your Raspberry). -> **[!] Important**: Make sure to use full disk encryption! Otherwise they will get in anyway. +> **[!] Important**: Make sure to use (partial) disk encryption ! Otherwise they will get in anyway. > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately. @@ -26,13 +26,14 @@ There are 3 reasons (maybe more?) to use this tool: - Compability with Linux, *BSD and OS X - Shutdown the computer when there is USB activity -- Ability to whitelist an USB device -- Ability to change the check interval (default: 0.5) +- Customizable. Define which commands should be executed just before shut down. +- Ability to whitelist a USB device +- Ability to change the check interval (default: 0.25) - Work perfectly in sleep mode (OS X) - Low memory consumption -- No dependency except Python +- No dependency except Python2/3 -and more to come! Custom commands for when a USB change is observed will be implemented. +and more to come! including monitoring of other ports. ### Contact From 1329537198bfa2484c291710d4a30fddc671a0dc Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 9 May 2015 12:37:23 +0000 Subject: [PATCH 04/77] Update usbkill.py --- usbkill.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usbkill.py b/usbkill.py index 3608ffb..87ac6b1 100644 --- a/usbkill.py +++ b/usbkill.py @@ -261,8 +261,7 @@ def startup_checks(): settings['killer'] = killer # Make sure there is a logging folder - log_folder = settings['log_file'].split("/")[:-1] - log_folder = "".join([x + "/" for x in log_folder])[:-1] + log_folder = os.path.dirname(settings['log_file']) if not os.path.isdir(log_folder): os.mkdir(log_folder) From bc50674bbd71a6def801d97a4ee3ac809cd80958 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 9 May 2015 12:58:21 +0000 Subject: [PATCH 05/77] added shred example --- settings.ini | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/settings.ini b/settings.ini index b37a52d..b9ffe59 100644 --- a/settings.ini +++ b/settings.ini @@ -15,6 +15,11 @@ sleep = 0.25 # This is where you want to release volumes, etc. # If you don't know what to place here, just make sure you use full disk encryption # Commands will run in order and as root +# Example shred: +# shred can take a long time for larger files! +# only use for tiny files! +# if you have an ssd then set -n=0 (only hdd need more) +# kill_commands = [ 'shred -f -n 0 -z -u FILENAME', 'shred -f -n 0 -z -u OTHERFILE' ] kill_commands = [] # Log file location: From 9e7ade88b1c8574c54fe9519f9aebf536ca74c7a Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 17:19:46 +0200 Subject: [PATCH 06/77] Re-adding the crashfix --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index 87ac6b1..44ddd54 100644 --- a/usbkill.py +++ b/usbkill.py @@ -129,7 +129,7 @@ def check_inside(result): # Append to the list of devices devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) # debug: devices.append(result["vendor_id"] + ':' + result["product_id"]) - except KeyError: {} + except AssertionError: {} # Check if there is items inside try: From 0751675e5b1350ce40643c5910f7673b1963affc Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 17:22:27 +0200 Subject: [PATCH 07/77] help_message typo --- usbkill.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/usbkill.py b/usbkill.py index 44ddd54..7da3ee4 100644 --- a/usbkill.py +++ b/usbkill.py @@ -46,8 +46,8 @@ SETTINGS_FILE = '/etc/usbkill/settings.ini' help_message = """ -usbkill is a simple program with one goal: quickly shutdown the computer when a usb is inserted or removed. -It logs to /var/log/usbkill/kills.log +usbkill is a simple program with one goal: quickly shutdown the computer when a USB is inserted or removed. +Events are logged in /var/log/usbkill/kills.log You can configure a whitelist of USB ids that are acceptable to insert and the remove. The USB id can be found by running the command 'lsusb'. Settings can be changed in /etc/usbkill/settings @@ -128,7 +128,6 @@ def check_inside(result): assert "vendor_id" in result and "product_id" in result # Append to the list of devices devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) - # debug: devices.append(result["vendor_id"] + ':' + result["product_id"]) except AssertionError: {} # Check if there is items inside From f276cb590eb48c77ffeba5d86e9c52bed0c0e83e Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 18:20:14 +0200 Subject: [PATCH 08/77] Cosmetic --- usbkill.py | 25 ++++++++++++------------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/usbkill.py b/usbkill.py index 7da3ee4..51b35db 100644 --- a/usbkill.py +++ b/usbkill.py @@ -1,8 +1,8 @@ -# _ _ _ _ _ -# | | | | (_) | | -# _ _ ___| |__ | | _ _| | | -# | | | |/___) _ \| |_/ ) | | | -# | |_| |___ | |_) ) _ (| | | | +# _ _ _ _ _ +# | | | | (_) | | +# _ _ ___| |__ | | _ _| | | +# | | | |/___) _ \| |_/ ) | | | +# | |_| |___ | |_) ) _ (| | | | # |____/(___/|____/|_| \_)_|\_)_) # # @@ -206,11 +206,11 @@ def exit_handler(signum, frame): def startup_checks(): # Splash print(" _ _ _ _ _ \n" + - " | | | | (_) | | \n" + - " _ _ ___| |__ | | _ _| | | \n" + - " | | | |/___) _ \| |_/ ) | | | \n" + - " | |_| |___ | |_) ) _ (| | | | \n" + - " |____/(___/|____/|_| \_)_|\_)_)\n") + " | | | | (_) | | \n" + + " _ _ ___| |__ | | _ _| | | \n" + + " | | | |/___) _ \| |_/ ) | | | \n" + + " | |_| |___ | |_) ) _ (| | | | \n" + + " |____/(___/|____/|_| \_)_|\_)_)\n") # Check arguments args = sys.argv[1:] @@ -267,14 +267,13 @@ def startup_checks(): return settings if __name__=="__main__": + # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) - # Run startup checks and load settings: + # Run startup checks and load settings settings = startup_checks() # Start main loop loop(settings) - - From 8a27d4d49546a3c069516a2a5c4a9f06ff0cec36 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 19:43:27 +0200 Subject: [PATCH 09/77] Added " && " as a variable --- settings.ini | 5 ++++- usbkill.py | 8 +++++--- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/settings.ini b/settings.ini index b9ffe59..ab8199c 100644 --- a/settings.ini +++ b/settings.ini @@ -29,8 +29,11 @@ log_file = /var/log/usbkill/usbkill.log # This might be usefull if you only encrypt portions of your disk (home folder or volumes). # Make sure to sync the system (using do_sync=True) if you want to remove logs and settings # (True/False) -# [[[ Currently not supported yet ]]] remove_logs_and_settings = False +# How many passes should we use to remove the files? +# If you have an SSD then set 0 (only HDD need more) +# (0, 3 or 7) +remove_passes = 3 # Should usbkill sync the file system for you? # This should not be a problem on most computers. diff --git a/usbkill.py b/usbkill.py index 51b35db..30d3d53 100644 --- a/usbkill.py +++ b/usbkill.py @@ -32,6 +32,9 @@ import configparser from json import loads as jsonloads +# Shell separator +SHELL_SEPARATOR = ' && ' + # Get the current platform CURRENT_PLATFORM = platform.system().upper() @@ -94,7 +97,7 @@ def kill_computer(settings): # Finally poweroff computer immediately if CURRENT_PLATFORM.startswith("DARWIN"): # OS X (Darwin) - Will halt ungracefully, without signaling apps - os.system("killall Finder && killall loginwindow && halt -q") + os.system("killall Finder" + SHELL_SEPARATOR + "killall loginwindow" + SHELL_SEPARATOR + "halt -q") elif CURRENT_PLATFORM.endswith("BSD"): # BSD-based systems - Will shutdown os.system("shutdown -h now") @@ -174,7 +177,7 @@ def loop(settings): msg = "[INFO] Started patrolling the USB ports every " + str(settings['sleep_time']) + " seconds..." log(settings, msg) print(msg) - + # Main loop while True: # List the current usb devices @@ -267,7 +270,6 @@ def startup_checks(): return settings if __name__=="__main__": - # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) From b622be175ba13a99d5ecdd877a12a2e6b1c46309 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 20:52:36 +0200 Subject: [PATCH 10/77] Settings: Typos --- settings.ini | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/settings.ini b/settings.ini index ab8199c..a74d6b2 100644 --- a/settings.ini +++ b/settings.ini @@ -30,8 +30,10 @@ log_file = /var/log/usbkill/usbkill.log # Make sure to sync the system (using do_sync=True) if you want to remove logs and settings # (True/False) remove_logs_and_settings = False + # How many passes should we use to remove the files? -# If you have an SSD then set 0 (only HDD need more) +# Note: If you have an SSD then set 0 (only HDD need more) +# Note: If 3 passes is not available on your OS then usbkill will fallback to 7 # (0, 3 or 7) remove_passes = 3 @@ -39,3 +41,4 @@ remove_passes = 3 # This should not be a problem on most computers. # Sync will save some of your work to disk before killing your computer. do_sync = True + From ca3eb6587fa62ab24e5f3b25c89cd2f8f4e1e01f Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 21:08:19 +0200 Subject: [PATCH 11/77] Experimental secure erasing system for remove_logs_and_settings --- usbkill.py | 103 ++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 98 insertions(+), 5 deletions(-) diff --git a/usbkill.py b/usbkill.py index 30d3d53..01b8fee 100644 --- a/usbkill.py +++ b/usbkill.py @@ -57,6 +57,26 @@ In order to be able to shutdown the computer, this program needs to run as root. """ +def which(program): + """ + Test if an executable exist in Python + http://stackoverflow.com/a/377028 + """ + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + def log(settings, msg): log_file = settings['log_file'] @@ -80,12 +100,67 @@ def kill_computer(settings): # Remove logs and settings if settings['remove_logs_and_settings']: - # This should be implemented with a propper wipe - os.remove(settings['log_file']) - os.remove(SETTINGS_FILE) - os.rmdir(os.path.dirname(settings['log_file'])) - os.rmdir(os.path.dirname(SETTINGS_FILE)) + + # Remove settings & logs + if settings['remove_program']['path'].endswith("/srm") and (settings['remove_passes'] == 3 or settings['remove_passes'] == 7): + + def return_command(version): + """ + Return the right command based on the version of srm and the number of passes defined in settings + """ + if version[1] == '2': # Check if this is an 1.2.x version + if version[2] <= '10': + # srm 1.2.10 introduce new commands which have 3-pass (-doe -E) + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + return '--force --recursive --dod' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + return '--force --recursive --doe' + elif version[2] == '9': + # srm 1.2.9 moved --medium to -dod (-D) + if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass + return '--force --recursive --dod' + elif version[2] <= '8': + # srm 1.2.8 and above (used in OS X Yosemite) support only 7/1-pass + if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass + return '--force --recursive --medium' + + # Fallback to 1-pass erasing + return '--force --recursive --simple' + + # Return the right command for srm + remove_command = return_command(settings['remove_program']['version']) + + elif settings['remove_program']['path'].endswith("/shred"): + + # Use find + custom_start = 'find ' + + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + remove_command = '-depth -type f -exec shred -f -n 7 -z -u {} \;' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' + else: # Fallback to 0-pass erasing + remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' + + else: + # Fallback to 0-pass erasing using rm + settings['remove_program']['path'] = str(which('rm')) + remove_command = '-rf' + # Set custom_start empty if not set + try: + custom_start + except UnboundLocalError: + custom_start = '' + + # Build the command + remove_command = SHELL_SEPARATOR + custom_start + settings['remove_program']['path'] + ' ' + remove_command + ' ' + remove_command = remove_command.lstrip(SHELL_SEPARATOR) + os.path.dirname(settings['log_file']) + remove_command + os.path.dirname(SETTINGS_FILE) + + # Execute the command + print(remove_command) + os.system(remove_command) + if settings['do_sync']: # Sync the filesystem to save recent changes os.system("sync") @@ -162,6 +237,7 @@ def load_settings(filename): 'kill_commands': jsonloads(section['kill_commands']), 'log_file': section['log_file'], 'remove_logs_and_settings' : section.getboolean('remove_logs_and_settings'), + 'remove_passes' : float(section['remove_passes']), 'do_sync' : section.getboolean('do_sync') }) return settings @@ -240,6 +316,21 @@ def startup_checks(): if not os.geteuid() == 0: sys.exit("\n[ERROR] This program needs to run as root.\n") + # Determine which secure tool should we use to remove files + # If none is available, fallback to "rm" + if which('srm') != None: + REMOVE_PROGRAM = dict({ + 'path': str(which('srm')) + }) + REMOVE_PROGRAM['version'] = re.findall("([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,2})+", subprocess.check_output(REMOVE_PROGRAM['path'] + ' --version', shell=True).decode('utf-8').strip())[0] + + elif which('shred') != None: + REMOVE_PROGRAM = str(which('shred')) + #elif which('wipe') != None: + # REMOVE_PROGRAM = str(which('wipe')) + else: + REMOVE_PROGRAM = False + # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): if subprocess.check_output("fdesetup isactive", shell=True).strip() != "true": @@ -261,6 +352,7 @@ def startup_checks(): # Load settings settings = load_settings(SETTINGS_FILE) settings['killer'] = killer + settings['remove_program'] = REMOVE_PROGRAM # Make sure there is a logging folder log_folder = os.path.dirname(settings['log_file']) @@ -270,6 +362,7 @@ def startup_checks(): return settings if __name__=="__main__": + # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) From 2bc935cb5e836b8ae73fd5143d5996b740dd203d Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 21:09:10 +0200 Subject: [PATCH 12/77] Added sys.exit(0) at the end of the killing function to prevent executing twice (or more) all killing commands --- usbkill.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/usbkill.py b/usbkill.py index 01b8fee..549a740 100644 --- a/usbkill.py +++ b/usbkill.py @@ -179,6 +179,9 @@ def return_command(version): else: # Linux-based systems - Will shutdown os.system("poweroff -f") + + # Exit the process to prevent executing twice (or more) all commands + sys.exit(0) def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids From e0e7e43e8e2e5a096fd7041e509fb8e36daf3ce0 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 21:19:36 +0200 Subject: [PATCH 13/77] Attempt to fix https://github.com/hephaest0s/usbkill/issues/53 --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index 549a740..ea4a335 100644 --- a/usbkill.py +++ b/usbkill.py @@ -336,7 +336,7 @@ def startup_checks(): # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): - if subprocess.check_output("fdesetup isactive", shell=True).strip() != "true": + if subprocess.check_output("/usr/bin/fdesetup isactive", shell=True).strip() != "true": print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") if not os.path.isdir("/etc/usbkill/"): From 75c45008e71ff56198a7baa84d972ca4c7047471 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 21:36:08 +0200 Subject: [PATCH 14/77] Typo --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index ea4a335..8fb003d 100644 --- a/usbkill.py +++ b/usbkill.py @@ -230,7 +230,7 @@ def check_inside(result): return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def load_settings(filename): - # read all lines of settings file + # Read all lines of settings file config = configparser.ConfigParser() config.read(filename) section = config['config'] From 3d3d0b3f4feb59f1d4c20103f6382088056be6ab Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 22:21:31 +0200 Subject: [PATCH 15/77] Optimisations and improvements in the secure files wiping code --- usbkill.py | 142 +++++++++++++++++++++++++++++------------------------ 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/usbkill.py b/usbkill.py index 8fb003d..6fab130 100644 --- a/usbkill.py +++ b/usbkill.py @@ -101,65 +101,67 @@ def kill_computer(settings): # Remove logs and settings if settings['remove_logs_and_settings']: - # Remove settings & logs - if settings['remove_program']['path'].endswith("/srm") and (settings['remove_passes'] == 3 or settings['remove_passes'] == 7): - - def return_command(version): - """ - Return the right command based on the version of srm and the number of passes defined in settings - """ - if version[1] == '2': # Check if this is an 1.2.x version - if version[2] <= '10': - # srm 1.2.10 introduce new commands which have 3-pass (-doe -E) - if settings['remove_passes'] == 7: # US Dod compliant 7-pass - return '--force --recursive --dod' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass - return '--force --recursive --doe' - elif version[2] == '9': - # srm 1.2.9 moved --medium to -dod (-D) - if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass - return '--force --recursive --dod' - elif version[2] <= '8': - # srm 1.2.8 and above (used in OS X Yosemite) support only 7/1-pass - if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass - return '--force --recursive --medium' - - # Fallback to 1-pass erasing - return '--force --recursive --simple' - - # Return the right command for srm - remove_command = return_command(settings['remove_program']['version']) - - elif settings['remove_program']['path'].endswith("/shred"): - - # Use find - custom_start = 'find ' + # Continue only if a shredder is available + if settings['remove_program']['path'] != None: - if settings['remove_passes'] == 7: # US Dod compliant 7-pass - remove_command = '-depth -type f -exec shred -f -n 7 -z -u {} \;' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass - remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' - else: # Fallback to 0-pass erasing - remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' + # Remove settings & logs + if settings['remove_program']['path'].endswith("/srm") and (settings['remove_passes'] == 3 or settings['remove_passes'] == 7): - else: - # Fallback to 0-pass erasing using rm - settings['remove_program']['path'] = str(which('rm')) - remove_command = '-rf' + def return_command(version): + """ + Return the right command based on the version of srm and the number of passes defined in settings + """ + if version[1] == '2': # Check if this is an 1.2.x version + if version[2] <= '10': + # srm 1.2.10 introduce new commands which have 3-pass (-doe -E) + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + return '--force --recursive --dod' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + return '--force --recursive --doe' + elif version[2] == '9': + # srm 1.2.9 moved --medium to -dod (-D) + if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass + return '--force --recursive --dod' + elif version[2] <= '8': + # srm 1.2.8 and above (used in OS X Yosemite) support only 7/1-pass + if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass + return '--force --recursive --medium' - # Set custom_start empty if not set - try: - custom_start - except UnboundLocalError: - custom_start = '' + # Fallback to 1-pass erasing + return '--force --recursive --simple' + + # Return the right command for srm + remove_command = return_command(settings['remove_program']['version']) + + elif settings['remove_program']['path'].endswith("/shred"): + + # Use find + custom_start = 'find ' + + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + remove_command = '-depth -type f -exec shred -f -n 7 -z -u {} \;' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' + else: # Fallback to 0-pass erasing + remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' - # Build the command - remove_command = SHELL_SEPARATOR + custom_start + settings['remove_program']['path'] + ' ' + remove_command + ' ' - remove_command = remove_command.lstrip(SHELL_SEPARATOR) + os.path.dirname(settings['log_file']) + remove_command + os.path.dirname(SETTINGS_FILE) + elif settings['remove_program']['path'].endswith("/rm"): + + # Fallback to 0-pass erasing using rm + remove_command = '-rf' - # Execute the command - print(remove_command) - os.system(remove_command) + # Set custom_start empty if not set + try: + custom_start + except UnboundLocalError: + custom_start = '' + + # Build the command + remove_command = SHELL_SEPARATOR + custom_start + settings['remove_program']['path'] + ' ' + remove_command + ' ' + remove_command = remove_command.lstrip(SHELL_SEPARATOR) + os.path.dirname(settings['log_file']) + remove_command + os.path.dirname(SETTINGS_FILE) + + # Execute the command + os.system(remove_command) if settings['do_sync']: # Sync the filesystem to save recent changes @@ -321,18 +323,32 @@ def startup_checks(): # Determine which secure tool should we use to remove files # If none is available, fallback to "rm" - if which('srm') != None: + REMOVE_PROGRAMS = [ + which('srm'), # + which('shred'),# + which('wipe'), # + which('rm') # + ] + if REMOVE_PROGRAMS[0] != None: # srm REMOVE_PROGRAM = dict({ - 'path': str(which('srm')) + 'path': REMOVE_PROGRAMS[0], + 'version': re.findall("([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,2})+", subprocess.check_output(REMOVE_PROGRAMS[0] + ' --version', shell=True).decode('utf-8').strip())[0] + }) + elif REMOVE_PROGRAMS[1] != None: # shred + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[1] + }) + #elif REMOVE_PROGRAMS[2] != None: # wipe + # REMOVE_PROGRAM = dict({ + # 'path': REMOVE_PROGRAMS[2] + # }) + elif REMOVE_PROGRAMS[3] != None: # rm + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[3] }) - REMOVE_PROGRAM['version'] = re.findall("([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,2})+", subprocess.check_output(REMOVE_PROGRAM['path'] + ' --version', shell=True).decode('utf-8').strip())[0] - - elif which('shred') != None: - REMOVE_PROGRAM = str(which('shred')) - #elif which('wipe') != None: - # REMOVE_PROGRAM = str(which('wipe')) else: - REMOVE_PROGRAM = False + REMOVE_PROGRAM = None + print('[WARNING] Files removing has been disabled because no shredder has been found! Please install srm, shred, wipe or rm!') # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): From 75957ea2b0fc4a687d38ce7ec7492e75b4dd1fe1 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 22:26:10 +0200 Subject: [PATCH 16/77] Typo --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index 6fab130..bc8e4be 100644 --- a/usbkill.py +++ b/usbkill.py @@ -352,7 +352,7 @@ def startup_checks(): # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): - if subprocess.check_output("/usr/bin/fdesetup isactive", shell=True).strip() != "true": + if subprocess.check_output("/usr/bin/fdesetup isactive", shell=True).decode('utf-8').strip() != "true": print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") if not os.path.isdir("/etc/usbkill/"): From 0b0c8f84fbc3598f0c58b723363d26789337295a Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 22:34:25 +0200 Subject: [PATCH 17/77] Fix https://github.com/hephaest0s/usbkill/issues/53 --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index bc8e4be..408cfef 100644 --- a/usbkill.py +++ b/usbkill.py @@ -352,7 +352,7 @@ def startup_checks(): # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): - if subprocess.check_output("/usr/bin/fdesetup isactive", shell=True).decode('utf-8').strip() != "true": + if subprocess.check_output(["/usr/bin/fdesetup", "isactive"]).decode('utf-8').strip() != "true": print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") if not os.path.isdir("/etc/usbkill/"): From 96f7143f673a318c4a088a801ada483367b7ba68 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 22:55:25 +0200 Subject: [PATCH 18/77] configparser: Compatibility layer for Python 2/3 --- usbkill.py | 61 +++++++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 51 insertions(+), 10 deletions(-) diff --git a/usbkill.py b/usbkill.py index 408cfef..6130f63 100644 --- a/usbkill.py +++ b/usbkill.py @@ -29,7 +29,11 @@ from time import sleep from datetime import datetime -import configparser +if sys.version_info[0] == 3: + import configparser +else: + import ConfigParser + from json import loads as jsonloads # Shell separator @@ -232,18 +236,55 @@ def check_inside(result): return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def load_settings(filename): + + if sys.version_info[0] == 3: + config = configparser.ConfigParser() + else: + config = ConfigParser.ConfigParser() + # Read all lines of settings file - config = configparser.ConfigParser() config.read(filename) - section = config['config'] + + def get_arg(config, name, gtype=''): + """ + configparser: Compatibility layer for Python 2/3 + """ + if sys.version_info[0] == 3: # Python 3 + + print(config['config']) + + section = config['config'] + + if gtype == '': + return section[name] + elif gtype == 'FLOAT': + return section.getfloat(name) + elif gtype == 'INT': + return section.getint(name) + elif gtype == 'BOOL': + return section.getboolean(name) + + else: # Python 2 + + if gtype == '': + return config.get('config', name) + elif gtype == 'FLOAT': + return config.getfloat('config', name) + elif gtype == 'INT': + return config.getint('config', name) + elif gtype == 'BOOL': + return config.getboolean('config', name) + + # Build settings settings = dict({ - 'sleep_time' : float(section['sleep']), - 'whitelist': jsonloads(section['whitelist']), - 'kill_commands': jsonloads(section['kill_commands']), - 'log_file': section['log_file'], - 'remove_logs_and_settings' : section.getboolean('remove_logs_and_settings'), - 'remove_passes' : float(section['remove_passes']), - 'do_sync' : section.getboolean('do_sync') }) + 'sleep_time' : get_arg(config, 'sleep', 'FLOAT'), + 'whitelist': jsonloads(get_arg(config, 'whitelist')), + 'kill_commands': jsonloads(get_arg(config, 'kill_commands')), + 'log_file': get_arg(config, 'log_file'), + 'remove_logs_and_settings' : get_arg(config, 'remove_logs_and_settings', 'BOOL'), + 'remove_passes' : get_arg(config, 'remove_passes', 'INT'), + 'do_sync' : get_arg(config, 'do_sync', 'BOOL') + }) return settings From 570a7767a210d1fac3aaff4e6f00c46bb684ee27 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:01:53 +0200 Subject: [PATCH 19/77] README: Typos in the Feature List --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 8cbd591..85b5776 100644 --- a/README.md +++ b/README.md @@ -24,14 +24,14 @@ There are 3 reasons (maybe more?) to use this tool: ### Feature List -- Compability with Linux, *BSD and OS X +- Compatible with Linux, *BSD and OS X - Shutdown the computer when there is USB activity - Customizable. Define which commands should be executed just before shut down. - Ability to whitelist a USB device -- Ability to change the check interval (default: 0.25) +- Ability to change the check interval (default: 25ms) - Work perfectly in sleep mode (OS X) - Low memory consumption -- No dependency except Python2/3 +- No dependency except Python 2/3 and more to come! including monitoring of other ports. From 12c6f4a70055502dc0de63a14a7f104f8ad8d6bd Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:02:29 +0200 Subject: [PATCH 20/77] README: oops, missed that 0! --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 85b5776..7deff79 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ There are 3 reasons (maybe more?) to use this tool: - Shutdown the computer when there is USB activity - Customizable. Define which commands should be executed just before shut down. - Ability to whitelist a USB device -- Ability to change the check interval (default: 25ms) +- Ability to change the check interval (default: 250ms) - Work perfectly in sleep mode (OS X) - Low memory consumption - No dependency except Python 2/3 From 8cf96801dd2cae7ae016b46198ef15f2af7f6408 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:07:01 +0200 Subject: [PATCH 21/77] Typo --- usbkill.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/usbkill.py b/usbkill.py index 6130f63..0c78923 100644 --- a/usbkill.py +++ b/usbkill.py @@ -250,9 +250,7 @@ def get_arg(config, name, gtype=''): configparser: Compatibility layer for Python 2/3 """ if sys.version_info[0] == 3: # Python 3 - - print(config['config']) - + section = config['config'] if gtype == '': From a7b37285863bbe3d55834e4c094e518bef720897 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:12:00 +0200 Subject: [PATCH 22/77] Attempt to fix https://github.com/hephaest0s/usbkill/issues/53 --- usbkill.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/usbkill.py b/usbkill.py index 0c78923..9825958 100644 --- a/usbkill.py +++ b/usbkill.py @@ -391,8 +391,13 @@ def startup_checks(): # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): - if subprocess.check_output(["/usr/bin/fdesetup", "isactive"]).decode('utf-8').strip() != "true": - print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") + + try: + if subprocess.check_output(["/usr/bin/fdesetup", "isactive"]).decode('utf-8').strip() != "true": + print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") + except subprocess.CalledProcessError as e: + print(e) + sys.exit(0) if not os.path.isdir("/etc/usbkill/"): os.mkdir("/etc/usbkill/") From f1c111cea87adf6b9657371ebd0f26ecd51f939d Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:20:30 +0200 Subject: [PATCH 23/77] Attempt to fix https://github.com/hephaest0s/usbkill/issues/53 --- usbkill.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/usbkill.py b/usbkill.py index 9825958..3d3a566 100644 --- a/usbkill.py +++ b/usbkill.py @@ -391,9 +391,8 @@ def startup_checks(): # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): - try: - if subprocess.check_output(["/usr/bin/fdesetup", "isactive"]).decode('utf-8').strip() != "true": + if subprocess.check_output(["/usr/bin/fdesetup", "status"]).decode('utf-8').strip() != "FileVault is On.": print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") except subprocess.CalledProcessError as e: print(e) From f3a6a0befe3215e0a67a1f743777d8b542d6b00a Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:25:12 +0200 Subject: [PATCH 24/77] Finally fixed https://github.com/hephaest0s/usbkill/issues/53 --- usbkill.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/usbkill.py b/usbkill.py index 3d3a566..2f550a3 100644 --- a/usbkill.py +++ b/usbkill.py @@ -392,11 +392,10 @@ def startup_checks(): # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): try: - if subprocess.check_output(["/usr/bin/fdesetup", "status"]).decode('utf-8').strip() != "FileVault is On.": - print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") - except subprocess.CalledProcessError as e: - print(e) - sys.exit(0) + # fdesetup return exit code 0 when true and 1 when false + subprocess.check_output(["/usr/bin/fdesetup", "isactive"]) + except subprocess.CalledProcessError: + print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") if not os.path.isdir("/etc/usbkill/"): os.mkdir("/etc/usbkill/") From 4ee9142baa6518639da1caa4355b1954feaffd6e Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:33:17 +0200 Subject: [PATCH 25/77] get_arg cleanup --- usbkill.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/usbkill.py b/usbkill.py index 2f550a3..31bf958 100644 --- a/usbkill.py +++ b/usbkill.py @@ -245,7 +245,7 @@ def load_settings(filename): # Read all lines of settings file config.read(filename) - def get_arg(config, name, gtype=''): + def get_arg(name, gtype=''): """ configparser: Compatibility layer for Python 2/3 """ @@ -275,13 +275,13 @@ def get_arg(config, name, gtype=''): # Build settings settings = dict({ - 'sleep_time' : get_arg(config, 'sleep', 'FLOAT'), - 'whitelist': jsonloads(get_arg(config, 'whitelist')), - 'kill_commands': jsonloads(get_arg(config, 'kill_commands')), - 'log_file': get_arg(config, 'log_file'), - 'remove_logs_and_settings' : get_arg(config, 'remove_logs_and_settings', 'BOOL'), - 'remove_passes' : get_arg(config, 'remove_passes', 'INT'), - 'do_sync' : get_arg(config, 'do_sync', 'BOOL') + 'sleep_time' : get_arg('sleep', 'FLOAT'), + 'whitelist': jsonloads(get_arg('whitelist')), + 'kill_commands': jsonloads(get_arg('kill_commands')), + 'log_file': get_arg('log_file'), + 'remove_logs_and_settings' : get_arg('remove_logs_and_settings', 'BOOL'), + 'remove_passes' : get_arg('remove_passes', 'INT'), + 'do_sync' : get_arg('do_sync', 'BOOL') }) return settings From 38c5a58caaa8f28305e9e8b51afc724611d00e33 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:47:01 +0200 Subject: [PATCH 26/77] Added indication to get the usbid on OS X --- settings.ini | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/settings.ini b/settings.ini index a74d6b2..89d57f3 100644 --- a/settings.ini +++ b/settings.ini @@ -1,9 +1,13 @@ [config] -# whitelist command lists the usb ids that you want whitelisted -# find the correct usbid for your trusted usb using the command 'lsusb' -# usbid looks something line 0123:9abc -# Be warned! other parties can copy your trusted usbid to another usb device! +# Whitelist command lists the USB ids that you want whitelisted +# How to get the correct usbid for your trusted USB device? +# BSD/Linux: run "lsusb", the usbid will looks like this: 0123:9abc +# Mac OS X: run "system_profiler SPUSBDataType" in the terminal and find the Vendor/Product ID, it will looks like this: +# > Product ID: 0x8403 +# > Vendor ID: 0x05ac (Apple Inc.) +# Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 8403:05ac +# Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: # whitelist = ['usbid1', 'usbid2'] whitelist = [] From bf9509f157a5407b9397692ad19688e2bc79903a Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:55:17 +0200 Subject: [PATCH 27/77] Secure wipe: Added "wipe" program - Added "wipe" program - Fix for srm --- usbkill.py | 28 ++++++++++++++++++++-------- 1 file changed, 20 insertions(+), 8 deletions(-) diff --git a/usbkill.py b/usbkill.py index 31bf958..70bdcdc 100644 --- a/usbkill.py +++ b/usbkill.py @@ -108,8 +108,8 @@ def kill_computer(settings): # Continue only if a shredder is available if settings['remove_program']['path'] != None: - # Remove settings & logs - if settings['remove_program']['path'].endswith("/srm") and (settings['remove_passes'] == 3 or settings['remove_passes'] == 7): + # srm support + if settings['remove_program']['path'].endswith("/srm"): def return_command(version): """ @@ -136,7 +136,8 @@ def return_command(version): # Return the right command for srm remove_command = return_command(settings['remove_program']['version']) - + + # shred support elif settings['remove_program']['path'].endswith("/shred"): # Use find @@ -148,7 +149,18 @@ def return_command(version): remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' else: # Fallback to 0-pass erasing remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' - + + # wipe support + elif settings['remove_program']['path'].endswith("/wipe"): + + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + remove_command = '-frsc -Q 7' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + remove_command = '-frsc -Q 3' + else: # Fallback to 0-pass erasing + remove_command = '-frsc -Q 0' + + # rm support elif settings['remove_program']['path'].endswith("/rm"): # Fallback to 0-pass erasing using rm @@ -377,10 +389,10 @@ def startup_checks(): REMOVE_PROGRAM = dict({ 'path': REMOVE_PROGRAMS[1] }) - #elif REMOVE_PROGRAMS[2] != None: # wipe - # REMOVE_PROGRAM = dict({ - # 'path': REMOVE_PROGRAMS[2] - # }) + elif REMOVE_PROGRAMS[2] != None: # wipe + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[2] + }) elif REMOVE_PROGRAMS[3] != None: # rm REMOVE_PROGRAM = dict({ 'path': REMOVE_PROGRAMS[3] From c402e765f556ce05e4405dada9e5e2ece0f64c1d Mon Sep 17 00:00:00 2001 From: Sabri Date: Sat, 9 May 2015 23:58:29 +0200 Subject: [PATCH 28/77] Typo --- settings.ini | 2 +- usbkill.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/settings.ini b/settings.ini index 89d57f3..1623079 100644 --- a/settings.ini +++ b/settings.ini @@ -6,7 +6,7 @@ # Mac OS X: run "system_profiler SPUSBDataType" in the terminal and find the Vendor/Product ID, it will looks like this: # > Product ID: 0x8403 # > Vendor ID: 0x05ac (Apple Inc.) -# Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 8403:05ac +# Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 05ac:8403 # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: # whitelist = ['usbid1', 'usbid2'] diff --git a/usbkill.py b/usbkill.py index 70bdcdc..2642c9f 100644 --- a/usbkill.py +++ b/usbkill.py @@ -64,7 +64,7 @@ def which(program): """ Test if an executable exist in Python - http://stackoverflow.com/a/377028 + -> http://stackoverflow.com/a/377028 """ def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) From 576aaacb7218bf4ca91d9b1e9a7fb23b30e1ba7c Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 00:16:40 +0200 Subject: [PATCH 29/77] Fixed some issues with the Configuration File Manager --- settings.ini | 48 ------------------------------------------------ usbkill.py | 10 ++++++---- 2 files changed, 6 insertions(+), 52 deletions(-) delete mode 100644 settings.ini diff --git a/settings.ini b/settings.ini deleted file mode 100644 index 1623079..0000000 --- a/settings.ini +++ /dev/null @@ -1,48 +0,0 @@ -[config] - -# Whitelist command lists the USB ids that you want whitelisted -# How to get the correct usbid for your trusted USB device? -# BSD/Linux: run "lsusb", the usbid will looks like this: 0123:9abc -# Mac OS X: run "system_profiler SPUSBDataType" in the terminal and find the Vendor/Product ID, it will looks like this: -# > Product ID: 0x8403 -# > Vendor ID: 0x05ac (Apple Inc.) -# Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 05ac:8403 -# Be warned! Other parties can copy your trusted usbid to another usb device! -# use whitelist command and single space separation as follows: -# whitelist = ['usbid1', 'usbid2'] -whitelist = [] - -# allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: -sleep = 0.25 - -# Custom kill commands. -# This is where you want to release volumes, etc. -# If you don't know what to place here, just make sure you use full disk encryption -# Commands will run in order and as root -# Example shred: -# shred can take a long time for larger files! -# only use for tiny files! -# if you have an ssd then set -n=0 (only hdd need more) -# kill_commands = [ 'shred -f -n 0 -z -u FILENAME', 'shred -f -n 0 -z -u OTHERFILE' ] -kill_commands = [] - -# Log file location: -log_file = /var/log/usbkill/usbkill.log - -# Remove log (folder) and settings (folder) upon kill? -# This might be usefull if you only encrypt portions of your disk (home folder or volumes). -# Make sure to sync the system (using do_sync=True) if you want to remove logs and settings -# (True/False) -remove_logs_and_settings = False - -# How many passes should we use to remove the files? -# Note: If you have an SSD then set 0 (only HDD need more) -# Note: If 3 passes is not available on your OS then usbkill will fallback to 7 -# (0, 3 or 7) -remove_passes = 3 - -# Should usbkill sync the file system for you? -# This should not be a problem on most computers. -# Sync will save some of your work to disk before killing your computer. -do_sync = True - diff --git a/usbkill.py b/usbkill.py index 2642c9f..427d4bb 100644 --- a/usbkill.py +++ b/usbkill.py @@ -412,15 +412,17 @@ def startup_checks(): if not os.path.isdir("/etc/usbkill/"): os.mkdir("/etc/usbkill/") + # Configuration File Manager # The following does: # 1 if no settings in /etc/, then copy settings to /etc/ and remove setting.ini in current folder # 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder if not os.path.isfile(SETTINGS_FILE) or dev: - if not os.path.isfile("settings.ini"): - sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + os.getcwd() + "/\n") - os.system("cp settings.ini " + SETTINGS_FILE) + sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' + if not os.path.isfile(sources_path + "settings.ini"): + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") + os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) if not dev: - os.remove("settings.ini") + os.remove(sources_path + "settings.ini") # Load settings settings = load_settings(SETTINGS_FILE) From f2d8fbc738bbcb9f67c69b0655b27050c146008f Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 00:26:06 +0200 Subject: [PATCH 30/77] Revert "Fixed some issues with the Configuration File Manager" This reverts commit 576aaacb7218bf4ca91d9b1e9a7fb23b30e1ba7c. --- settings.ini | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ usbkill.py | 10 ++++------ 2 files changed, 52 insertions(+), 6 deletions(-) create mode 100644 settings.ini diff --git a/settings.ini b/settings.ini new file mode 100644 index 0000000..1623079 --- /dev/null +++ b/settings.ini @@ -0,0 +1,48 @@ +[config] + +# Whitelist command lists the USB ids that you want whitelisted +# How to get the correct usbid for your trusted USB device? +# BSD/Linux: run "lsusb", the usbid will looks like this: 0123:9abc +# Mac OS X: run "system_profiler SPUSBDataType" in the terminal and find the Vendor/Product ID, it will looks like this: +# > Product ID: 0x8403 +# > Vendor ID: 0x05ac (Apple Inc.) +# Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 05ac:8403 +# Be warned! Other parties can copy your trusted usbid to another usb device! +# use whitelist command and single space separation as follows: +# whitelist = ['usbid1', 'usbid2'] +whitelist = [] + +# allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: +sleep = 0.25 + +# Custom kill commands. +# This is where you want to release volumes, etc. +# If you don't know what to place here, just make sure you use full disk encryption +# Commands will run in order and as root +# Example shred: +# shred can take a long time for larger files! +# only use for tiny files! +# if you have an ssd then set -n=0 (only hdd need more) +# kill_commands = [ 'shred -f -n 0 -z -u FILENAME', 'shred -f -n 0 -z -u OTHERFILE' ] +kill_commands = [] + +# Log file location: +log_file = /var/log/usbkill/usbkill.log + +# Remove log (folder) and settings (folder) upon kill? +# This might be usefull if you only encrypt portions of your disk (home folder or volumes). +# Make sure to sync the system (using do_sync=True) if you want to remove logs and settings +# (True/False) +remove_logs_and_settings = False + +# How many passes should we use to remove the files? +# Note: If you have an SSD then set 0 (only HDD need more) +# Note: If 3 passes is not available on your OS then usbkill will fallback to 7 +# (0, 3 or 7) +remove_passes = 3 + +# Should usbkill sync the file system for you? +# This should not be a problem on most computers. +# Sync will save some of your work to disk before killing your computer. +do_sync = True + diff --git a/usbkill.py b/usbkill.py index 427d4bb..2642c9f 100644 --- a/usbkill.py +++ b/usbkill.py @@ -412,17 +412,15 @@ def startup_checks(): if not os.path.isdir("/etc/usbkill/"): os.mkdir("/etc/usbkill/") - # Configuration File Manager # The following does: # 1 if no settings in /etc/, then copy settings to /etc/ and remove setting.ini in current folder # 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder if not os.path.isfile(SETTINGS_FILE) or dev: - sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' - if not os.path.isfile(sources_path + "settings.ini"): - sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") - os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) + if not os.path.isfile("settings.ini"): + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + os.getcwd() + "/\n") + os.system("cp settings.ini " + SETTINGS_FILE) if not dev: - os.remove(sources_path + "settings.ini") + os.remove("settings.ini") # Load settings settings = load_settings(SETTINGS_FILE) From af0449c197b6a6e1b1165f95394ffd00ff421139 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 00:26:52 +0200 Subject: [PATCH 31/77] Re-adding settings.ini, sorry --- usbkill.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/usbkill.py b/usbkill.py index 2642c9f..427d4bb 100644 --- a/usbkill.py +++ b/usbkill.py @@ -412,15 +412,17 @@ def startup_checks(): if not os.path.isdir("/etc/usbkill/"): os.mkdir("/etc/usbkill/") + # Configuration File Manager # The following does: # 1 if no settings in /etc/, then copy settings to /etc/ and remove setting.ini in current folder # 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder if not os.path.isfile(SETTINGS_FILE) or dev: - if not os.path.isfile("settings.ini"): - sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + os.getcwd() + "/\n") - os.system("cp settings.ini " + SETTINGS_FILE) + sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' + if not os.path.isfile(sources_path + "settings.ini"): + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") + os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) if not dev: - os.remove("settings.ini") + os.remove(sources_path + "settings.ini") # Load settings settings = load_settings(SETTINGS_FILE) From 9e0671252ec1a5349b2553c7d5872854710e47eb Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 00:48:17 +0200 Subject: [PATCH 32/77] Cleanup --- usbkill.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/usbkill.py b/usbkill.py index 427d4bb..aeca749 100644 --- a/usbkill.py +++ b/usbkill.py @@ -28,14 +28,12 @@ import os, sys, signal from time import sleep from datetime import datetime - +from json import loads as jsonloads if sys.version_info[0] == 3: import configparser else: import ConfigParser -from json import loads as jsonloads - # Shell separator SHELL_SEPARATOR = ' && ' @@ -284,7 +282,7 @@ def get_arg(name, gtype=''): return config.getint('config', name) elif gtype == 'BOOL': return config.getboolean('config', name) - + # Build settings settings = dict({ 'sleep_time' : get_arg('sleep', 'FLOAT'), From f011eb0305fc6b915d37df3b05ad191adb30e778 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 00:48:36 +0200 Subject: [PATCH 33/77] Fix JSON decoding errors --- settings.ini | 6 +++--- usbkill.py | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/settings.ini b/settings.ini index 1623079..db5dad9 100644 --- a/settings.ini +++ b/settings.ini @@ -9,8 +9,8 @@ # Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 05ac:8403 # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: -# whitelist = ['usbid1', 'usbid2'] -whitelist = [] +# whitelist = ["usbid1", "usbid2"] +whitelist = ["usbid1", "usbid2"] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: sleep = 0.25 @@ -23,7 +23,7 @@ sleep = 0.25 # shred can take a long time for larger files! # only use for tiny files! # if you have an ssd then set -n=0 (only hdd need more) -# kill_commands = [ 'shred -f -n 0 -z -u FILENAME', 'shred -f -n 0 -z -u OTHERFILE' ] +# kill_commands = ["shred -f -n 0 -z -u FILENAME", "shred -f -n 0 -z -u OTHERFILE"] kill_commands = [] # Log file location: diff --git a/usbkill.py b/usbkill.py index aeca749..b8985cc 100644 --- a/usbkill.py +++ b/usbkill.py @@ -286,8 +286,8 @@ def get_arg(name, gtype=''): # Build settings settings = dict({ 'sleep_time' : get_arg('sleep', 'FLOAT'), - 'whitelist': jsonloads(get_arg('whitelist')), - 'kill_commands': jsonloads(get_arg('kill_commands')), + 'whitelist': jsonloads(get_arg('whitelist').strip()), + 'kill_commands': jsonloads(get_arg('kill_commands').strip()), 'log_file': get_arg('log_file'), 'remove_logs_and_settings' : get_arg('remove_logs_and_settings', 'BOOL'), 'remove_passes' : get_arg('remove_passes', 'INT'), From 082eebd615c38164c118d3f6cc428aa3f6df50ea Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 01:09:04 +0200 Subject: [PATCH 34/77] As it is always the same pass, wipe is not DoE/Dod compliant --- usbkill.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/usbkill.py b/usbkill.py index b8985cc..629cdad 100644 --- a/usbkill.py +++ b/usbkill.py @@ -151,9 +151,9 @@ def return_command(version): # wipe support elif settings['remove_program']['path'].endswith("/wipe"): - if settings['remove_passes'] == 7: # US Dod compliant 7-pass + if settings['remove_passes'] == 7: # Probably not US Dod compliant 7-pass remove_command = '-frsc -Q 7' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + elif settings['remove_passes'] == 3: # Probably not US DoE compliant 3-pass remove_command = '-frsc -Q 3' else: # Fallback to 0-pass erasing remove_command = '-frsc -Q 0' From fb28832361fd5ec9fce65a62b8f8c89a5c030920 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 01:11:35 +0200 Subject: [PATCH 35/77] Fixed --dev mode --- usbkill.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/usbkill.py b/usbkill.py index 629cdad..ff16bb9 100644 --- a/usbkill.py +++ b/usbkill.py @@ -413,8 +413,9 @@ def startup_checks(): # Configuration File Manager # The following does: # 1 if no settings in /etc/, then copy settings to /etc/ and remove setting.ini in current folder - # 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder - if not os.path.isfile(SETTINGS_FILE) or dev: + # Nonsense because running one time without --dev will bug the --dev mode as the non-dev mode remove the setting.ini file: + # -> 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder + if not os.path.isfile(SETTINGS_FILE): sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' if not os.path.isfile(sources_path + "settings.ini"): sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") From 5d23c5ecde434b802496f6d7a0dac25fab1a8502 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 10 May 2015 15:41:22 +0000 Subject: [PATCH 36/77] Update settings.ini --- settings.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.ini b/settings.ini index db5dad9..098f3ad 100644 --- a/settings.ini +++ b/settings.ini @@ -10,7 +10,7 @@ # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: # whitelist = ["usbid1", "usbid2"] -whitelist = ["usbid1", "usbid2"] +whitelist = [] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: sleep = 0.25 From a739ca3f4791782a0f79caf6eebb41c09f3a6e6f Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 10 May 2015 16:37:02 +0000 Subject: [PATCH 37/77] Significan rollback, sorry --- usbkill.py | 282 +++++++++++++++-------------------------------------- 1 file changed, 77 insertions(+), 205 deletions(-) diff --git a/usbkill.py b/usbkill.py index ff16bb9..0fe96bb 100644 --- a/usbkill.py +++ b/usbkill.py @@ -28,14 +28,6 @@ import os, sys, signal from time import sleep from datetime import datetime -from json import loads as jsonloads -if sys.version_info[0] == 3: - import configparser -else: - import ConfigParser - -# Shell separator -SHELL_SEPARATOR = ' && ' # Get the current platform CURRENT_PLATFORM = platform.system().upper() @@ -44,6 +36,9 @@ if CURRENT_PLATFORM.startswith("DARWIN"): import plistlib +# Shell separator +SHELL_SEPARATOR = ' && ' + # We compile this function beforehand for efficiency. DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] @@ -59,26 +54,6 @@ In order to be able to shutdown the computer, this program needs to run as root. """ -def which(program): - """ - Test if an executable exist in Python - -> http://stackoverflow.com/a/377028 - """ - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - def log(settings, msg): log_file = settings['log_file'] @@ -101,81 +76,8 @@ def kill_computer(settings): os.system(command) # Remove logs and settings - if settings['remove_logs_and_settings']: - - # Continue only if a shredder is available - if settings['remove_program']['path'] != None: - - # srm support - if settings['remove_program']['path'].endswith("/srm"): - - def return_command(version): - """ - Return the right command based on the version of srm and the number of passes defined in settings - """ - if version[1] == '2': # Check if this is an 1.2.x version - if version[2] <= '10': - # srm 1.2.10 introduce new commands which have 3-pass (-doe -E) - if settings['remove_passes'] == 7: # US Dod compliant 7-pass - return '--force --recursive --dod' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass - return '--force --recursive --doe' - elif version[2] == '9': - # srm 1.2.9 moved --medium to -dod (-D) - if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass - return '--force --recursive --dod' - elif version[2] <= '8': - # srm 1.2.8 and above (used in OS X Yosemite) support only 7/1-pass - if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass - return '--force --recursive --medium' - - # Fallback to 1-pass erasing - return '--force --recursive --simple' - - # Return the right command for srm - remove_command = return_command(settings['remove_program']['version']) - - # shred support - elif settings['remove_program']['path'].endswith("/shred"): - - # Use find - custom_start = 'find ' - - if settings['remove_passes'] == 7: # US Dod compliant 7-pass - remove_command = '-depth -type f -exec shred -f -n 7 -z -u {} \;' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass - remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' - else: # Fallback to 0-pass erasing - remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' - - # wipe support - elif settings['remove_program']['path'].endswith("/wipe"): - - if settings['remove_passes'] == 7: # Probably not US Dod compliant 7-pass - remove_command = '-frsc -Q 7' - elif settings['remove_passes'] == 3: # Probably not US DoE compliant 3-pass - remove_command = '-frsc -Q 3' - else: # Fallback to 0-pass erasing - remove_command = '-frsc -Q 0' - - # rm support - elif settings['remove_program']['path'].endswith("/rm"): - - # Fallback to 0-pass erasing using rm - remove_command = '-rf' - - # Set custom_start empty if not set - try: - custom_start - except UnboundLocalError: - custom_start = '' - - # Build the command - remove_command = SHELL_SEPARATOR + custom_start + settings['remove_program']['path'] + ' ' + remove_command + ' ' - remove_command = remove_command.lstrip(SHELL_SEPARATOR) + os.path.dirname(settings['log_file']) + remove_command + os.path.dirname(SETTINGS_FILE) - - # Execute the command - os.system(remove_command) + if settings['melt_usbkill']: + pass if settings['do_sync']: # Sync the filesystem to save recent changes @@ -199,99 +101,103 @@ def return_command(version): # Exit the process to prevent executing twice (or more) all commands sys.exit(0) +def lsusb_darwin(): + # Use OS X system_profiler (native and 60% faster than lsusb port) + df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) + if sys.version_info[0] == 2: + df = plistlib.readPlistFromString(df) + elif sys.version_info[0] == 3: + df = plistlib.loads(df) + + def check_inside(result, devices): + """ + I suspect this function can become more readable. + Function currently depends on a side effect, which is not necessary. + """ + # Do not take devices with Built-in_Device=Yes + try: + result["Built-in_Device"] + except KeyError: + + # Check if vendor_id/product_id is available for this one + try: + assert "vendor_id" in result and "product_id" in result + # Append to the list of devices + devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) + except AssertionError: {} + + # Check if there is items inside + try: + # Looks like, do the while again + for result_deep in result["_items"]: + # Check what's inside the _items array + check_inside(result_deep) + + except KeyError: {} + + # Run the loop + devices = [] + for result in df[0]["_items"]: + check_inside(result, devices) + return devices + def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids if CURRENT_PLATFORM.startswith("DARWIN"): # Use OS X system_profiler (native and 60% faster than lsusb port) - df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) - if sys.version_info[0] == 2: - df = plistlib.readPlistFromString(df) - elif sys.version_info[0] == 3: - df = plistlib.loads(df) - - devices = [] - def check_inside(result): - """ - I suspect this function can become more readable. - Function currently depends on a side effect, which is not necessary. - """ - # Do not take devices with Built-in_Device=Yes - try: - result["Built-in_Device"] - except KeyError: - - # Check if vendor_id/product_id is available for this one - try: - assert "vendor_id" in result and "product_id" in result - # Append to the list of devices - devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) - except AssertionError: {} - - # Check if there is items inside - try: - # Looks like, do the while again - for result_deep in result["_items"]: - # Check what's inside the _items array - check_inside(result_deep) - - except KeyError: {} - - # Run the loop - for result in df[0]["_items"]: - check_inside(result) - - return devices + return lsusb_darwin() else: # Use lsusb on linux and bsd return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def load_settings(filename): - + # Libraries that are only needed in this function: + from json import loads as jsonloads if sys.version_info[0] == 3: - config = configparser.ConfigParser() + import configparser else: - config = ConfigParser.ConfigParser() - + import ConfigParser as configparser + + config = configparser.ConfigParser() + # Read all lines of settings file config.read(filename) - def get_arg(name, gtype=''): - """ - configparser: Compatibility layer for Python 2/3 - """ - if sys.version_info[0] == 3: # Python 3 - + if sys.version_info[0] == 3: + # Python3 + def get_setting(name, gtype=''): + """ + configparser: Compatibility layer for Python 2/3 + Function currently depends on a side effect, which is not necessary. + """ section = config['config'] - - if gtype == '': - return section[name] - elif gtype == 'FLOAT': + if gtype == 'FLOAT': return section.getfloat(name) elif gtype == 'INT': return section.getint(name) elif gtype == 'BOOL': return section.getboolean(name) - - else: # Python 2 - - if gtype == '': - return config.get('config', name) - elif gtype == 'FLOAT': + return section[name] + else: + #Python2 + def get_setting(name, gtype=''): + if gtype == 'FLOAT': return config.getfloat('config', name) elif gtype == 'INT': return config.getint('config', name) elif gtype == 'BOOL': return config.getboolean('config', name) + return config.get('config', name) # Build settings settings = dict({ - 'sleep_time' : get_arg('sleep', 'FLOAT'), - 'whitelist': jsonloads(get_arg('whitelist').strip()), - 'kill_commands': jsonloads(get_arg('kill_commands').strip()), - 'log_file': get_arg('log_file'), - 'remove_logs_and_settings' : get_arg('remove_logs_and_settings', 'BOOL'), - 'remove_passes' : get_arg('remove_passes', 'INT'), - 'do_sync' : get_arg('do_sync', 'BOOL') + 'sleep_time' : get_setting('sleep', 'FLOAT'), + 'whitelist': jsonloads(get_setting('whitelist').strip()), + 'kill_commands': jsonloads(get_setting('kill_commands').strip()), + 'log_file': get_setting('log_file'), + 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), + 'remove_passes' : get_setting('remove_passes', 'INT'), + 'do_sync' : get_setting('do_sync', 'BOOL') }) return settings @@ -370,35 +276,6 @@ def startup_checks(): if not os.geteuid() == 0: sys.exit("\n[ERROR] This program needs to run as root.\n") - # Determine which secure tool should we use to remove files - # If none is available, fallback to "rm" - REMOVE_PROGRAMS = [ - which('srm'), # - which('shred'),# - which('wipe'), # - which('rm') # - ] - if REMOVE_PROGRAMS[0] != None: # srm - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[0], - 'version': re.findall("([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,2})+", subprocess.check_output(REMOVE_PROGRAMS[0] + ' --version', shell=True).decode('utf-8').strip())[0] - }) - elif REMOVE_PROGRAMS[1] != None: # shred - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[1] - }) - elif REMOVE_PROGRAMS[2] != None: # wipe - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[2] - }) - elif REMOVE_PROGRAMS[3] != None: # rm - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[3] - }) - else: - REMOVE_PROGRAM = None - print('[WARNING] Files removing has been disabled because no shredder has been found! Please install srm, shred, wipe or rm!') - # Warn the user if he does not have FileVault if CURRENT_PLATFORM.startswith("DARWIN"): try: @@ -410,12 +287,9 @@ def startup_checks(): if not os.path.isdir("/etc/usbkill/"): os.mkdir("/etc/usbkill/") - # Configuration File Manager - # The following does: - # 1 if no settings in /etc/, then copy settings to /etc/ and remove setting.ini in current folder - # Nonsense because running one time without --dev will bug the --dev mode as the non-dev mode remove the setting.ini file: - # -> 2 if you are dev (--dev), then always copy settings.ini to /etc/ and keep settings.ini in current folder - if not os.path.isfile(SETTINGS_FILE): + # On first time use copy settings.ini to /etc/usebkill/settings.ini + # If dev-mode, always copy and don't remove old settings + if not os.path.isfile(SETTINGS_FILE) or dev: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' if not os.path.isfile(sources_path + "settings.ini"): sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") @@ -426,7 +300,6 @@ def startup_checks(): # Load settings settings = load_settings(SETTINGS_FILE) settings['killer'] = killer - settings['remove_program'] = REMOVE_PROGRAM # Make sure there is a logging folder log_folder = os.path.dirname(settings['log_file']) @@ -436,7 +309,6 @@ def startup_checks(): return settings if __name__=="__main__": - # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) From 1007e342e903ca1a30b2e2f669deffd48d594ed0 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 10 May 2015 16:37:28 +0000 Subject: [PATCH 38/77] Update settings.ini --- settings.ini | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/settings.ini b/settings.ini index 098f3ad..c9cfd9f 100644 --- a/settings.ini +++ b/settings.ini @@ -33,12 +33,13 @@ log_file = /var/log/usbkill/usbkill.log # This might be usefull if you only encrypt portions of your disk (home folder or volumes). # Make sure to sync the system (using do_sync=True) if you want to remove logs and settings # (True/False) -remove_logs_and_settings = False +# [[[ not implemented yet ]]] +melt_usbkill = False # How many passes should we use to remove the files? # Note: If you have an SSD then set 0 (only HDD need more) -# Note: If 3 passes is not available on your OS then usbkill will fallback to 7 -# (0, 3 or 7) +# Note: If 3 passes is not available on your OS then usbkill will fall back to 7 +# (0, 3) remove_passes = 3 # Should usbkill sync the file system for you? From ae353824abe84502abaf5dfcee38bc1df2a8b60c Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 19:17:27 +0200 Subject: [PATCH 39/77] Typos --- usbkill.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/usbkill.py b/usbkill.py index 0fe96bb..e29e354 100644 --- a/usbkill.py +++ b/usbkill.py @@ -102,7 +102,8 @@ def kill_computer(settings): sys.exit(0) def lsusb_darwin(): - # Use OS X system_profiler (native and 60% faster than lsusb port) + # Use OS X system_profiler + # Native and 60% faster than the OS X lsusb port df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) if sys.version_info[0] == 2: df = plistlib.readPlistFromString(df) @@ -144,14 +145,14 @@ def check_inside(result, devices): def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids if CURRENT_PLATFORM.startswith("DARWIN"): - # Use OS X system_profiler (native and 60% faster than lsusb port) + # Use OS X system_profiler return lsusb_darwin() else: - # Use lsusb on linux and bsd + # Use lsusb on Linux and BSD return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def load_settings(filename): - # Libraries that are only needed in this function: + # Libraries that are only needed in this function from json import loads as jsonloads if sys.version_info[0] == 3: import configparser @@ -179,7 +180,7 @@ def get_setting(name, gtype=''): return section.getboolean(name) return section[name] else: - #Python2 + # Python2 def get_setting(name, gtype=''): if gtype == 'FLOAT': return config.getfloat('config', name) From eee664be25530394e1e192a28f6aead793f5aa25 Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 19:18:48 +0200 Subject: [PATCH 40/77] Added the removed secure files wipe in a separate file So the we can start to create the class --- remove.py | 133 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 remove.py diff --git a/remove.py b/remove.py new file mode 100644 index 0000000..394fad5 --- /dev/null +++ b/remove.py @@ -0,0 +1,133 @@ +def which(program): + """ + Test if an executable exist in Python + -> http://stackoverflow.com/a/377028 + """ + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) + + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return program + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return exe_file + return None + +# Remove logs and settings +if settings['remove_logs_and_settings']: + + # Continue only if a shredder is available + if settings['remove_program']['path'] != None: + + # srm support + if settings['remove_program']['path'].endswith("/srm"): + + def return_command(version): + """ + Return the right command based on the version of srm and the number of passes defined in settings + """ + if version[1] == '2': # Check if this is an 1.2.x version + if version[2] <= '10': + # srm 1.2.10 introduce new commands which have 3-pass (-doe -E) + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + return '--force --recursive --dod' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + return '--force --recursive --doe' + elif version[2] == '9': + # srm 1.2.9 moved --medium to -dod (-D) + if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass + return '--force --recursive --dod' + elif version[2] <= '8': + # srm 1.2.8 and above (used in OS X Yosemite) support only 7/1-pass + if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass + return '--force --recursive --medium' + + # Fallback to 1-pass erasing + return '--force --recursive --simple' + + # Return the right command for srm + remove_command = return_command(settings['remove_program']['version']) + + # shred support + elif settings['remove_program']['path'].endswith("/shred"): + + # Use find + custom_start = 'find ' + + if settings['remove_passes'] == 7: # US Dod compliant 7-pass + remove_command = '-depth -type f -exec shred -f -n 7 -z -u {} \;' + elif settings['remove_passes'] == 3: # US DoE compliant 3-pass + remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' + else: # Fallback to 0-pass erasing + remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' + + # wipe support + elif settings['remove_program']['path'].endswith("/wipe"): + + if settings['remove_passes'] == 7: # Probably not US Dod compliant 7-pass + remove_command = '-frsc -Q 7' + elif settings['remove_passes'] == 3: # Probably not US DoE compliant 3-pass + remove_command = '-frsc -Q 3' + else: # Fallback to 0-pass erasing + remove_command = '-frsc -Q 0' + + # rm support + elif settings['remove_program']['path'].endswith("/rm"): + + # Fallback to 0-pass erasing using rm + remove_command = '-rf' + + # Set custom_start empty if not set + try: + custom_start + except UnboundLocalError: + custom_start = '' + + # Build the command + remove_command = SHELL_SEPARATOR + custom_start + settings['remove_program']['path'] + ' ' + remove_command + ' ' + remove_command = remove_command.lstrip(SHELL_SEPARATOR) + os.path.dirname(settings['log_file']) + remove_command + os.path.dirname(SETTINGS_FILE) + remove_command + __file__ + + # If the directory where the script is start with "usbkill" (e.g.: usbkill, usbkill-dev, usbkill-master...) + if SOURCES_PATH.endswith('usbkill') + remove_command = SOURCES_PATH + + # Execute the command + os.system(remove_command) + + + + + + # Determine which secure tool should we use to remove files + # If none is available, fallback to "rm" + REMOVE_PROGRAMS = [ + which('srm'), # + which('shred'),# + which('wipe'), # + which('rm') # + ] + if REMOVE_PROGRAMS[0] != None: # srm + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[0], + 'version': re.findall("([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,2})+", subprocess.check_output(REMOVE_PROGRAMS[0] + ' --version', shell=True).decode('utf-8').strip())[0] + }) + elif REMOVE_PROGRAMS[1] != None: # shred + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[1] + }) + elif REMOVE_PROGRAMS[2] != None: # wipe + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[2] + }) + elif REMOVE_PROGRAMS[3] != None: # rm + REMOVE_PROGRAM = dict({ + 'path': REMOVE_PROGRAMS[3] + }) + else: + REMOVE_PROGRAM = None + print('[WARNING] Files removing has been disabled because no shredder has been found! Please install srm, shred, wipe or rm!') From 690a17e342089c5f0dcee31d8b7d85aaba70d34d Mon Sep 17 00:00:00 2001 From: Sabri Date: Sun, 10 May 2015 19:37:37 +0200 Subject: [PATCH 41/77] Crash fix for OS X --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index e29e354..a02ff4d 100644 --- a/usbkill.py +++ b/usbkill.py @@ -132,7 +132,7 @@ def check_inside(result, devices): # Looks like, do the while again for result_deep in result["_items"]: # Check what's inside the _items array - check_inside(result_deep) + check_inside(result_deep, devices) except KeyError: {} From 081d8d02867ae13f40b922225c49b3d9e06e9c41 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 11 May 2015 11:47:17 +0000 Subject: [PATCH 42/77] Update settings.ini --- settings.ini | 45 ++++++++++++++++++++++++++------------------- 1 file changed, 26 insertions(+), 19 deletions(-) diff --git a/settings.ini b/settings.ini index c9cfd9f..0b88c25 100644 --- a/settings.ini +++ b/settings.ini @@ -15,35 +15,42 @@ whitelist = [] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: sleep = 0.25 -# Custom kill commands. -# This is where you want to release volumes, etc. -# If you don't know what to place here, just make sure you use full disk encryption -# Commands will run in order and as root -# Example shred: -# shred can take a long time for larger files! -# only use for tiny files! -# if you have an ssd then set -n=0 (only hdd need more) -# kill_commands = ["shred -f -n 0 -z -u FILENAME", "shred -f -n 0 -z -u OTHERFILE"] -kill_commands = [] - # Log file location: log_file = /var/log/usbkill/usbkill.log -# Remove log (folder) and settings (folder) upon kill? +# Remove log (folder) and settings (folder) and usbkill program (folder) upon kill? # This might be usefull if you only encrypt portions of your disk (home folder or volumes). -# Make sure to sync the system (using do_sync=True) if you want to remove logs and settings +# Make sure to sync the system (using do_sync=True) if this is a critical feature for you. # (True/False) -# [[[ not implemented yet ]]] melt_usbkill = False -# How many passes should we use to remove the files? -# Note: If you have an SSD then set 0 (only HDD need more) -# Note: If 3 passes is not available on your OS then usbkill will fall back to 7 -# (0, 3) -remove_passes = 3 +# What command should be used to remove files? Add the arguments you want to be used +# The developer of usbkill advises srm or shred +# Example 'srm -zr' +remove_file_command = srm -zr + +# What files should be removed upon a kill? +# Provide absolute paths to the files (paths that start with '/' or '~'). +# Use " not ' to define the strings, e.g.: +# files_to_remove = ["~/Desktop/contacts.txt", "~/Desktop/dpr_journal.txt"] +files_to_remove = [] + +# What folders should be removed upon a kill? +# Provide absolute paths to the files (paths that start with '/' or '~'). +# Content in folders will be removed recursively +# Use " not ' to define the strings, e.g.: +# folders_to_remove = ["~/Desktop/sensitive/", "~/Desktop/dpr_journal_entries/"] +folders_to_remove = [ ] # Should usbkill sync the file system for you? # This should not be a problem on most computers. # Sync will save some of your work to disk before killing your computer. do_sync = True +# Custom kill commands that can not be specified using above described mechanisms. +# This is where you want to release volumes, etc. +# These commands will run in order and as root, as the last commands before a possible sync and shut down. +# Use " not ' to define the strings, e.g.: +# kill_commands = [ "bash ~/scripts/destroy.sh" ] +kill_commands = [ ] + From 18c7784a59160f3b5c46f753aa7fc3e92d496e71 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 11 May 2015 11:54:20 +0000 Subject: [PATCH 43/77] File shredding now supported, new cli arguments User is now expected to define a shred option in the settings.ini file. e.g. `srm -zr' I prefer this option as opposed to programmatically finding an executable on the user system. - added setting option for removal of files - added setting option for removal of folders - melt_usbkill is now supported, implemented by adding to remove_files and remove_folders. Removes program folder if folder.upper().startswith('USB') - removed --dev option - added --no-shut-down option. This prevents shutdown, but execute all the (dangerous) commands that user specified. - added --cs option. cs stands for copy settings and copies the settings.ini to /etc/usbkill/settings.ini --- usbkill.py | 143 +++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 100 insertions(+), 43 deletions(-) diff --git a/usbkill.py b/usbkill.py index 0fe96bb..265bd8e 100644 --- a/usbkill.py +++ b/usbkill.py @@ -36,9 +36,6 @@ if CURRENT_PLATFORM.startswith("DARWIN"): import plistlib -# Shell separator -SHELL_SEPARATOR = ' && ' - # We compile this function beforehand for efficiency. DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] @@ -57,7 +54,7 @@ def log(settings, msg): log_file = settings['log_file'] - contents = '\n{0} {1}\nCurrent state:'.format(str(datetime.now()), msg) + contents = '\n{0} {1}\nCurrent state:\n'.format(str(datetime.now()), msg) with open(log_file, 'a+') as log: log.write(contents) @@ -67,17 +64,39 @@ def log(settings, msg): else: os.system("lsusb >> " + log_file) +def shred(settings): + shredder = settings['remove_file_command'] + + # List logs and settings to be removed + if settings['melt_usbkill']: + settings['folders_to_remove'].append(os.path.dirname(settings['log_file'])) + settings['folders_to_remove'].append(os.path.dirname(SETTINGS_FILE)) + usbkill_folder = os.path.dirname(os.path.realpath(__file__)) + if usbkill_folder.upper().startswith('USB'): + settings['folders_to_remove'].append(usbkill_folder) + else: + settings['files_to_remove'].append(os.path.realpath(__file__)) + + # Remove files + for _file in settings['files_to_remove']: + os.system(shredder + _file) + + # Remove files in folders and the folders + for folder in settings['folders_to_remove']: + os.system("find " + folder + " -exec " + shredder + " {} \;") + os.system("rm -rf " + folder) # this is in case the shredder doesn't handle folders (e.g. shred) + def kill_computer(settings): # Log what is happening: - log(settings, "Detected a USB change. Dumping the list of connected devices and killing the computer...") + if not settings['melt_usbkill']: # No need to spend time on logging if logs will be removed + log(settings, "Detected a USB change. Dumping the list of connected devices and killing the computer...") + + # Shred as specified in settings + shred(settings) # Execute kill commands in order. for command in settings['kill_commands']: os.system(command) - - # Remove logs and settings - if settings['melt_usbkill']: - pass if settings['do_sync']: # Sync the filesystem to save recent changes @@ -87,16 +106,17 @@ def kill_computer(settings): # This will still allow for syncing in most cases. sleep(0.05) - # Finally poweroff computer immediately - if CURRENT_PLATFORM.startswith("DARWIN"): - # OS X (Darwin) - Will halt ungracefully, without signaling apps - os.system("killall Finder" + SHELL_SEPARATOR + "killall loginwindow" + SHELL_SEPARATOR + "halt -q") - elif CURRENT_PLATFORM.endswith("BSD"): - # BSD-based systems - Will shutdown - os.system("shutdown -h now") - else: - # Linux-based systems - Will shutdown - os.system("poweroff -f") + if settings['shut_down']: # Use argument --no-shut-down to prevent a shutdown. + # Finally poweroff computer immediately + if CURRENT_PLATFORM.startswith("DARWIN"): + # OS X (Darwin) - Will halt ungracefully, without signaling apps + os.system("killall Finder && killall loginwindow && halt -q") + elif CURRENT_PLATFORM.endswith("BSD"): + # BSD-based systems - Will shutdown + os.system("shutdown -h now") + else: + # Linux-based systems - Will shutdown + os.system("poweroff -f") # Exit the process to prevent executing twice (or more) all commands sys.exit(0) @@ -112,7 +132,6 @@ def lsusb_darwin(): def check_inside(result, devices): """ I suspect this function can become more readable. - Function currently depends on a side effect, which is not necessary. """ # Do not take devices with Built-in_Device=Yes try: @@ -131,7 +150,7 @@ def check_inside(result, devices): # Looks like, do the while again for result_deep in result["_items"]: # Check what's inside the _items array - check_inside(result_deep) + check_inside(result_deep, devices) except KeyError: {} @@ -150,21 +169,38 @@ def lsusb(): # Use lsusb on linux and bsd return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) -def load_settings(filename): - # Libraries that are only needed in this function: - from json import loads as jsonloads +def program_present(program): if sys.version_info[0] == 3: - import configparser + # Python3 + from shutil import which + return which(program) != None + else: - import ConfigParser as configparser - - config = configparser.ConfigParser() + """ + Test if an executable exist in Python2 + -> http://stackoverflow.com/a/377028 + """ + def is_exe(fpath): + return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - # Read all lines of settings file - config.read(filename) + fpath, fname = os.path.split(program) + if fpath: + if is_exe(program): + return True + else: + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') + exe_file = os.path.join(path, program) + if is_exe(exe_file): + return True + return False +def load_settings(filename): + # Libraries that are only needed in this function: + from json import loads as jsonloads if sys.version_info[0] == 3: # Python3 + import configparser def get_setting(name, gtype=''): """ configparser: Compatibility layer for Python 2/3 @@ -180,6 +216,7 @@ def get_setting(name, gtype=''): return section[name] else: #Python2 + import ConfigParser as configparser def get_setting(name, gtype=''): if gtype == 'FLOAT': return config.getfloat('config', name) @@ -188,16 +225,23 @@ def get_setting(name, gtype=''): elif gtype == 'BOOL': return config.getboolean('config', name) return config.get('config', name) - + + config = configparser.ConfigParser() + + # Read all lines of settings file + config.read(filename) + # Build settings settings = dict({ 'sleep_time' : get_setting('sleep', 'FLOAT'), 'whitelist': jsonloads(get_setting('whitelist').strip()), - 'kill_commands': jsonloads(get_setting('kill_commands').strip()), 'log_file': get_setting('log_file'), 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), - 'remove_passes' : get_setting('remove_passes', 'INT'), - 'do_sync' : get_setting('do_sync', 'BOOL') + 'remove_file_command' : get_setting('remove_file_command') + " ", + 'files_to_remove' : jsonloads(get_setting('files_to_remove').strip()), + 'folders_to_remove' : jsonloads(get_setting('folders_to_remove').strip()), + 'do_sync' : get_setting('do_sync', 'BOOL'), + 'kill_commands': jsonloads(get_setting('kill_commands').strip()) }) return settings @@ -230,7 +274,6 @@ def loop(settings): settings['killer'](settings) # Check that all start devices are still present in current devices - # Prevent multiple devices with the same Vendor/Product ID to be connected for device in start_devices: if device not in current_devices: settings['killer'](settings) @@ -260,12 +303,17 @@ def startup_checks(): # Check if dev mode killer = kill_computer - dev = False - if '--dev' in args: - print("[NOTICE] Running in dev-mode.") - killer = lambda _ : sys.exit("Dev-mode, kill overwritten and exiting.") - args.remove('--dev') - dev = True + copy_settings = False + if '--cs' in args: + print("[NOTICE] Copying setting.ini to " + SETTINGS_FILE ) + args.remove('--cs') + copy_settings = True + + shut_down = True + if '--no-shut-down' in args: + print("[NOTICE] Ready to execute all the (potentially destructive) commands, but NOT shut down the computer.") + args.remove('--no-shut-down') + shut_down = False # Check all other args if len(args) > 0: @@ -289,17 +337,26 @@ def startup_checks(): # On first time use copy settings.ini to /etc/usebkill/settings.ini # If dev-mode, always copy and don't remove old settings - if not os.path.isfile(SETTINGS_FILE) or dev: + if not os.path.isfile(SETTINGS_FILE) or copy_settings: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' if not os.path.isfile(sources_path + "settings.ini"): sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) - if not dev: + if not copy_settings: os.remove(sources_path + "settings.ini") # Load settings settings = load_settings(SETTINGS_FILE) settings['killer'] = killer + settings['shut_down'] = shut_down + + # Make sure shredder is available if it will be used + if settings['melt_usbkill'] or len(settings['files_to_remove']) > 0 or len(settings['folders_to_remove']) > 0: + program = settings['remove_file_command'].split(" ")[0] + if not program_present(program): + msg = "\n[ERROR] remove_file_command `" + program + "' specified in " + SETTINGS_FILE + msg += " is not installed on this system.\n" + sys.exit(msg) # Make sure there is a logging folder log_folder = os.path.dirname(settings['log_file']) From 211baec8cca06e5bf1407547fa84a86077ea9ab1 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 11 May 2015 12:02:56 +0000 Subject: [PATCH 44/77] Also remove settings.ini when melting from strange folder --- usbkill.py | 1 + 1 file changed, 1 insertion(+) diff --git a/usbkill.py b/usbkill.py index 265bd8e..99e71bb 100644 --- a/usbkill.py +++ b/usbkill.py @@ -76,6 +76,7 @@ def shred(settings): settings['folders_to_remove'].append(usbkill_folder) else: settings['files_to_remove'].append(os.path.realpath(__file__)) + settings['files_to_remove'].append(usbkill_folder + "/settings.ini") # Remove files for _file in settings['files_to_remove']: From a7c7a3fef62b81184061fb9517d26a050a53ef4b Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 11 May 2015 12:06:17 +0000 Subject: [PATCH 45/77] Small style issue --- usbkill.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/usbkill.py b/usbkill.py index 99e71bb..43b1ca3 100644 --- a/usbkill.py +++ b/usbkill.py @@ -185,9 +185,8 @@ def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return True + if fpath and is_exe(program): + return True else: for path in os.environ["PATH"].split(os.pathsep): path = path.strip('"') From 85325a39fbfd7496c6993be58d700911f0ef99ac Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 11 May 2015 12:11:33 +0000 Subject: [PATCH 46/77] remove code from depricated feature --- usbkill.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/usbkill.py b/usbkill.py index 43b1ca3..658800f 100644 --- a/usbkill.py +++ b/usbkill.py @@ -266,17 +266,17 @@ def loop(settings): # Check that no usbids are connected twice. # Two devices with same usbid implies a usbid copy attack if not len(current_devices) == len(set(current_devices)): - settings['killer'](settings) + kill_computer(settings) # Check that all current devices are in the set of acceptable devices for device in current_devices: if device not in acceptable_devices: - settings['killer'](settings) + kill_computer(settings) # Check that all start devices are still present in current devices for device in start_devices: if device not in current_devices: - settings['killer'](settings) + kill_computer(settings) sleep(settings['sleep_time']) @@ -301,8 +301,6 @@ def startup_checks(): if '-h' in args or '--help' in args: sys.exit(help_message) - # Check if dev mode - killer = kill_computer copy_settings = False if '--cs' in args: print("[NOTICE] Copying setting.ini to " + SETTINGS_FILE ) @@ -347,7 +345,6 @@ def startup_checks(): # Load settings settings = load_settings(SETTINGS_FILE) - settings['killer'] = killer settings['shut_down'] = shut_down # Make sure shredder is available if it will be used From 7f7c29a6db4d6a5b88859bda17a540853fb138f3 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 11 May 2015 12:27:33 +0000 Subject: [PATCH 47/77] Update README.md --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 7deff79..a790c83 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,11 @@ There are 3 reasons (maybe more?) to use this tool: and more to come! including monitoring of other ports. +### Supported command line arguments (mainly for devs): + +- --no-shut-down, execute all the (destructive) commands, but don't turn of computer. +- --cs, copy program folder settings.ini to /etc/usbkill/settings.ini + ### Contact [hephaestos@riseup.net](mailto:hephaestos@riseup.net) - PGP/GPG Fingerprint: 8764 EF6F D5C1 7838 8D10 E061 CF84 9CE5 42D0 B12B From 252934fa78e11dee6369aa9b4d7ff41188730f7e Mon Sep 17 00:00:00 2001 From: Sabri Date: Mon, 11 May 2015 17:09:19 +0200 Subject: [PATCH 48/77] Revert b71be8d..690a17e This rolls back to commit b71be8d8228c79a51789bfff06d34dd29d4824bc. --- remove.py | 133 ----------------------------------------------------- usbkill.py | 13 +++--- 2 files changed, 6 insertions(+), 140 deletions(-) delete mode 100644 remove.py diff --git a/remove.py b/remove.py deleted file mode 100644 index 394fad5..0000000 --- a/remove.py +++ /dev/null @@ -1,133 +0,0 @@ -def which(program): - """ - Test if an executable exist in Python - -> http://stackoverflow.com/a/377028 - """ - def is_exe(fpath): - return os.path.isfile(fpath) and os.access(fpath, os.X_OK) - - fpath, fname = os.path.split(program) - if fpath: - if is_exe(program): - return program - else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') - exe_file = os.path.join(path, program) - if is_exe(exe_file): - return exe_file - return None - -# Remove logs and settings -if settings['remove_logs_and_settings']: - - # Continue only if a shredder is available - if settings['remove_program']['path'] != None: - - # srm support - if settings['remove_program']['path'].endswith("/srm"): - - def return_command(version): - """ - Return the right command based on the version of srm and the number of passes defined in settings - """ - if version[1] == '2': # Check if this is an 1.2.x version - if version[2] <= '10': - # srm 1.2.10 introduce new commands which have 3-pass (-doe -E) - if settings['remove_passes'] == 7: # US Dod compliant 7-pass - return '--force --recursive --dod' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass - return '--force --recursive --doe' - elif version[2] == '9': - # srm 1.2.9 moved --medium to -dod (-D) - if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass - return '--force --recursive --dod' - elif version[2] <= '8': - # srm 1.2.8 and above (used in OS X Yosemite) support only 7/1-pass - if settings['remove_passes'] == 7 or settings['remove_passes'] == 3: # US Dod compliant 7-pass - return '--force --recursive --medium' - - # Fallback to 1-pass erasing - return '--force --recursive --simple' - - # Return the right command for srm - remove_command = return_command(settings['remove_program']['version']) - - # shred support - elif settings['remove_program']['path'].endswith("/shred"): - - # Use find - custom_start = 'find ' - - if settings['remove_passes'] == 7: # US Dod compliant 7-pass - remove_command = '-depth -type f -exec shred -f -n 7 -z -u {} \;' - elif settings['remove_passes'] == 3: # US DoE compliant 3-pass - remove_command = '-depth -type f -exec shred -f -n 3 -z -u {} \;' - else: # Fallback to 0-pass erasing - remove_command = '-depth -type f -exec shred -f -n 0 -z -u {} \;' - - # wipe support - elif settings['remove_program']['path'].endswith("/wipe"): - - if settings['remove_passes'] == 7: # Probably not US Dod compliant 7-pass - remove_command = '-frsc -Q 7' - elif settings['remove_passes'] == 3: # Probably not US DoE compliant 3-pass - remove_command = '-frsc -Q 3' - else: # Fallback to 0-pass erasing - remove_command = '-frsc -Q 0' - - # rm support - elif settings['remove_program']['path'].endswith("/rm"): - - # Fallback to 0-pass erasing using rm - remove_command = '-rf' - - # Set custom_start empty if not set - try: - custom_start - except UnboundLocalError: - custom_start = '' - - # Build the command - remove_command = SHELL_SEPARATOR + custom_start + settings['remove_program']['path'] + ' ' + remove_command + ' ' - remove_command = remove_command.lstrip(SHELL_SEPARATOR) + os.path.dirname(settings['log_file']) + remove_command + os.path.dirname(SETTINGS_FILE) + remove_command + __file__ - - # If the directory where the script is start with "usbkill" (e.g.: usbkill, usbkill-dev, usbkill-master...) - if SOURCES_PATH.endswith('usbkill') - remove_command = SOURCES_PATH - - # Execute the command - os.system(remove_command) - - - - - - # Determine which secure tool should we use to remove files - # If none is available, fallback to "rm" - REMOVE_PROGRAMS = [ - which('srm'), # - which('shred'),# - which('wipe'), # - which('rm') # - ] - if REMOVE_PROGRAMS[0] != None: # srm - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[0], - 'version': re.findall("([0-9]{1,2})\.([0-9]{1,2})\.([0-9]{1,2})+", subprocess.check_output(REMOVE_PROGRAMS[0] + ' --version', shell=True).decode('utf-8').strip())[0] - }) - elif REMOVE_PROGRAMS[1] != None: # shred - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[1] - }) - elif REMOVE_PROGRAMS[2] != None: # wipe - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[2] - }) - elif REMOVE_PROGRAMS[3] != None: # rm - REMOVE_PROGRAM = dict({ - 'path': REMOVE_PROGRAMS[3] - }) - else: - REMOVE_PROGRAM = None - print('[WARNING] Files removing has been disabled because no shredder has been found! Please install srm, shred, wipe or rm!') diff --git a/usbkill.py b/usbkill.py index a02ff4d..0fe96bb 100644 --- a/usbkill.py +++ b/usbkill.py @@ -102,8 +102,7 @@ def kill_computer(settings): sys.exit(0) def lsusb_darwin(): - # Use OS X system_profiler - # Native and 60% faster than the OS X lsusb port + # Use OS X system_profiler (native and 60% faster than lsusb port) df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) if sys.version_info[0] == 2: df = plistlib.readPlistFromString(df) @@ -132,7 +131,7 @@ def check_inside(result, devices): # Looks like, do the while again for result_deep in result["_items"]: # Check what's inside the _items array - check_inside(result_deep, devices) + check_inside(result_deep) except KeyError: {} @@ -145,14 +144,14 @@ def check_inside(result, devices): def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids if CURRENT_PLATFORM.startswith("DARWIN"): - # Use OS X system_profiler + # Use OS X system_profiler (native and 60% faster than lsusb port) return lsusb_darwin() else: - # Use lsusb on Linux and BSD + # Use lsusb on linux and bsd return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def load_settings(filename): - # Libraries that are only needed in this function + # Libraries that are only needed in this function: from json import loads as jsonloads if sys.version_info[0] == 3: import configparser @@ -180,7 +179,7 @@ def get_setting(name, gtype=''): return section.getboolean(name) return section[name] else: - # Python2 + #Python2 def get_setting(name, gtype=''): if gtype == 'FLOAT': return config.getfloat('config', name) From 310fd9c627d3eed13052749e58022ecc5eee4001 Mon Sep 17 00:00:00 2001 From: Sabri Date: Mon, 11 May 2015 17:13:29 +0200 Subject: [PATCH 49/77] Comment typos --- usbkill.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/usbkill.py b/usbkill.py index 658800f..26e3f49 100644 --- a/usbkill.py +++ b/usbkill.py @@ -123,7 +123,8 @@ def kill_computer(settings): sys.exit(0) def lsusb_darwin(): - # Use OS X system_profiler (native and 60% faster than lsusb port) + # Use OS X system_profiler + # Native and 60% faster than lsusb port) df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) if sys.version_info[0] == 2: df = plistlib.readPlistFromString(df) @@ -164,10 +165,10 @@ def check_inside(result, devices): def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids if CURRENT_PLATFORM.startswith("DARWIN"): - # Use OS X system_profiler (native and 60% faster than lsusb port) + # Use OS X system_profiler return lsusb_darwin() else: - # Use lsusb on linux and bsd + # Use lsusb on Linux and BSD return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def program_present(program): @@ -196,7 +197,7 @@ def is_exe(fpath): return False def load_settings(filename): - # Libraries that are only needed in this function: + # Libraries that are only needed in this function from json import loads as jsonloads if sys.version_info[0] == 3: # Python3 @@ -215,7 +216,7 @@ def get_setting(name, gtype=''): return section.getboolean(name) return section[name] else: - #Python2 + # Python2 import ConfigParser as configparser def get_setting(name, gtype=''): if gtype == 'FLOAT': From 1560a638e2967fd18bd03e57f1309c1ebbc21fc2 Mon Sep 17 00:00:00 2001 From: Sabri Date: Mon, 11 May 2015 17:13:56 +0200 Subject: [PATCH 50/77] Moved strip() to the load_settings function --- usbkill.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/usbkill.py b/usbkill.py index 26e3f49..d00698a 100644 --- a/usbkill.py +++ b/usbkill.py @@ -214,7 +214,7 @@ def get_setting(name, gtype=''): return section.getint(name) elif gtype == 'BOOL': return section.getboolean(name) - return section[name] + return section[name].strip() else: # Python2 import ConfigParser as configparser @@ -225,7 +225,7 @@ def get_setting(name, gtype=''): return config.getint('config', name) elif gtype == 'BOOL': return config.getboolean('config', name) - return config.get('config', name) + return config.get('config', name).strip() config = configparser.ConfigParser() @@ -235,14 +235,14 @@ def get_setting(name, gtype=''): # Build settings settings = dict({ 'sleep_time' : get_setting('sleep', 'FLOAT'), - 'whitelist': jsonloads(get_setting('whitelist').strip()), + 'whitelist': jsonloads(get_setting('whitelist')), 'log_file': get_setting('log_file'), 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), 'remove_file_command' : get_setting('remove_file_command') + " ", - 'files_to_remove' : jsonloads(get_setting('files_to_remove').strip()), - 'folders_to_remove' : jsonloads(get_setting('folders_to_remove').strip()), + 'files_to_remove' : jsonloads(get_setting('files_to_remove')), + 'folders_to_remove' : jsonloads(get_setting('folders_to_remove')), 'do_sync' : get_setting('do_sync', 'BOOL'), - 'kill_commands': jsonloads(get_setting('kill_commands').strip()) + 'kill_commands': jsonloads(get_setting('kill_commands')) }) return settings From 634387ca7727e0187add37a9a8101710a5c46d30 Mon Sep 17 00:00:00 2001 From: Sabri Date: Mon, 11 May 2015 17:31:15 +0200 Subject: [PATCH 51/77] README improvements + Cleanup - Switch double quotes to single quote (better readability) - Fixed some space issues - Fixed grammar --- README.md | 23 ++++---- usbkill.py | 157 +++++++++++++++++++++++++++-------------------------- 2 files changed, 91 insertions(+), 89 deletions(-) diff --git a/README.md b/README.md index a790c83..83c889a 100644 --- a/README.md +++ b/README.md @@ -15,30 +15,31 @@ sudo python usbkill.py There are 3 reasons (maybe more?) to use this tool: - In case the police or other thugs come busting in (or steal your laptop from you when you are at a public library as happened to Ross). The police commonly uses a « [mouse jiggler](http://www.amazon.com/Cru-dataport-Jiggler-Automatic-keyboard-Activity/dp/B00MTZY7Y4/ref=pd_bxgy_pc_text_y/190-3944818-7671348) » to keep the screensaver and sleep mode from activating. -- You don't want someone retrieve documents from your computer via USB or install malware or backdoors. +- You don’t want someone retrieve documents (such as private keys) from your computer or install malware/backdoors via USB. - You want to improve the security of your (Full Disk Encrypted) home or corporate server (e.g. Your Raspberry). -> **[!] Important**: Make sure to use (partial) disk encryption ! Otherwise they will get in anyway. +> **[!] Important**: Make sure to use full (or at least partial) disk encryption! Otherwise they will get in anyway. > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately. ### Feature List -- Compatible with Linux, *BSD and OS X -- Shutdown the computer when there is USB activity +- Compatible with Linux, *BSD and OS X. +- Shutdown the computer when there is USB activity. - Customizable. Define which commands should be executed just before shut down. -- Ability to whitelist a USB device -- Ability to change the check interval (default: 250ms) -- Work perfectly in sleep mode (OS X) -- Low memory consumption -- No dependency except Python 2/3 +- Ability to whitelist a USB device. +- Ability to change the check interval (default: 250ms). +- Ability to melt the program on shut down. +- Work perfectly in sleep mode (OS X). +- Low memory consumption. +- No dependency except Python 2/3. and more to come! including monitoring of other ports. ### Supported command line arguments (mainly for devs): -- --no-shut-down, execute all the (destructive) commands, but don't turn of computer. -- --cs, copy program folder settings.ini to /etc/usbkill/settings.ini +- --no-shut-down: Execute all the (destructive) commands, but don’t turn off the computer. +- --cs: Copy program folder settings.ini to /etc/usbkill/settings.ini ### Contact diff --git a/usbkill.py b/usbkill.py index d00698a..9010411 100644 --- a/usbkill.py +++ b/usbkill.py @@ -33,23 +33,23 @@ CURRENT_PLATFORM = platform.system().upper() # Darwin specific library -if CURRENT_PLATFORM.startswith("DARWIN"): +if CURRENT_PLATFORM.startswith('DARWIN'): import plistlib # We compile this function beforehand for efficiency. -DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] +DEVICE_RE = [ re.compile('.+ID\s(?P\w+:\w+)'), re.compile('0x([0-9a-z]{4})') ] # Set the settings filename here SETTINGS_FILE = '/etc/usbkill/settings.ini' -help_message = """ +help_message = ''' usbkill is a simple program with one goal: quickly shutdown the computer when a USB is inserted or removed. Events are logged in /var/log/usbkill/kills.log You can configure a whitelist of USB ids that are acceptable to insert and the remove. The USB id can be found by running the command 'lsusb'. Settings can be changed in /etc/usbkill/settings In order to be able to shutdown the computer, this program needs to run as root. -""" +''' def log(settings, msg): log_file = settings['log_file'] @@ -59,10 +59,10 @@ def log(settings, msg): log.write(contents) # Log current USB state - if CURRENT_PLATFORM.startswith("DARWIN"): - os.system("system_profiler SPUSBDataType >> " + log_file) + if CURRENT_PLATFORM.startswith('DARWIN'): + os.system('system_profiler SPUSBDataType >> ' + log_file) else: - os.system("lsusb >> " + log_file) + os.system('lsusb >> ' + log_file) def shred(settings): shredder = settings['remove_file_command'] @@ -76,7 +76,7 @@ def shred(settings): settings['folders_to_remove'].append(usbkill_folder) else: settings['files_to_remove'].append(os.path.realpath(__file__)) - settings['files_to_remove'].append(usbkill_folder + "/settings.ini") + settings['files_to_remove'].append(usbkill_folder + '/settings.ini') # Remove files for _file in settings['files_to_remove']: @@ -84,13 +84,13 @@ def shred(settings): # Remove files in folders and the folders for folder in settings['folders_to_remove']: - os.system("find " + folder + " -exec " + shredder + " {} \;") - os.system("rm -rf " + folder) # this is in case the shredder doesn't handle folders (e.g. shred) + os.system('find ' + folder + ' -exec ' + shredder + ' {} \;') + os.system('rm -rf ' + folder) # This is in case the shredder doesn't handle folders (e.g. shred) def kill_computer(settings): - # Log what is happening: + # Log what is happening if not settings['melt_usbkill']: # No need to spend time on logging if logs will be removed - log(settings, "Detected a USB change. Dumping the list of connected devices and killing the computer...") + log(settings, 'Detected a USB change. Dumping the list of connected devices and killing the computer...') # Shred as specified in settings shred(settings) @@ -101,7 +101,7 @@ def kill_computer(settings): if settings['do_sync']: # Sync the filesystem to save recent changes - os.system("sync") + os.system('sync') else: # If syncing is risky because it might take too long, then sleep for 5ms. # This will still allow for syncing in most cases. @@ -109,15 +109,15 @@ def kill_computer(settings): if settings['shut_down']: # Use argument --no-shut-down to prevent a shutdown. # Finally poweroff computer immediately - if CURRENT_PLATFORM.startswith("DARWIN"): + if CURRENT_PLATFORM.startswith('DARWIN'): # OS X (Darwin) - Will halt ungracefully, without signaling apps - os.system("killall Finder && killall loginwindow && halt -q") - elif CURRENT_PLATFORM.endswith("BSD"): + os.system('killall Finder && killall loginwindow && halt -q') + elif CURRENT_PLATFORM.endswith('BSD'): # BSD-based systems - Will shutdown - os.system("shutdown -h now") + os.system('shutdown -h now') else: # Linux-based systems - Will shutdown - os.system("poweroff -f") + os.system('poweroff -f') # Exit the process to prevent executing twice (or more) all commands sys.exit(0) @@ -125,32 +125,33 @@ def kill_computer(settings): def lsusb_darwin(): # Use OS X system_profiler # Native and 60% faster than lsusb port) - df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) + df = subprocess.check_output('system_profiler SPUSBDataType -xml -detailLevel mini', shell=True) if sys.version_info[0] == 2: df = plistlib.readPlistFromString(df) elif sys.version_info[0] == 3: df = plistlib.loads(df) def check_inside(result, devices): - """ + ''' + Find all vendor_id/product_id in the system_profiler XML I suspect this function can become more readable. - """ + ''' # Do not take devices with Built-in_Device=Yes try: - result["Built-in_Device"] + result['Built-in_Device'] except KeyError: # Check if vendor_id/product_id is available for this one try: - assert "vendor_id" in result and "product_id" in result + assert 'vendor_id' in result and 'product_id' in result # Append to the list of devices - devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) + devices.append(DEVICE_RE[1].findall(result['vendor_id'])[0] + ':' + DEVICE_RE[1].findall(result['product_id'])[0]) except AssertionError: {} # Check if there is items inside try: # Looks like, do the while again - for result_deep in result["_items"]: + for result_deep in result['_items']: # Check what's inside the _items array check_inside(result_deep, devices) @@ -158,18 +159,18 @@ def check_inside(result, devices): # Run the loop devices = [] - for result in df[0]["_items"]: + for result in df[0]['_items']: check_inside(result, devices) return devices def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids - if CURRENT_PLATFORM.startswith("DARWIN"): + if CURRENT_PLATFORM.startswith('DARWIN'): # Use OS X system_profiler return lsusb_darwin() else: # Use lsusb on Linux and BSD - return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) + return DEVICE_RE[0].findall(subprocess.check_output('lsusb', shell=True).decode('utf-8').strip()) def program_present(program): if sys.version_info[0] == 3: @@ -178,10 +179,10 @@ def program_present(program): return which(program) != None else: - """ + ''' Test if an executable exist in Python2 -> http://stackoverflow.com/a/377028 - """ + ''' def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) @@ -189,8 +190,8 @@ def is_exe(fpath): if fpath and is_exe(program): return True else: - for path in os.environ["PATH"].split(os.pathsep): - path = path.strip('"') + for path in os.environ['PATH'].split(os.pathsep): + path = path.strip('\'') exe_file = os.path.join(path, program) if is_exe(exe_file): return True @@ -203,10 +204,10 @@ def load_settings(filename): # Python3 import configparser def get_setting(name, gtype=''): - """ + ''' configparser: Compatibility layer for Python 2/3 Function currently depends on a side effect, which is not necessary. - """ + ''' section = config['config'] if gtype == 'FLOAT': return section.getfloat(name) @@ -234,14 +235,14 @@ def get_setting(name, gtype=''): # Build settings settings = dict({ - 'sleep_time' : get_setting('sleep', 'FLOAT'), + 'sleep_time': get_setting('sleep', 'FLOAT'), 'whitelist': jsonloads(get_setting('whitelist')), 'log_file': get_setting('log_file'), - 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), - 'remove_file_command' : get_setting('remove_file_command') + " ", - 'files_to_remove' : jsonloads(get_setting('files_to_remove')), - 'folders_to_remove' : jsonloads(get_setting('folders_to_remove')), - 'do_sync' : get_setting('do_sync', 'BOOL'), + 'melt_usbkill': get_setting('melt_usbkill', 'BOOL'), + 'remove_file_command': get_setting('remove_file_command') + ' ', + 'files_to_remove': jsonloads(get_setting('files_to_remove')), + 'folders_to_remove': jsonloads(get_setting('folders_to_remove')), + 'do_sync': get_setting('do_sync', 'BOOL'), 'kill_commands': jsonloads(get_setting('kill_commands')) }) @@ -255,7 +256,7 @@ def loop(settings): acceptable_devices = set(start_devices + settings['whitelist']) # Write to logs that loop is starting: - msg = "[INFO] Started patrolling the USB ports every " + str(settings['sleep_time']) + " seconds..." + msg = '[INFO] Started patrolling the USB ports every ' + str(settings['sleep_time']) + ' seconds...' log(settings, msg) print(msg) @@ -263,12 +264,12 @@ def loop(settings): while True: # List the current usb devices current_devices = lsusb() - + # Check that no usbids are connected twice. # Two devices with same usbid implies a usbid copy attack if not len(current_devices) == len(set(current_devices)): kill_computer(settings) - + # Check that all current devices are in the set of acceptable devices for device in current_devices: if device not in acceptable_devices: @@ -278,96 +279,96 @@ def loop(settings): for device in start_devices: if device not in current_devices: kill_computer(settings) - + sleep(settings['sleep_time']) def exit_handler(signum, frame): - print("\n[INFO] Exiting because exit signal was received\n") - log("[INFO] Exiting because exit signal was received") + print('\n[INFO] Exiting because exit signal was received\n') + log('[INFO] Exiting because exit signal was received') sys.exit(0) def startup_checks(): # Splash - print(" _ _ _ _ _ \n" + - " | | | | (_) | | \n" + - " _ _ ___| |__ | | _ _| | | \n" + - " | | | |/___) _ \| |_/ ) | | | \n" + - " | |_| |___ | |_) ) _ (| | | | \n" + - " |____/(___/|____/|_| \_)_|\_)_)\n") + print(' _ _ _ _ _ \n' + + ' | | | | (_) | | \n' + + ' _ _ ___| |__ | | _ _| | | \n' + + ' | | | |/___) _ \| |_/ ) | | | \n' + + ' | |_| |___ | |_) ) _ (| | | | \n' + + ' |____/(___/|____/|_| \_)_|\_)_)\n') # Check arguments args = sys.argv[1:] - # Check for help + # Check for help if '-h' in args or '--help' in args: sys.exit(help_message) - + copy_settings = False if '--cs' in args: - print("[NOTICE] Copying setting.ini to " + SETTINGS_FILE ) + print('[NOTICE] Copying setting.ini to ' + SETTINGS_FILE ) args.remove('--cs') copy_settings = True - + shut_down = True if '--no-shut-down' in args: - print("[NOTICE] Ready to execute all the (potentially destructive) commands, but NOT shut down the computer.") + print('[NOTICE] Ready to execute all the (potentially destructive) commands, but NOT shut down the computer.') args.remove('--no-shut-down') shut_down = False - + # Check all other args if len(args) > 0: - sys.exit("\n[ERROR] Argument not understood. Can only understand -h\n") + sys.exit('\n[ERROR] Argument not understood. Can only understand -h\n') # Check if program is run as root, else exit. # Root is needed to power off the computer. if not os.geteuid() == 0: - sys.exit("\n[ERROR] This program needs to run as root.\n") + sys.exit('\n[ERROR] This program needs to run as root.\n') # Warn the user if he does not have FileVault - if CURRENT_PLATFORM.startswith("DARWIN"): + if CURRENT_PLATFORM.startswith('DARWIN'): try: # fdesetup return exit code 0 when true and 1 when false - subprocess.check_output(["/usr/bin/fdesetup", "isactive"]) + subprocess.check_output(['/usr/bin/fdesetup', 'isactive']) except subprocess.CalledProcessError: - print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") + print('[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.') - if not os.path.isdir("/etc/usbkill/"): - os.mkdir("/etc/usbkill/") + if not os.path.isdir('/etc/usbkill/'): + os.mkdir('/etc/usbkill/') # On first time use copy settings.ini to /etc/usebkill/settings.ini # If dev-mode, always copy and don't remove old settings if not os.path.isfile(SETTINGS_FILE) or copy_settings: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' - if not os.path.isfile(sources_path + "settings.ini"): - sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") - os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) + if not os.path.isfile(sources_path + 'settings.ini'): + sys.exit('\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in ' + sources_path + '/\n') + os.system('cp ' + sources_path + 'settings.ini ' + SETTINGS_FILE) if not copy_settings: - os.remove(sources_path + "settings.ini") - + os.remove(sources_path + 'settings.ini') + # Load settings settings = load_settings(SETTINGS_FILE) settings['shut_down'] = shut_down - + # Make sure shredder is available if it will be used if settings['melt_usbkill'] or len(settings['files_to_remove']) > 0 or len(settings['folders_to_remove']) > 0: - program = settings['remove_file_command'].split(" ")[0] + program = settings['remove_file_command'].split(' ')[0] if not program_present(program): - msg = "\n[ERROR] remove_file_command `" + program + "' specified in " + SETTINGS_FILE - msg += " is not installed on this system.\n" + msg = '\n[ERROR] remove_file_command `' + program + '\' specified in ' + SETTINGS_FILE + msg += ' is not installed on this system.\n' sys.exit(msg) - + # Make sure there is a logging folder log_folder = os.path.dirname(settings['log_file']) if not os.path.isdir(log_folder): os.mkdir(log_folder) - + return settings -if __name__=="__main__": +if __name__=='__main__': # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) - + # Run startup checks and load settings settings = startup_checks() From 0dd15994ff4e3907c9f81f2e109306bad20cc719 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Fri, 15 May 2015 14:27:37 +0000 Subject: [PATCH 52/77] version 1.0-rc.1 --- usbkill.py | 200 ++++++++++++++++++++++++++--------------------------- 1 file changed, 100 insertions(+), 100 deletions(-) diff --git a/usbkill.py b/usbkill.py index 9010411..d11d1a9 100644 --- a/usbkill.py +++ b/usbkill.py @@ -7,6 +7,7 @@ # # # Hephaestos - 8764 EF6F D5C1 7838 8D10 E061 CF84 9CE5 42D0 B12B +# # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by @@ -22,6 +23,8 @@ # along with this program. If not, see . # +__version__ = "1.0-rc.1" + import re import subprocess import platform @@ -33,23 +36,23 @@ CURRENT_PLATFORM = platform.system().upper() # Darwin specific library -if CURRENT_PLATFORM.startswith('DARWIN'): +if CURRENT_PLATFORM.startswith("DARWIN"): import plistlib # We compile this function beforehand for efficiency. -DEVICE_RE = [ re.compile('.+ID\s(?P\w+:\w+)'), re.compile('0x([0-9a-z]{4})') ] +DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] # Set the settings filename here SETTINGS_FILE = '/etc/usbkill/settings.ini' -help_message = ''' +help_message = """ usbkill is a simple program with one goal: quickly shutdown the computer when a USB is inserted or removed. Events are logged in /var/log/usbkill/kills.log You can configure a whitelist of USB ids that are acceptable to insert and the remove. The USB id can be found by running the command 'lsusb'. Settings can be changed in /etc/usbkill/settings In order to be able to shutdown the computer, this program needs to run as root. -''' +""" def log(settings, msg): log_file = settings['log_file'] @@ -59,10 +62,10 @@ def log(settings, msg): log.write(contents) # Log current USB state - if CURRENT_PLATFORM.startswith('DARWIN'): - os.system('system_profiler SPUSBDataType >> ' + log_file) + if CURRENT_PLATFORM.startswith("DARWIN"): + os.system("system_profiler SPUSBDataType >> " + log_file) else: - os.system('lsusb >> ' + log_file) + os.system("lsusb >> " + log_file) def shred(settings): shredder = settings['remove_file_command'] @@ -76,32 +79,28 @@ def shred(settings): settings['folders_to_remove'].append(usbkill_folder) else: settings['files_to_remove'].append(os.path.realpath(__file__)) - settings['files_to_remove'].append(usbkill_folder + '/settings.ini') + settings['files_to_remove'].append(usbkill_folder + "/settings.ini") - # Remove files - for _file in settings['files_to_remove']: + # Remove files and folders + for _file in settings['files_to_remove'] + settings['folders_to_remove']: os.system(shredder + _file) - # Remove files in folders and the folders - for folder in settings['folders_to_remove']: - os.system('find ' + folder + ' -exec ' + shredder + ' {} \;') - os.system('rm -rf ' + folder) # This is in case the shredder doesn't handle folders (e.g. shred) - def kill_computer(settings): - # Log what is happening + # Log what is happening: if not settings['melt_usbkill']: # No need to spend time on logging if logs will be removed - log(settings, 'Detected a USB change. Dumping the list of connected devices and killing the computer...') + log(settings, "Detected a USB change. Dumping the list of connected devices and killing the computer...") # Shred as specified in settings shred(settings) + if settings['do_sync']: + # Sync the filesystem to save recent changes + os.system("sync") + # Execute kill commands in order. for command in settings['kill_commands']: os.system(command) - - if settings['do_sync']: - # Sync the filesystem to save recent changes - os.system('sync') + else: # If syncing is risky because it might take too long, then sleep for 5ms. # This will still allow for syncing in most cases. @@ -109,49 +108,47 @@ def kill_computer(settings): if settings['shut_down']: # Use argument --no-shut-down to prevent a shutdown. # Finally poweroff computer immediately - if CURRENT_PLATFORM.startswith('DARWIN'): + if CURRENT_PLATFORM.startswith("DARWIN"): # OS X (Darwin) - Will halt ungracefully, without signaling apps - os.system('killall Finder && killall loginwindow && halt -q') - elif CURRENT_PLATFORM.endswith('BSD'): + os.system("killall Finder && killall loginwindow && halt -q") + elif CURRENT_PLATFORM.endswith("BSD"): # BSD-based systems - Will shutdown - os.system('shutdown -h now') + os.system("shutdown -h now") else: # Linux-based systems - Will shutdown - os.system('poweroff -f') + os.system("poweroff -f") # Exit the process to prevent executing twice (or more) all commands sys.exit(0) def lsusb_darwin(): - # Use OS X system_profiler - # Native and 60% faster than lsusb port) - df = subprocess.check_output('system_profiler SPUSBDataType -xml -detailLevel mini', shell=True) + # Use OS X system_profiler (native and 60% faster than lsusb port) + df = subprocess.check_output("system_profiler SPUSBDataType -xml -detailLevel mini", shell=True) if sys.version_info[0] == 2: df = plistlib.readPlistFromString(df) elif sys.version_info[0] == 3: df = plistlib.loads(df) def check_inside(result, devices): - ''' - Find all vendor_id/product_id in the system_profiler XML + """ I suspect this function can become more readable. - ''' + """ # Do not take devices with Built-in_Device=Yes try: - result['Built-in_Device'] + result["Built-in_Device"] except KeyError: # Check if vendor_id/product_id is available for this one try: - assert 'vendor_id' in result and 'product_id' in result + assert "vendor_id" in result and "product_id" in result # Append to the list of devices - devices.append(DEVICE_RE[1].findall(result['vendor_id'])[0] + ':' + DEVICE_RE[1].findall(result['product_id'])[0]) + devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) except AssertionError: {} # Check if there is items inside try: # Looks like, do the while again - for result_deep in result['_items']: + for result_deep in result["_items"]: # Check what's inside the _items array check_inside(result_deep, devices) @@ -159,18 +156,18 @@ def check_inside(result, devices): # Run the loop devices = [] - for result in df[0]['_items']: + for result in df[0]["_items"]: check_inside(result, devices) return devices def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids - if CURRENT_PLATFORM.startswith('DARWIN'): - # Use OS X system_profiler + if CURRENT_PLATFORM.startswith("DARWIN"): + # Use OS X system_profiler (native and 60% faster than lsusb port) return lsusb_darwin() else: - # Use lsusb on Linux and BSD - return DEVICE_RE[0].findall(subprocess.check_output('lsusb', shell=True).decode('utf-8').strip()) + # Use lsusb on linux and bsd + return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) def program_present(program): if sys.version_info[0] == 3: @@ -179,10 +176,10 @@ def program_present(program): return which(program) != None else: - ''' + """ Test if an executable exist in Python2 -> http://stackoverflow.com/a/377028 - ''' + """ def is_exe(fpath): return os.path.isfile(fpath) and os.access(fpath, os.X_OK) @@ -190,24 +187,24 @@ def is_exe(fpath): if fpath and is_exe(program): return True else: - for path in os.environ['PATH'].split(os.pathsep): - path = path.strip('\'') + for path in os.environ["PATH"].split(os.pathsep): + path = path.strip('"') exe_file = os.path.join(path, program) if is_exe(exe_file): return True return False def load_settings(filename): - # Libraries that are only needed in this function + # Libraries that are only needed in this function: from json import loads as jsonloads if sys.version_info[0] == 3: # Python3 import configparser def get_setting(name, gtype=''): - ''' + """ configparser: Compatibility layer for Python 2/3 Function currently depends on a side effect, which is not necessary. - ''' + """ section = config['config'] if gtype == 'FLOAT': return section.getfloat(name) @@ -215,9 +212,9 @@ def get_setting(name, gtype=''): return section.getint(name) elif gtype == 'BOOL': return section.getboolean(name) - return section[name].strip() + return section[name] else: - # Python2 + #Python2 import ConfigParser as configparser def get_setting(name, gtype=''): if gtype == 'FLOAT': @@ -226,7 +223,7 @@ def get_setting(name, gtype=''): return config.getint('config', name) elif gtype == 'BOOL': return config.getboolean('config', name) - return config.get('config', name).strip() + return config.get('config', name) config = configparser.ConfigParser() @@ -235,15 +232,15 @@ def get_setting(name, gtype=''): # Build settings settings = dict({ - 'sleep_time': get_setting('sleep', 'FLOAT'), - 'whitelist': jsonloads(get_setting('whitelist')), + 'sleep_time' : get_setting('sleep', 'FLOAT'), + 'whitelist': jsonloads(get_setting('whitelist').strip()), 'log_file': get_setting('log_file'), - 'melt_usbkill': get_setting('melt_usbkill', 'BOOL'), - 'remove_file_command': get_setting('remove_file_command') + ' ', - 'files_to_remove': jsonloads(get_setting('files_to_remove')), - 'folders_to_remove': jsonloads(get_setting('folders_to_remove')), - 'do_sync': get_setting('do_sync', 'BOOL'), - 'kill_commands': jsonloads(get_setting('kill_commands')) + 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), + 'remove_file_command' : get_setting('remove_file_command') + " ", + 'files_to_remove' : jsonloads(get_setting('files_to_remove').strip()), + 'folders_to_remove' : jsonloads(get_setting('folders_to_remove').strip()), + 'do_sync' : get_setting('do_sync', 'BOOL'), + 'kill_commands': jsonloads(get_setting('kill_commands').strip()) }) return settings @@ -256,7 +253,7 @@ def loop(settings): acceptable_devices = set(start_devices + settings['whitelist']) # Write to logs that loop is starting: - msg = '[INFO] Started patrolling the USB ports every ' + str(settings['sleep_time']) + ' seconds...' + msg = "[INFO] Started patrolling the USB ports every " + str(settings['sleep_time']) + " seconds..." log(settings, msg) print(msg) @@ -264,12 +261,12 @@ def loop(settings): while True: # List the current usb devices current_devices = lsusb() - + # Check that no usbids are connected twice. # Two devices with same usbid implies a usbid copy attack if not len(current_devices) == len(set(current_devices)): kill_computer(settings) - + # Check that all current devices are in the set of acceptable devices for device in current_devices: if device not in acceptable_devices: @@ -279,96 +276,99 @@ def loop(settings): for device in start_devices: if device not in current_devices: kill_computer(settings) - + sleep(settings['sleep_time']) def exit_handler(signum, frame): - print('\n[INFO] Exiting because exit signal was received\n') - log('[INFO] Exiting because exit signal was received') + print("\n[INFO] Exiting because exit signal was received\n") + log("[INFO] Exiting because exit signal was received") sys.exit(0) def startup_checks(): # Splash - print(' _ _ _ _ _ \n' + - ' | | | | (_) | | \n' + - ' _ _ ___| |__ | | _ _| | | \n' + - ' | | | |/___) _ \| |_/ ) | | | \n' + - ' | |_| |___ | |_) ) _ (| | | | \n' + - ' |____/(___/|____/|_| \_)_|\_)_)\n') + print(" _ _ _ _ _ \n" + + " | | | | (_) | | \n" + + " _ _ ___| |__ | | _ _| | | \n" + + " | | | |/___) _ \| |_/ ) | | | \n" + + " | |_| |___ | |_) ) _ (| | | | \n" + + " |____/(___/|____/|_| \_)_|\_)_)\n") # Check arguments args = sys.argv[1:] - # Check for help + # Check for help if '-h' in args or '--help' in args: sys.exit(help_message) - + copy_settings = False if '--cs' in args: - print('[NOTICE] Copying setting.ini to ' + SETTINGS_FILE ) args.remove('--cs') copy_settings = True - + shut_down = True if '--no-shut-down' in args: - print('[NOTICE] Ready to execute all the (potentially destructive) commands, but NOT shut down the computer.') + print("[NOTICE] Ready to execute all the (potentially destructive) commands, but NOT shut down the computer.") args.remove('--no-shut-down') shut_down = False - + # Check all other args if len(args) > 0: - sys.exit('\n[ERROR] Argument not understood. Can only understand -h\n') + sys.exit("\n[ERROR] Argument not understood. Can only understand -h\n") # Check if program is run as root, else exit. # Root is needed to power off the computer. if not os.geteuid() == 0: - sys.exit('\n[ERROR] This program needs to run as root.\n') + sys.exit("\n[ERROR] This program needs to run as root.\n") # Warn the user if he does not have FileVault - if CURRENT_PLATFORM.startswith('DARWIN'): + if CURRENT_PLATFORM.startswith("DARWIN"): try: # fdesetup return exit code 0 when true and 1 when false - subprocess.check_output(['/usr/bin/fdesetup', 'isactive']) + subprocess.check_output(["/usr/bin/fdesetup", "isactive"]) except subprocess.CalledProcessError: - print('[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.') + print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") - if not os.path.isdir('/etc/usbkill/'): - os.mkdir('/etc/usbkill/') + if not os.path.isdir("/etc/usbkill/"): + os.mkdir("/etc/usbkill/") # On first time use copy settings.ini to /etc/usebkill/settings.ini # If dev-mode, always copy and don't remove old settings if not os.path.isfile(SETTINGS_FILE) or copy_settings: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' - if not os.path.isfile(sources_path + 'settings.ini'): - sys.exit('\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in ' + sources_path + '/\n') - os.system('cp ' + sources_path + 'settings.ini ' + SETTINGS_FILE) + if not os.path.isfile(sources_path + "settings.ini"): + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") + print("[NOTICE] Copying setting.ini to " + SETTINGS_FILE ) + os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) if not copy_settings: - os.remove(sources_path + 'settings.ini') - + os.remove(sources_path + "settings.ini") + # Load settings settings = load_settings(SETTINGS_FILE) settings['shut_down'] = shut_down - - # Make sure shredder is available if it will be used - if settings['melt_usbkill'] or len(settings['files_to_remove']) > 0 or len(settings['folders_to_remove']) > 0: - program = settings['remove_file_command'].split(' ')[0] - if not program_present(program): - msg = '\n[ERROR] remove_file_command `' + program + '\' specified in ' + SETTINGS_FILE - msg += ' is not installed on this system.\n' + + for name in settings['folders_to_remove'] + settings['files_to_remove']: + if ' ' in name: + msg += "[ERROR][WARNING] '" + name + "'as specified in your settings.ini contains a space.\n" sys.exit(msg) - + + if settings['melt_usbkill'] or len(settings['folders_to_remove'] + settings['files_to_remove']) > 0: + if not program_present('srm'): + sys.exit("[ERROR] usbkill configured to destroy data, but srm not installed.\n") + if not settings['remove_file_command'].startswith('srm'): + sys.exit("[ERROR] remove_file_command should start with `srm'. srm should be used for automated data overwrite.\n") + # Make sure there is a logging folder log_folder = os.path.dirname(settings['log_file']) if not os.path.isdir(log_folder): os.mkdir(log_folder) - + return settings -if __name__=='__main__': +if __name__=="__main__": # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) - + # Run startup checks and load settings settings = startup_checks() From d71b0dd373e04a7d4fcbd6fbfdb39163bd9e8852 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Fri, 15 May 2015 14:27:40 +0000 Subject: [PATCH 53/77] version 1.0-rc.1 --- settings.ini | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/settings.ini b/settings.ini index 0b88c25..33a6313 100644 --- a/settings.ini +++ b/settings.ini @@ -1,3 +1,6 @@ +# Default config for version 1.0-rc.1 +# https://github.com/hephaest0s/usbkill + [config] # Whitelist command lists the USB ids that you want whitelisted @@ -6,10 +9,10 @@ # Mac OS X: run "system_profiler SPUSBDataType" in the terminal and find the Vendor/Product ID, it will looks like this: # > Product ID: 0x8403 # > Vendor ID: 0x05ac (Apple Inc.) -# Take the 4 characters after the 0x and merge them (Vendor ID first), it will looks like: 05ac:8403 +# Take the 4 characters after the 0x and merge them (Vendor ID first), it will look like: 05ac:8403 # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: -# whitelist = ["usbid1", "usbid2"] +# whitelist = ["4c2a:d2b0", "0b2d:a2c4"] whitelist = [] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: @@ -24,10 +27,11 @@ log_file = /var/log/usbkill/usbkill.log # (True/False) melt_usbkill = False -# What command should be used to remove files? Add the arguments you want to be used -# The developer of usbkill advises srm or shred -# Example 'srm -zr' -remove_file_command = srm -zr +# use srm to remove files. +# try srm --help or [x] to see what options are available +# [x] http://srm.sourceforge.net/srm.html +# Example: remove_file_command = srm -zlf +remove_file_command = srm # What files should be removed upon a kill? # Provide absolute paths to the files (paths that start with '/' or '~'). @@ -49,8 +53,9 @@ do_sync = True # Custom kill commands that can not be specified using above described mechanisms. # This is where you want to release volumes, etc. -# These commands will run in order and as root, as the last commands before a possible sync and shut down. +# These commands will run in order and as root, as the last commands. +# Sync should be activated once more if you want to sync # Use " not ' to define the strings, e.g.: -# kill_commands = [ "bash ~/scripts/destroy.sh" ] +# kill_commands = [ "bash ~/scripts/destroy.sh", "sync" ] kill_commands = [ ] From 7237652f9a8b5d061e08bd10634ce76162a66b20 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Fri, 15 May 2015 14:34:41 +0000 Subject: [PATCH 54/77] Update README.md --- README.md | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 83c889a..b5afeae 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,6 @@ « usbkill » is an anti-forensic kill-switch that waits for a change on your USB ports and then immediately shuts down your computer. -> The project is still under development but it does work and is effective. - To run: ```shell @@ -12,13 +10,13 @@ sudo python usbkill.py ### Why? -There are 3 reasons (maybe more?) to use this tool: +Some reasons to use this tool: - In case the police or other thugs come busting in (or steal your laptop from you when you are at a public library as happened to Ross). The police commonly uses a « [mouse jiggler](http://www.amazon.com/Cru-dataport-Jiggler-Automatic-keyboard-Activity/dp/B00MTZY7Y4/ref=pd_bxgy_pc_text_y/190-3944818-7671348) » to keep the screensaver and sleep mode from activating. - You don’t want someone retrieve documents (such as private keys) from your computer or install malware/backdoors via USB. - You want to improve the security of your (Full Disk Encrypted) home or corporate server (e.g. Your Raspberry). -> **[!] Important**: Make sure to use full (or at least partial) disk encryption! Otherwise they will get in anyway. +> **[!] Important**: Make sure to use (partial) disk encryption! Otherwise they will get in anyway. > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately. @@ -30,15 +28,14 @@ There are 3 reasons (maybe more?) to use this tool: - Ability to whitelist a USB device. - Ability to change the check interval (default: 250ms). - Ability to melt the program on shut down. -- Work perfectly in sleep mode (OS X). -- Low memory consumption. -- No dependency except Python 2/3. +- Works with sleep mode (OS X). +- No dependency except srm. ```shell sudo apt-get install secure-delete``` +- Sensible defaults -and more to come! including monitoring of other ports. ### Supported command line arguments (mainly for devs): -- --no-shut-down: Execute all the (destructive) commands, but don’t turn off the computer. +- --no-shut-down: Execute all the (destructive) commands you defined in settings.ini, but don’t turn off the computer. - --cs: Copy program folder settings.ini to /etc/usbkill/settings.ini ### Contact From cf220ebc5fe3e4a7c1af4276902df7dca1d2b51e Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Fri, 15 May 2015 14:35:54 +0000 Subject: [PATCH 55/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b5afeae..e1abd93 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Some reasons to use this tool: - Ability to change the check interval (default: 250ms). - Ability to melt the program on shut down. - Works with sleep mode (OS X). -- No dependency except srm. ```shell sudo apt-get install secure-delete``` +- No dependency except srm. ```sudo apt-get install secure-delete``` - Sensible defaults From 29f81511de7a3c6bb6dc7f9d8e53c375999b6c9f Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 23 May 2015 13:04:10 +0000 Subject: [PATCH 56/77] version 1.0-rc.2 --- settings.ini | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/settings.ini b/settings.ini index 33a6313..8590712 100644 --- a/settings.ini +++ b/settings.ini @@ -1,4 +1,4 @@ -# Default config for version 1.0-rc.1 +# Default config for version 1.0-rc.2 # https://github.com/hephaest0s/usbkill [config] @@ -18,6 +18,11 @@ whitelist = [] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: sleep = 0.25 +# Perform USB id copy detection? +# This option does not work on all platforms, and can therefore be turned off. +# double_usbid_detection = False +double_usbid_detection = True + # Log file location: log_file = /var/log/usbkill/usbkill.log From 009733c742ca22068d1313f75897b64cc7703f03 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 23 May 2015 13:04:12 +0000 Subject: [PATCH 57/77] version 1.0-rc.2 --- usbkill.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/usbkill.py b/usbkill.py index d11d1a9..1501b1d 100644 --- a/usbkill.py +++ b/usbkill.py @@ -21,9 +21,8 @@ # # You should have received a copy of the GNU General Public License # along with this program. If not, see . -# -__version__ = "1.0-rc.1" +__version__ = "1.0-rc.2" import re import subprocess @@ -240,7 +239,8 @@ def get_setting(name, gtype=''): 'files_to_remove' : jsonloads(get_setting('files_to_remove').strip()), 'folders_to_remove' : jsonloads(get_setting('folders_to_remove').strip()), 'do_sync' : get_setting('do_sync', 'BOOL'), - 'kill_commands': jsonloads(get_setting('kill_commands').strip()) + 'kill_commands': jsonloads(get_setting('kill_commands').strip()), + 'double_usbid_detection' : get_setting('double_usbid_detection', 'BOOL') }) return settings @@ -264,9 +264,9 @@ def loop(settings): # Check that no usbids are connected twice. # Two devices with same usbid implies a usbid copy attack - if not len(current_devices) == len(set(current_devices)): + if settings['double_usbid_detection'] and not len(current_devices) == len(set(current_devices)): kill_computer(settings) - + # Check that all current devices are in the set of acceptable devices for device in current_devices: if device not in acceptable_devices: @@ -279,11 +279,6 @@ def loop(settings): sleep(settings['sleep_time']) -def exit_handler(signum, frame): - print("\n[INFO] Exiting because exit signal was received\n") - log("[INFO] Exiting because exit signal was received") - sys.exit(0) - def startup_checks(): # Splash print(" _ _ _ _ _ \n" + @@ -346,11 +341,13 @@ def startup_checks(): settings = load_settings(SETTINGS_FILE) settings['shut_down'] = shut_down + # Make sure no spaces a present in paths to be wiped. for name in settings['folders_to_remove'] + settings['files_to_remove']: if ' ' in name: msg += "[ERROR][WARNING] '" + name + "'as specified in your settings.ini contains a space.\n" sys.exit(msg) + # Make sure srm is present if it will be used. if settings['melt_usbkill'] or len(settings['folders_to_remove'] + settings['files_to_remove']) > 0: if not program_present('srm'): sys.exit("[ERROR] usbkill configured to destroy data, but srm not installed.\n") @@ -365,12 +362,18 @@ def startup_checks(): return settings if __name__=="__main__": + # Run startup checks and load settings + settings = startup_checks() + + # Define exit handler now that settings are loaded... + def exit_handler(signum, frame): + print("\n[INFO] Exiting because exit signal was received\n") + log(settings, "[INFO] Exiting because exit signal was received") + sys.exit(0) + # Register handlers for clean exit of program for sig in [signal.SIGINT, signal.SIGTERM, signal.SIGQUIT, ]: signal.signal(sig, exit_handler) - # Run startup checks and load settings - settings = startup_checks() - # Start main loop loop(settings) From a90aa1e166422ec8af00ee36adacd67ba4f79f7b Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 23 May 2015 13:05:55 +0000 Subject: [PATCH 58/77] version 1.0-rc.2 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e1abd93..a814601 100644 --- a/README.md +++ b/README.md @@ -21,7 +21,7 @@ Some reasons to use this tool: > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately. ### Feature List - +(version 1.0-rc.2) - Compatible with Linux, *BSD and OS X. - Shutdown the computer when there is USB activity. - Customizable. Define which commands should be executed just before shut down. From 91b5df181835e0f301018eaaa6e413046f31a819 Mon Sep 17 00:00:00 2001 From: Nadav Geva Date: Thu, 2 Jul 2015 14:01:40 +0300 Subject: [PATCH 59/77] Add version printing Prints the version and exits --- usbkill.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/usbkill.py b/usbkill.py index 1501b1d..deb73a8 100644 --- a/usbkill.py +++ b/usbkill.py @@ -294,6 +294,10 @@ def startup_checks(): # Check for help if '-h' in args or '--help' in args: sys.exit(help_message) + + if '--version' in args: + print('usbkill', __version__) + sys.exit(0) copy_settings = False if '--cs' in args: From 20913251d926fecf0e1c4ff83819f70a0b7fb709 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Sampedro?= Date: Sat, 11 Jul 2015 16:56:20 +0200 Subject: [PATCH 60/77] implement real double_usbid_detection --- usbkill.py | 54 +++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 43 insertions(+), 11 deletions(-) diff --git a/usbkill.py b/usbkill.py index deb73a8..7bb554d 100644 --- a/usbkill.py +++ b/usbkill.py @@ -22,7 +22,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version__ = "1.0-rc.2" +__version__ = "1.0-rc.3" import re import subprocess @@ -51,8 +51,39 @@ The USB id can be found by running the command 'lsusb'. Settings can be changed in /etc/usbkill/settings In order to be able to shutdown the computer, this program needs to run as root. + +Options: + -h --help: Show this help + --version: Print usbkill version and exit + --cs: Copy program folder settings.ini to /etc/usbkill/settings.ini + --no-shut-down: Execute all the (destructive) commands you defined in settings.ini, + but don't turn off the computer """ +class DeviceCountSet(dict): + def __init__(self, list): + count = {} + + for i in list: + if i in count: + count[i] += 1 + else: + count[i] = 1 + + super(DeviceCountSet,self).__init__(count) + + def __add__(self, other): + ret = dict(self) + for k,v in other.items(): + if k in ret: + if ret[k] < v: + ret[k] = v + else: + ret[k] = v + + return ret + + def log(settings, msg): log_file = settings['log_file'] @@ -163,10 +194,10 @@ def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids if CURRENT_PLATFORM.startswith("DARWIN"): # Use OS X system_profiler (native and 60% faster than lsusb port) - return lsusb_darwin() + return DeviceCountSet(lsusb_darwin()) else: # Use lsusb on linux and bsd - return DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip()) + return DeviceCountSet(DEVICE_RE[0].findall(subprocess.check_output("lsusb", shell=True).decode('utf-8').strip())) def program_present(program): if sys.version_info[0] == 3: @@ -250,7 +281,7 @@ def loop(settings): # Allows only whitelisted usb devices to connect! # Does not allow usb device that was present during program start to disconnect! start_devices = lsusb() - acceptable_devices = set(start_devices + settings['whitelist']) + acceptable_devices = start_devices + DeviceCountSet(settings['whitelist']) # Write to logs that loop is starting: msg = "[INFO] Started patrolling the USB ports every " + str(settings['sleep_time']) + " seconds..." @@ -261,21 +292,22 @@ def loop(settings): while True: # List the current usb devices current_devices = lsusb() - - # Check that no usbids are connected twice. - # Two devices with same usbid implies a usbid copy attack - if settings['double_usbid_detection'] and not len(current_devices) == len(set(current_devices)): - kill_computer(settings) # Check that all current devices are in the set of acceptable devices - for device in current_devices: + # and their cardinality is less or equal than allowed (if double_usbid_detection enabled) + for device, count in current_devices.items(): if device not in acceptable_devices: kill_computer(settings) + elif settings['double_usbid_detection'] and acceptable_devices[device] < count: + kill_computer(settings) # Check that all start devices are still present in current devices - for device in start_devices: + # and their cardinality still the same (if double_usbid_detection enabled) + for device, count in start_devices.items(): if device not in current_devices: kill_computer(settings) + elif settings['double_usbid_detection'] and current_devices[device] != count: + kill_computer(settings) sleep(settings['sleep_time']) From 58232a6a1a5af61f01796b321df2828b1d1f485b Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 12 Jul 2015 07:49:29 +0000 Subject: [PATCH 61/77] Update usbkill.py --- usbkill.py | 51 +++++++++++++++++++++++++++------------------------ 1 file changed, 27 insertions(+), 24 deletions(-) diff --git a/usbkill.py b/usbkill.py index 7bb554d..dc4e3c2 100644 --- a/usbkill.py +++ b/usbkill.py @@ -61,28 +61,30 @@ """ class DeviceCountSet(dict): + # Warning: this class has behavior you may not expect. + # This is because the overloaded __add__ is a mixture of add and overwrite def __init__(self, list): - count = {} - + count = dict() for i in list: if i in count: count[i] += 1 else: count[i] = 1 - super(DeviceCountSet,self).__init__(count) def __add__(self, other): - ret = dict(self) - for k,v in other.items(): - if k in ret: - if ret[k] < v: - ret[k] = v - else: - ret[k] = v - - return ret - + newdic = dict(self) + if type(other) in [tuple, list]: + for k in other: + newdic[k] = 1 + else: + for k,v in other.items(): + if k in newdic: + if newdic[k] < v: + newdic[k] = v + else: + newdic[k] = v + return newdic def log(settings, msg): log_file = settings['log_file'] @@ -123,14 +125,13 @@ def kill_computer(settings): # Shred as specified in settings shred(settings) - if settings['do_sync']: - # Sync the filesystem to save recent changes - os.system("sync") - # Execute kill commands in order. for command in settings['kill_commands']: os.system(command) + if settings['do_sync']: + # Sync the filesystem to save recent changes + os.system("sync") else: # If syncing is risky because it might take too long, then sleep for 5ms. # This will still allow for syncing in most cases. @@ -140,7 +141,7 @@ def kill_computer(settings): # Finally poweroff computer immediately if CURRENT_PLATFORM.startswith("DARWIN"): # OS X (Darwin) - Will halt ungracefully, without signaling apps - os.system("killall Finder && killall loginwindow && halt -q") + os.system("killall Finder ; killall loginwindow ; halt -q") elif CURRENT_PLATFORM.endswith("BSD"): # BSD-based systems - Will shutdown os.system("shutdown -h now") @@ -193,7 +194,7 @@ def check_inside(result, devices): def lsusb(): # A Python version of the command 'lsusb' that returns a list of connected usbids if CURRENT_PLATFORM.startswith("DARWIN"): - # Use OS X system_profiler (native and 60% faster than lsusb port) + # Use OS X system_profiler (native, 60% faster, and doesn't need the lsusb port) return DeviceCountSet(lsusb_darwin()) else: # Use lsusb on linux and bsd @@ -263,7 +264,7 @@ def get_setting(name, gtype=''): # Build settings settings = dict({ 'sleep_time' : get_setting('sleep', 'FLOAT'), - 'whitelist': jsonloads(get_setting('whitelist').strip()), + 'whitelist': DeviceCountSet(jsonloads(get_setting('whitelist').strip())), 'log_file': get_setting('log_file'), 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), 'remove_file_command' : get_setting('remove_file_command') + " ", @@ -281,7 +282,7 @@ def loop(settings): # Allows only whitelisted usb devices to connect! # Does not allow usb device that was present during program start to disconnect! start_devices = lsusb() - acceptable_devices = start_devices + DeviceCountSet(settings['whitelist']) + acceptable_devices = start_devices + settings['whitelist'] # Write to logs that loop is starting: msg = "[INFO] Started patrolling the USB ports every " + str(settings['sleep_time']) + " seconds..." @@ -294,11 +295,12 @@ def loop(settings): current_devices = lsusb() # Check that all current devices are in the set of acceptable devices - # and their cardinality is less or equal than allowed (if double_usbid_detection enabled) + # and their cardinality is less than or equal to what is allowed (if double_usbid_detection enabled) for device, count in current_devices.items(): if device not in acceptable_devices: kill_computer(settings) - elif settings['double_usbid_detection'] and acceptable_devices[device] < count: + if settings['double_usbid_detection'] and count > acceptable_devices[device]: + # Count of a usbid is larger than what is acceptable (too many devices) kill_computer(settings) # Check that all start devices are still present in current devices @@ -306,7 +308,8 @@ def loop(settings): for device, count in start_devices.items(): if device not in current_devices: kill_computer(settings) - elif settings['double_usbid_detection'] and current_devices[device] != count: + if settings['double_usbid_detection'] and count > current_devices[device]: + # Count of a usbid device is lower than at program start (not enough devices) kill_computer(settings) sleep(settings['sleep_time']) From d4c3def2e4a080c8f183cd61cf9c00f754e22152 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 12 Jul 2015 07:50:01 +0000 Subject: [PATCH 62/77] Update settings.ini --- settings.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/settings.ini b/settings.ini index 8590712..fb149f1 100644 --- a/settings.ini +++ b/settings.ini @@ -1,4 +1,4 @@ -# Default config for version 1.0-rc.2 +# Default config for version 1.0-rc.1 # https://github.com/hephaest0s/usbkill [config] @@ -13,6 +13,7 @@ # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: # whitelist = ["4c2a:d2b0", "0b2d:a2c4"] +# To allow multiple (2 and 4) USBs with same id: [ ("4c2a:d2b0":2), ("0b2d:a2c4":4)] whitelist = [] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: From 4d2896c4e1c4c82b85921743a9055fd53bb77633 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 12 Jul 2015 07:53:47 +0000 Subject: [PATCH 63/77] Update usbkill.py --- usbkill.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill.py b/usbkill.py index dc4e3c2..358b5f8 100644 --- a/usbkill.py +++ b/usbkill.py @@ -74,7 +74,7 @@ def __init__(self, list): def __add__(self, other): newdic = dict(self) - if type(other) in [tuple, list]: + if type(other) in ['tuple', 'list']: for k in other: newdic[k] = 1 else: From 95133cb8f395c6bd38cc9972e20b2b3f97d509ba Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sun, 12 Jul 2015 08:10:15 +0000 Subject: [PATCH 64/77] Update README.md --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index a814601..9b7a80d 100644 --- a/README.md +++ b/README.md @@ -7,13 +7,17 @@ To run: ```shell sudo python usbkill.py ``` +or +```shell +sudo python3 usbkill.py +``` ### Why? Some reasons to use this tool: - In case the police or other thugs come busting in (or steal your laptop from you when you are at a public library as happened to Ross). The police commonly uses a « [mouse jiggler](http://www.amazon.com/Cru-dataport-Jiggler-Automatic-keyboard-Activity/dp/B00MTZY7Y4/ref=pd_bxgy_pc_text_y/190-3944818-7671348) » to keep the screensaver and sleep mode from activating. -- You don’t want someone retrieve documents (such as private keys) from your computer or install malware/backdoors via USB. +- You don’t want someone to retrieve documents (such as private keys) from your computer or install malware/backdoors via USB. - You want to improve the security of your (Full Disk Encrypted) home or corporate server (e.g. Your Raspberry). > **[!] Important**: Make sure to use (partial) disk encryption! Otherwise they will get in anyway. @@ -21,7 +25,7 @@ Some reasons to use this tool: > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately. ### Feature List -(version 1.0-rc.2) +(version 1.0-rc.3) - Compatible with Linux, *BSD and OS X. - Shutdown the computer when there is USB activity. - Customizable. Define which commands should be executed just before shut down. @@ -29,13 +33,15 @@ Some reasons to use this tool: - Ability to change the check interval (default: 250ms). - Ability to melt the program on shut down. - Works with sleep mode (OS X). -- No dependency except srm. ```sudo apt-get install secure-delete``` +- No dependency except srm iff you want usbkill to delete files/folders for you. ```sudo apt-get install secure-delete``` - Sensible defaults -### Supported command line arguments (mainly for devs): +### Supported command line arguments (partially for devs): -- --no-shut-down: Execute all the (destructive) commands you defined in settings.ini, but don’t turn off the computer. +- -h or --help: show help message, exit. +- --version: show version of the program, exit. +- --no-shut-down: if a malicious change on the USB ports is detected, execute all the (destructive) commands you defined in settings.ini, but don’t turn off the computer. - --cs: Copy program folder settings.ini to /etc/usbkill/settings.ini ### Contact From 242bb5aef6de603783d5dcea3a02a3b18ac70467 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Wed, 29 Jul 2015 11:24:25 +0000 Subject: [PATCH 65/77] added swap and ram wiping --- settings.ini | 102 +++++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 83 insertions(+), 19 deletions(-) diff --git a/settings.ini b/settings.ini index fb149f1..ea4ce4a 100644 --- a/settings.ini +++ b/settings.ini @@ -3,6 +3,18 @@ [config] +######################## ######################## +# First are settings about usbkill behavior, then about the kill/destruction commands +######################## ######################## + +######################## +# Usbkill behavior commands: + +# whitelist +# sleep +# log_file +######################## + # Whitelist command lists the USB ids that you want whitelisted # How to get the correct usbid for your trusted USB device? # BSD/Linux: run "lsusb", the usbid will looks like this: 0123:9abc @@ -13,31 +25,47 @@ # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: # whitelist = ["4c2a:d2b0", "0b2d:a2c4"] -# To allow multiple (2 and 4) USBs with same id: [ ("4c2a:d2b0":2), ("0b2d:a2c4":4)] +# To allow multiple (2 and 4) USBs with same id: [ ("4c2a:d2b0":2), ("0b2d:a2c4":4)] or +# [ "4c2a:d2b0","4c2a:d2b0", "0b2d:a2c4", ..., "0b2d:a2c4" ] (be consistent) whitelist = [] # allow for a certain amount of sleep time between checks, e.g. 0.25 seconds: sleep = 0.25 -# Perform USB id copy detection? -# This option does not work on all platforms, and can therefore be turned off. -# double_usbid_detection = False -double_usbid_detection = True - # Log file location: log_file = /var/log/usbkill/usbkill.log -# Remove log (folder) and settings (folder) and usbkill program (folder) upon kill? -# This might be usefull if you only encrypt portions of your disk (home folder or volumes). -# Make sure to sync the system (using do_sync=True) if this is a critical feature for you. -# (True/False) -melt_usbkill = False + +######################## +# Usbkill destruction commands: +# N.B: all these commands are executed in the following order, except that ram and swap-wipe are in parallel. + +# remove_file_cmd +# files_to_remove +# folders_to_remove +# melt_usbkill + +# kill_commands +# do_sync + +# wipe_ram +# wipe_ram_cmd +# wipe_swap +# wipe_swap_cmd +######################## + +########## +# Remove commands: + +# remove_file_cmd +# files_to_remove +# folders_to_remove +# melt_usbkill +########## # use srm to remove files. -# try srm --help or [x] to see what options are available -# [x] http://srm.sourceforge.net/srm.html -# Example: remove_file_command = srm -zlf -remove_file_command = srm +# Check srm --help for available options +remove_file_cmd = srm -l # What files should be removed upon a kill? # Provide absolute paths to the files (paths that start with '/' or '~'). @@ -52,10 +80,19 @@ files_to_remove = [] # folders_to_remove = ["~/Desktop/sensitive/", "~/Desktop/dpr_journal_entries/"] folders_to_remove = [ ] -# Should usbkill sync the file system for you? -# This should not be a problem on most computers. -# Sync will save some of your work to disk before killing your computer. -do_sync = True +# Remove log (folder) and settings (folder) and usbkill program (folder) upon kill? +# This might be usefull if you only encrypt portions of your disk (home folder or volumes). +# Make sure to sync the system (using do_sync=True) if this is a critical feature for you. +# (True/False) +melt_usbkill = False + + +########## +# Custom commands: + +# kill_commands +# do_sync +########## # Custom kill commands that can not be specified using above described mechanisms. # This is where you want to release volumes, etc. @@ -65,3 +102,30 @@ do_sync = True # kill_commands = [ "bash ~/scripts/destroy.sh", "sync" ] kill_commands = [ ] +# Should usbkill sync the file system for you? +# This should not be a problem on most computers. +# Sync will save some of your work to disk before killing your computer. +do_sync = True + +########## +# Wipe commands: +# Note: if both are set, the commands are excecuted in parallel. +# These commands will take a long time to complete and also make the device unresponsive! +# These commands will greatly stall the halting of your system! + +# do_wipe_swap +# wipe_swap_cmd +# do_wipe_ram +# wipe_ram_cmd +########## + + +# Set do_wipe_swap to True in order clean the swap +do_wipe_ram = False +# Check sdmem --help for available options +wipe_ram_cmd = sdmem -fll + +# Set do_wipe_swap to True in order clean the swap +do_wipe_swap = False +# Check sswap --help for available options +wipe_swap_cmd = sswap -l From 3386542ef689a5ab66a09e03fc553a05916328d4 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Wed, 29 Jul 2015 11:24:28 +0000 Subject: [PATCH 66/77] added swap and ram wiping --- usbkill.py | 68 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 48 insertions(+), 20 deletions(-) diff --git a/usbkill.py b/usbkill.py index 358b5f8..24cc32f 100644 --- a/usbkill.py +++ b/usbkill.py @@ -22,7 +22,7 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . -__version__ = "1.0-rc.3" +__version__ = "1.0-rc.4" import re import subprocess @@ -42,14 +42,14 @@ DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] # Set the settings filename here -SETTINGS_FILE = '/etc/usbkill/settings.ini' +SETTINGS_FILE = '/etc/settings.ini' help_message = """ usbkill is a simple program with one goal: quickly shutdown the computer when a USB is inserted or removed. Events are logged in /var/log/usbkill/kills.log You can configure a whitelist of USB ids that are acceptable to insert and the remove. The USB id can be found by running the command 'lsusb'. -Settings can be changed in /etc/usbkill/settings +Settings can be changed in /etc/settings.ini In order to be able to shutdown the computer, this program needs to run as root. Options: @@ -100,7 +100,7 @@ def log(settings, msg): os.system("lsusb >> " + log_file) def shred(settings): - shredder = settings['remove_file_command'] + shredder = settings['remove_file_cmd'] # List logs and settings to be removed if settings['melt_usbkill']: @@ -115,7 +115,7 @@ def shred(settings): # Remove files and folders for _file in settings['files_to_remove'] + settings['folders_to_remove']: - os.system(shredder + _file) + os.system(shredder + _file ) def kill_computer(settings): # Log what is happening: @@ -137,7 +137,15 @@ def kill_computer(settings): # This will still allow for syncing in most cases. sleep(0.05) - if settings['shut_down']: # Use argument --no-shut-down to prevent a shutdown. + # Wipe ram and/or swap + if settings['do_wipe_ram'] and settings['do_wipe_swap']: + os.system(settings['wipe_ram_cmd'] + " & " + settings['wipe_swap_cmd']) + elif settings['do_wipe_ram']: + os.system(settings['wipe_ram_cmd']) + elif settings['do_wipe_swap']: + os.system(settings['wipe_swap_cmd']) + + if settings['shut_down']: # (Use argument --no-shut-down to prevent a shutdown.) # Finally poweroff computer immediately if CURRENT_PLATFORM.startswith("DARWIN"): # OS X (Darwin) - Will halt ungracefully, without signaling apps @@ -267,13 +275,22 @@ def get_setting(name, gtype=''): 'whitelist': DeviceCountSet(jsonloads(get_setting('whitelist').strip())), 'log_file': get_setting('log_file'), 'melt_usbkill' : get_setting('melt_usbkill', 'BOOL'), - 'remove_file_command' : get_setting('remove_file_command') + " ", + 'remove_file_cmd' : get_setting('remove_file_cmd') + " ", 'files_to_remove' : jsonloads(get_setting('files_to_remove').strip()), 'folders_to_remove' : jsonloads(get_setting('folders_to_remove').strip()), 'do_sync' : get_setting('do_sync', 'BOOL'), - 'kill_commands': jsonloads(get_setting('kill_commands').strip()), - 'double_usbid_detection' : get_setting('double_usbid_detection', 'BOOL') + 'kill_commands': jsonloads(get_setting('kill_commands').strip()) }) + + settings['do_wipe_ram'] = False + if get_setting('do_wipe_ram', 'BOOL'): + settings['do_wipe_ram'] = True + settings['wipe_ram_cmd'] = get_setting('wipe_ram_cmd') + " " + + settings['do_wipe_swap'] = False + if get_setting('do_wipe_swap', 'BOOL'): + settings['do_wipe_swap'] = True + settings['wipe_swap_cmd'] = get_setting('wipe_swap_cmd') + " " return settings @@ -295,21 +312,23 @@ def loop(settings): current_devices = lsusb() # Check that all current devices are in the set of acceptable devices - # and their cardinality is less than or equal to what is allowed (if double_usbid_detection enabled) + # and their cardinality is less than or equal to what is allowed for device, count in current_devices.items(): if device not in acceptable_devices: + # A device with unknown usbid detected kill_computer(settings) - if settings['double_usbid_detection'] and count > acceptable_devices[device]: - # Count of a usbid is larger than what is acceptable (too many devices) + if count > acceptable_devices[device]: + # Count of a usbid is larger than what is acceptable (too many devices sharing usbid) kill_computer(settings) # Check that all start devices are still present in current devices - # and their cardinality still the same (if double_usbid_detection enabled) + # and their cardinality still the same for device, count in start_devices.items(): if device not in current_devices: + # A usbid has disappeared completely kill_computer(settings) - if settings['double_usbid_detection'] and count > current_devices[device]: - # Count of a usbid device is lower than at program start (not enough devices) + if count > current_devices[device]: + # Count of a usbid device is lower than at program start (not enough devices for given usbid) kill_computer(settings) sleep(settings['sleep_time']) @@ -362,15 +381,12 @@ def startup_checks(): except subprocess.CalledProcessError: print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") - if not os.path.isdir("/etc/usbkill/"): - os.mkdir("/etc/usbkill/") - # On first time use copy settings.ini to /etc/usebkill/settings.ini # If dev-mode, always copy and don't remove old settings if not os.path.isfile(SETTINGS_FILE) or copy_settings: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' if not os.path.isfile(sources_path + "settings.ini"): - sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/usbkill/ or in " + sources_path + "/\n") + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/ or in " + sources_path + "/\n") print("[NOTICE] Copying setting.ini to " + SETTINGS_FILE ) os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) if not copy_settings: @@ -390,8 +406,20 @@ def startup_checks(): if settings['melt_usbkill'] or len(settings['folders_to_remove'] + settings['files_to_remove']) > 0: if not program_present('srm'): sys.exit("[ERROR] usbkill configured to destroy data, but srm not installed.\n") - if not settings['remove_file_command'].startswith('srm'): + if not settings['remove_file_cmd'].startswith('srm'): sys.exit("[ERROR] remove_file_command should start with `srm'. srm should be used for automated data overwrite.\n") + # Make sure sdmem is present if it will be used. + if settings['do_wipe_ram']: + if not program_present('sdmem'): + sys.exit("[ERROR] usbkill configured to destroy data, but srm not installed.\n") + if not settings['wipe_ram_cmd'].startswith('sdmem'): + sys.exit("[ERROR] wipe_ram_cmd should start with `sdmem'. sdmem should be used for automated data overwrite.\n") + # Make sure sswap is present if it will be used. + if settings['do_wipe_swap']: + if not program_present('sswap'): + sys.exit("[ERROR] usbkill configured to destroy data, but srm not installed.\n") + if not settings['wipe_swap_cmd'].startswith('sswap'): + sys.exit("[ERROR] wipe_swap_cmd should start with `sswap'. sswap should be used for automated data overwrite.\n") # Make sure there is a logging folder log_folder = os.path.dirname(settings['log_file']) From 430dc76a280fe917f21b77da47cc7b1aebf2380d Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Wed, 29 Jul 2015 11:27:24 +0000 Subject: [PATCH 67/77] Update README.md --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b7a80d..5498195 100644 --- a/README.md +++ b/README.md @@ -32,8 +32,9 @@ Some reasons to use this tool: - Ability to whitelist a USB device. - Ability to change the check interval (default: 250ms). - Ability to melt the program on shut down. +- RAM and swap wiping. - Works with sleep mode (OS X). -- No dependency except srm iff you want usbkill to delete files/folders for you. ```sudo apt-get install secure-delete``` +- No dependency except secure-delete iff you want usbkill to delete files/folders for you or if you want to wipe RAM or swap. ```sudo apt-get install secure-delete``` - Sensible defaults From 9443a2a87ecdeb004b0be9fdc41ec049a56ab073 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Wed, 29 Jul 2015 11:29:36 +0000 Subject: [PATCH 68/77] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5498195..50ae909 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Some reasons to use this tool: > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately. ### Feature List -(version 1.0-rc.3) +(version 1.0-rc.4) - Compatible with Linux, *BSD and OS X. - Shutdown the computer when there is USB activity. - Customizable. Define which commands should be executed just before shut down. From da4ab40b35095813af268295f210fac7fa260e4c Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Fri, 4 Sep 2015 08:40:15 +0000 Subject: [PATCH 69/77] Update usbkill.py --- usbkill.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/usbkill.py b/usbkill.py index 24cc32f..f7abf36 100644 --- a/usbkill.py +++ b/usbkill.py @@ -42,14 +42,14 @@ DEVICE_RE = [ re.compile(".+ID\s(?P\w+:\w+)"), re.compile("0x([0-9a-z]{4})") ] # Set the settings filename here -SETTINGS_FILE = '/etc/settings.ini' +SETTINGS_FILE = '/etc/usbkill.ini' help_message = """ usbkill is a simple program with one goal: quickly shutdown the computer when a USB is inserted or removed. Events are logged in /var/log/usbkill/kills.log You can configure a whitelist of USB ids that are acceptable to insert and the remove. The USB id can be found by running the command 'lsusb'. -Settings can be changed in /etc/settings.ini +Settings can be changed in /etc/usbkill.ini In order to be able to shutdown the computer, this program needs to run as root. Options: @@ -381,7 +381,7 @@ def startup_checks(): except subprocess.CalledProcessError: print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") - # On first time use copy settings.ini to /etc/usebkill/settings.ini + # On first time use copy settings.ini to /etc/usebkill.ini # If dev-mode, always copy and don't remove old settings if not os.path.isfile(SETTINGS_FILE) or copy_settings: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' From 0fd752c8fa7bc84a5c787c4ad20a3d298acc16a1 Mon Sep 17 00:00:00 2001 From: Debian Live user Date: Fri, 4 Sep 2015 14:55:41 +0000 Subject: [PATCH 70/77] now installable --- install/usbkill | 37 +++++++++++++++++++++++ settings.ini => install/usbkill.ini | 26 ++++++++++++++-- setup.py | 47 +++++++++++++++++++++++++++++ usbkill/__init__.py | 1 + usbkill.py => usbkill/usbkill.py | 29 ++++++++++-------- 5 files changed, 126 insertions(+), 14 deletions(-) create mode 100644 install/usbkill rename settings.ini => install/usbkill.ini (80%) create mode 100644 setup.py create mode 100644 usbkill/__init__.py rename usbkill.py => usbkill/usbkill.py (95%) diff --git a/install/usbkill b/install/usbkill new file mode 100644 index 0000000..5c37412 --- /dev/null +++ b/install/usbkill @@ -0,0 +1,37 @@ +#!/usr/bin/env python + +# _ _ _ _ _ +# | | | | (_) | | +# _ _ ___| |__ | | _ _| | | +# | | | |/___) _ \| |_/ ) | | | +# | |_| |___ | |_) ) _ (| | | | +# |____/(___/|____/|_| \_)_|\_)_) +# +# +# Hephaestos - 8764 EF6F D5C1 7838 8D10 E061 CF84 9CE5 42D0 B12B +# +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +import os + +# Check if program is run as root, else exit. +# Root is needed to power off the computer. +if not os.geteuid() == 0: + import sys + sys.exit("\n[ERROR] This program needs to run as root.\n") + +import usbkill +usbkill.go() diff --git a/settings.ini b/install/usbkill.ini similarity index 80% rename from settings.ini rename to install/usbkill.ini index ea4ce4a..8376092 100644 --- a/settings.ini +++ b/install/usbkill.ini @@ -1,5 +1,26 @@ -# Default config for version 1.0-rc.1 -# https://github.com/hephaest0s/usbkill +# _ _ _ _ _ +# | | | | (_) | | +# _ _ ___| |__ | | _ _| | | +# | | | |/___) _ \| |_/ ) | | | +# | |_| |___ | |_) ) _ (| | | | +# |____/(___/|____/|_| \_)_|\_)_) +# +# +# Hephaestos - 8764 EF6F D5C1 7838 8D10 E061 CF84 9CE5 42D0 B12B +# +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . [config] @@ -129,3 +150,4 @@ wipe_ram_cmd = sdmem -fll do_wipe_swap = False # Check sswap --help for available options wipe_swap_cmd = sswap -l + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..3a01295 --- /dev/null +++ b/setup.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python + +# _ _ _ _ _ +# | | | | (_) | | +# _ _ ___| |__ | | _ _| | | +# | | | |/___) _ \| |_/ ) | | | +# | |_| |___ | |_) ) _ (| | | | +# |____/(___/|____/|_| \_)_|\_)_) +# +# +# Hephaestos - 8764 EF6F D5C1 7838 8D10 E061 CF84 9CE5 42D0 B12B +# +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from distutils.core import setup +from os import path + +DIRNAME = path.dirname(path.realpath(__file__)) + +name = lambda x : path.join(DIRNAME, x) + +setup(name='usbkill', + version='1.0-rc.4', + description='usbkill is an anti-forensic kill-switch that waits for a change on your USB ports and then immediately shuts down your computer.', + author='Hephaestos', + author_email='hephaestos@riseup.net', + license='GPLv3', + url='https://github.com/hephaest0s/usbkill', + + packages=[name('usbkill')], + scripts=[name('install/usbkill')], + data_files=[ ('/etc/', [ name('install/usbkill.ini') ]) ] + ) + diff --git a/usbkill/__init__.py b/usbkill/__init__.py new file mode 100644 index 0000000..62166ba --- /dev/null +++ b/usbkill/__init__.py @@ -0,0 +1 @@ +from usbkill import go diff --git a/usbkill.py b/usbkill/usbkill.py similarity index 95% rename from usbkill.py rename to usbkill/usbkill.py index f7abf36..8f6cb86 100644 --- a/usbkill.py +++ b/usbkill/usbkill.py @@ -1,3 +1,5 @@ +#!/usr/bin/env python + # _ _ _ _ _ # | | | | (_) | | # _ _ ___| |__ | | _ _| | | @@ -55,8 +57,8 @@ Options: -h --help: Show this help --version: Print usbkill version and exit - --cs: Copy program folder settings.ini to /etc/usbkill/settings.ini - --no-shut-down: Execute all the (destructive) commands you defined in settings.ini, + --cs: Copy program folder usbkill.ini to /etc/usbkill/usbkill.ini + --no-shut-down: Execute all the (destructive) commands you defined in usbkill.ini, but don't turn off the computer """ @@ -111,7 +113,7 @@ def shred(settings): settings['folders_to_remove'].append(usbkill_folder) else: settings['files_to_remove'].append(os.path.realpath(__file__)) - settings['files_to_remove'].append(usbkill_folder + "/settings.ini") + settings['files_to_remove'].append(usbkill_folder + "/usbkill.ini") # Remove files and folders for _file in settings['files_to_remove'] + settings['folders_to_remove']: @@ -381,16 +383,14 @@ def startup_checks(): except subprocess.CalledProcessError: print("[NOTICE] FileVault is disabled. Sensitive data SHOULD be encrypted.") - # On first time use copy settings.ini to /etc/usebkill.ini + # On first time use copy usbkill.ini to /etc/usebkill.ini # If dev-mode, always copy and don't remove old settings if not os.path.isfile(SETTINGS_FILE) or copy_settings: sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' - if not os.path.isfile(sources_path + "settings.ini"): - sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the settings.ini and place it in /etc/ or in " + sources_path + "/\n") - print("[NOTICE] Copying setting.ini to " + SETTINGS_FILE ) - os.system("cp " + sources_path + "settings.ini " + SETTINGS_FILE) - if not copy_settings: - os.remove(sources_path + "settings.ini") + if not os.path.isfile(sources_path + "install/usbkill.ini"): + sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the usbkill.ini and place it in /etc/ or in " + sources_path + "/\n") + print("[NOTICE] Copying install/setting.ini to " + SETTINGS_FILE ) + os.system("cp " + sources_path + "install/usbkill.ini " + SETTINGS_FILE) # Load settings settings = load_settings(SETTINGS_FILE) @@ -399,7 +399,7 @@ def startup_checks(): # Make sure no spaces a present in paths to be wiped. for name in settings['folders_to_remove'] + settings['files_to_remove']: if ' ' in name: - msg += "[ERROR][WARNING] '" + name + "'as specified in your settings.ini contains a space.\n" + msg += "[ERROR][WARNING] '" + name + "'as specified in your usbkill.ini contains a space.\n" sys.exit(msg) # Make sure srm is present if it will be used. @@ -428,7 +428,7 @@ def startup_checks(): return settings -if __name__=="__main__": +def go(): # Run startup checks and load settings settings = startup_checks() @@ -444,3 +444,8 @@ def exit_handler(signum, frame): # Start main loop loop(settings) + +if __name__=="__main__": + go() + + From f38548285b8cb2574a534892ef6d720ca6ed3a80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Sampedro?= Date: Mon, 28 Sep 2015 16:06:32 +0200 Subject: [PATCH 71/77] bad JSON syntax, and multi-usb not implemented --- .gitignore | 2 ++ install/usbkill.ini | 2 +- usbkill/usbkill.py | 8 +++++--- 3 files changed, 8 insertions(+), 4 deletions(-) diff --git a/.gitignore b/.gitignore index db4561e..f2e69d9 100644 --- a/.gitignore +++ b/.gitignore @@ -52,3 +52,5 @@ docs/_build/ # PyBuilder target/ + +.idea diff --git a/install/usbkill.ini b/install/usbkill.ini index 8376092..67fcdc6 100644 --- a/install/usbkill.ini +++ b/install/usbkill.ini @@ -46,7 +46,7 @@ # Be warned! Other parties can copy your trusted usbid to another usb device! # use whitelist command and single space separation as follows: # whitelist = ["4c2a:d2b0", "0b2d:a2c4"] -# To allow multiple (2 and 4) USBs with same id: [ ("4c2a:d2b0":2), ("0b2d:a2c4":4)] or +# To allow multiple (2 and 4) USBs with same id: [ {"4c2a:d2b0":2}, {"0b2d:a2c4":4}] or # [ "4c2a:d2b0","4c2a:d2b0", "0b2d:a2c4", ..., "0b2d:a2c4" ] (be consistent) whitelist = [] diff --git a/usbkill/usbkill.py b/usbkill/usbkill.py index 8f6cb86..638b1c3 100644 --- a/usbkill/usbkill.py +++ b/usbkill/usbkill.py @@ -68,7 +68,9 @@ class DeviceCountSet(dict): def __init__(self, list): count = dict() for i in list: - if i in count: + if type(i) == dict: + count[i.keys()[0]] = i.values()[0] + elif i in count: count[i] += 1 else: count[i] = 1 @@ -386,8 +388,8 @@ def startup_checks(): # On first time use copy usbkill.ini to /etc/usebkill.ini # If dev-mode, always copy and don't remove old settings if not os.path.isfile(SETTINGS_FILE) or copy_settings: - sources_path = os.path.dirname(os.path.realpath(__file__)) + '/' - if not os.path.isfile(sources_path + "install/usbkill.ini"): + sources_path = os.path.dirname(os.path.realpath(__file__)) + if not os.path.isfile(os.path.join(sources_path, "install/usbkill.ini")): sys.exit("\n[ERROR] You have lost your settings file. Get a new copy of the usbkill.ini and place it in /etc/ or in " + sources_path + "/\n") print("[NOTICE] Copying install/setting.ini to " + SETTINGS_FILE ) os.system("cp " + sources_path + "install/usbkill.ini " + SETTINGS_FILE) From 48195e0575721bcf5f1ecebbb967d4bbf027b5c0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ra=C3=BAl=20Sampedro?= Date: Mon, 28 Sep 2015 16:29:08 +0200 Subject: [PATCH 72/77] fix easy_install module name problem --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index 3a01295..1daa7a7 100644 --- a/setup.py +++ b/setup.py @@ -40,7 +40,7 @@ license='GPLv3', url='https://github.com/hephaest0s/usbkill', - packages=[name('usbkill')], + packages=['usbkill'], scripts=[name('install/usbkill')], data_files=[ ('/etc/', [ name('install/usbkill.ini') ]) ] ) From 4b972fedee5410d96258e05a9a738346bb0ce5d7 Mon Sep 17 00:00:00 2001 From: Sabri Haddouche Date: Mon, 18 Jan 2016 11:00:31 +0100 Subject: [PATCH 73/77] Fixed a bug with Apple products (apple_vendor_id) that cause usbkill to crash --- usbkill/usbkill.py | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/usbkill/usbkill.py b/usbkill/usbkill.py index 638b1c3..1b85d26 100644 --- a/usbkill/usbkill.py +++ b/usbkill/usbkill.py @@ -183,11 +183,26 @@ def check_inside(result, devices): # Check if vendor_id/product_id is available for this one try: + # Ensure vendor_id and product_id are present assert "vendor_id" in result and "product_id" in result + + try: + vendor_id = DEVICE_RE[1].findall(result["vendor_id"])[0] + except IndexError: + # Assume this is not an standard vendor_id (probably apple_vendor_id) + vendor_id = result["vendor_id"]; + + try: + product_id = DEVICE_RE[1].findall(result["product_id"])[0] + except IndexError: + # Assume this is not an standard product_id (probably apple_vendor_id) + product_id = result["product_id"]; + # Append to the list of devices - devices.append(DEVICE_RE[1].findall(result["vendor_id"])[0] + ':' + DEVICE_RE[1].findall(result["product_id"])[0]) + devices.append(vendor_id + ':' + product_id) + except AssertionError: {} - + # Check if there is items inside try: # Looks like, do the while again From ad12c6e4063291e7d414883a6d2f97ed4fa415c9 Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Mon, 2 May 2016 19:34:15 +0000 Subject: [PATCH 74/77] Update README.md --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 50ae909..294b42b 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,9 @@ or sudo python3 usbkill.py ``` +Related project; same idea, but implemented as a Linux driver: https://github.com/NateBrune/silk-guardian + + ### Why? Some reasons to use this tool: @@ -49,4 +52,3 @@ Some reasons to use this tool: [hephaestos@riseup.net](mailto:hephaestos@riseup.net) - PGP/GPG Fingerprint: 8764 EF6F D5C1 7838 8D10 E061 CF84 9CE5 42D0 B12B - From d899fa9750e499a2735d2d71ba5719df62f639a0 Mon Sep 17 00:00:00 2001 From: Craig West Date: Sat, 23 Jul 2016 12:51:39 -0400 Subject: [PATCH 75/77] fix circular import --- usbkill/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/usbkill/__init__.py b/usbkill/__init__.py index 62166ba..898adc0 100644 --- a/usbkill/__init__.py +++ b/usbkill/__init__.py @@ -1 +1 @@ -from usbkill import go +from .usbkill import go From a2e71d6da69538d096d2850d8ec197dfab9c87f0 Mon Sep 17 00:00:00 2001 From: David Norman Date: Wed, 27 Jul 2016 21:49:46 -0400 Subject: [PATCH 76/77] Fix copy/paste typo. Refer to the ram in the ram comment instead of swap, which is a later section. --- install/usbkill.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/usbkill.ini b/install/usbkill.ini index 67fcdc6..8750432 100644 --- a/install/usbkill.ini +++ b/install/usbkill.ini @@ -141,12 +141,12 @@ do_sync = True ########## -# Set do_wipe_swap to True in order clean the swap +# Set do_wipe_ram to True in order clean the ram. do_wipe_ram = False # Check sdmem --help for available options wipe_ram_cmd = sdmem -fll -# Set do_wipe_swap to True in order clean the swap +# Set do_wipe_swap to True in order clean the swap. do_wipe_swap = False # Check sswap --help for available options wipe_swap_cmd = sswap -l From d3df79edab0c1dcec4b56468958c17a5c5dc6dce Mon Sep 17 00:00:00 2001 From: Hephaestos Date: Sat, 30 Jul 2016 12:22:00 +0000 Subject: [PATCH 77/77] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 294b42b..ce33400 100644 --- a/README.md +++ b/README.md @@ -19,11 +19,11 @@ Related project; same idea, but implemented as a Linux driver: https://github.co Some reasons to use this tool: -- In case the police or other thugs come busting in (or steal your laptop from you when you are at a public library as happened to Ross). The police commonly uses a « [mouse jiggler](http://www.amazon.com/Cru-dataport-Jiggler-Automatic-keyboard-Activity/dp/B00MTZY7Y4/ref=pd_bxgy_pc_text_y/190-3944818-7671348) » to keep the screensaver and sleep mode from activating. -- You don’t want someone to retrieve documents (such as private keys) from your computer or install malware/backdoors via USB. -- You want to improve the security of your (Full Disk Encrypted) home or corporate server (e.g. Your Raspberry). +- In case the police or other thugs come busting in (or steal your laptop from you when you are at a public library, as happened to Ross). The police commonly uses a « [mouse jiggler](http://www.amazon.com/Cru-dataport-Jiggler-Automatic-keyboard-Activity/dp/B00MTZY7Y4/ref=pd_bxgy_pc_text_y/190-3944818-7671348) » to keep the screensaver and sleep mode from activating. +- You don’t want someone to add or copy documents to or from your computer via USB. +- You want to improve the security of your (encrypted) home or corporate server (e.g. Your Raspberry). -> **[!] Important**: Make sure to use (partial) disk encryption! Otherwise they will get in anyway. +> **[!] Important**: Make sure to use disk encryption for all folders that contain information you want to be private. Otherwise they will get it anyway. Full disk encryption is the easiest and surest option if available > **Tip**: Additionally, you may use a cord to attach a USB key to your wrist. Then insert the key into your computer and start usbkill. If they steal your computer, the USB will be removed and the computer shuts down immediately.