From ebfeaf84ae6220a1063068a317973be0ef2f4e6c Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Tue, 6 Jun 2023 22:03:58 +0200 Subject: [PATCH 01/18] Journal entry and trying to fix no-proc-kill-bug. --- doc/dev_journal.md | 9 +++++++-- src/kldetect.py | 2 +- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/doc/dev_journal.md b/doc/dev_journal.md index 86146a8..4318938 100644 --- a/doc/dev_journal.md +++ b/doc/dev_journal.md @@ -120,6 +120,11 @@ Test in VM and finnishing touches to smooth things out. Did a first test in a Fedora 37 VM. At first it didn't work. Then I tested it on my normal machine and it also stopped working. It turned out that it was just not getting the root priviledges right, even tho it passed the root check. Testing on the VM was then succesfull in that it found the pids. Killing the process wasn't succesfull and will need further testing and fixes. #### TODO: 1. Fix the bug where the killing of the process doesn't work. -2. Build config files, maybe check if there is a better way to to do configs than with .txt files. +~~2. Build config files, maybe check if there is a better way to to do configs than with .txt files.~~ (finnished 6.06.23) 3. Keep testing. Goal is that if run as '''$ sudo ./kldetect.py -v''' one is prompted to kill the keylogger, and then rerunning the programm would give the output '''[+] No suspicious programms found''' -4. Note to self: Problem with killing is that not using pids-program dict to choose which program to kill. \ No newline at end of file +~~4. Note to self: Problem with killing is that not using pids-program dict to choose which program to kill.~~ +#### Later that same say +Configuration is now done with json to keep it all central. +Test with json configuration works. +Killing a process still doesn't work: +''' TypeError: 'str' object cannot be interpreted as integer ''' diff --git a/src/kldetect.py b/src/kldetect.py index a3a4898..b15b26a 100755 --- a/src/kldetect.py +++ b/src/kldetect.py @@ -201,7 +201,7 @@ def detect_keyloggers(): # Get program names for pid in pids: program_name = get_program_name(pid) - program_pid_dict[program_name] = pid + program_pid_dict[program_name] = int(pid) if auto_kill_option and program_name in auto_kill_programs: os.kill(pid, signal.SIGKILL) if verbose_option: From 529f45dd82b011586641d270e78528360dea75a0 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Tue, 6 Jun 2023 22:07:08 +0200 Subject: [PATCH 02/18] DEBUG --- src/kldetect.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/kldetect.py b/src/kldetect.py index b15b26a..9ed2344 100755 --- a/src/kldetect.py +++ b/src/kldetect.py @@ -148,6 +148,8 @@ def confirm_kill_programs(programs, times=0): # kill list of processes def kill_processes(pids): + print(pids) ## DEBUG + print("Killing processes with pids:", pids) for pid in pids: os.kill(pid, signal.SIGKILL) if verbose_option: From dba2deb1e54ff97e51024e2b10ad530e821688c5 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Tue, 6 Jun 2023 22:26:18 +0200 Subject: [PATCH 03/18] DEBUGing issue where only one pid per programm can be killed --- src/kldetect.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/kldetect.py b/src/kldetect.py index 9ed2344..04a03d2 100755 --- a/src/kldetect.py +++ b/src/kldetect.py @@ -203,7 +203,7 @@ def detect_keyloggers(): # Get program names for pid in pids: program_name = get_program_name(pid) - program_pid_dict[program_name] = int(pid) + program_pid_dict[program_name] = program_pid_dict[program_name].append(int(pid)) if auto_kill_option and program_name in auto_kill_programs: os.kill(pid, signal.SIGKILL) if verbose_option: @@ -270,9 +270,9 @@ def detect_keyloggers(): ############################### # Step 5: Save config ############################### - config['auto_kill_programs'] = auto_kill_programs - config['white_listed_programs'] = white_listed_programs - config['kbd_names'] = kbd_names + config['auto_kill_programs'] = list(set(auto_kill_programs)) + config['white_listed_programs'] = list(set(white_listed_programs)) + config['kbd_names'] = list(set(kbd_names)) save_config(config) From e942206b1221d75909ff8f126c6cfadf22215666 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 00:51:07 +0200 Subject: [PATCH 04/18] Refactor userspace detector. --- .gitignore | 1 + src/config.py | 50 ++++---- src/keylogger_detector.py | 240 +++++++++++++++++++++++++++++++++++ src/{ => legacy}/kldetect.py | 31 ----- src/utils.py | 113 +++++++++++++++++ 5 files changed, 378 insertions(+), 57 deletions(-) create mode 100755 src/keylogger_detector.py rename src/{ => legacy}/kldetect.py (90%) create mode 100755 src/utils.py diff --git a/.gitignore b/.gitignore index e7e5ccc..013adba 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ +__pycache__/ *.pdf /docs/research/ /build/ diff --git a/src/config.py b/src/config.py index 644f7c5..5802398 100755 --- a/src/config.py +++ b/src/config.py @@ -4,43 +4,41 @@ import os CONFIG_FILE = 'config.json' def load_config(): + """ + Load the configuration from the JSON file or create a new one if it doesn't exist + + Returns: + dict: The configuration data + """ config = {} - # Check if the configuration file exists if os.path.exists(CONFIG_FILE): try: with open(CONFIG_FILE, 'r') as file: config = json.load(file) - except (IOError, json.JSONDecodeError) as e: - print(f"Error loading configuration: {e}") + except: + print("[-] Error: Failed to load config file") + else: + config = { + 'white_listed_programs': [], + 'auto_kill_programs': [], + 'kbd_names': ['kbd'] + } + save_config(config) return config + def save_config(config): + """ + Save the configuration to the JSON file + + Args: + config (dict): The configuration data + """ try: with open(CONFIG_FILE, 'w') as file: json.dump(config, file, indent=4) - except IOError as e: - print(f"Error saving configuration: {e}") - -# Load the configuration -config_data = load_config() - -# Access and modify the settings -whitelist = config_data.get('whitelist', []) -autokill_list = config_data.get('autokill_list', []) -other_setting = config_data.get('other_setting') - -# Add a process to the whitelist -whitelist.append(9999) - -# Remove a process from the autokill list -if 1234 in autokill_list: - autokill_list.remove(1234) - -# Modify the other_setting value -config_data['other_setting'] = 'new_value' - -# Save the modified configuration back to the JSON file -save_config(config_data) + except: + print("[-] Error: Failed to save config file") diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py new file mode 100755 index 0000000..22860e2 --- /dev/null +++ b/src/keylogger_detector.py @@ -0,0 +1,240 @@ +#!/usr/bin/env python3 + +import sys +from config import CONFIG_FILE, load_config, save_config +from utils import ( + check_root, + check_packages, + get_keyboard_device_files, + get_real_path, + get_pids_using_file, + get_process_name, + kill_processes + ) + +# Global variables + +auto_kill_option = False +verbose_option = False +safe_option = False + +# Functions + +def print_help(): + print('Usage: python3 keylogger_detector.py [OPTIONS]') + print('Options:') + print(' -h, --help\t\t\tPrint this help message') + print(' -v, --verbose\t\t\tVerbose mode. Will cause additional information to be printed during execution') + print(' -a, --auto-kill\t\tAutomatically kill blacklisted processes') + print(' -s, --safe\t\t\tSafe mode. Asked to confirm before killing a process') + +def set_input_options(): + """ + Set input options based on command line arguments + + Invalid arguments are ignored + + Raises: + SystemExit: If -h or --help is passed as an argument, the help message is printed and the program exits + """ + + global auto_kill_option, verbose_option, safe_option + if len(sys.argv) > 1: + for arg in sys.argv[1:]: + if arg == '-h' or arg == '--help': + print_help() + sys.exit(0) + elif arg == '-v' or arg == '--verbose': + verbose_option = True + elif arg == '-a' or arg == '--auto-kill': + auto_kill_option = True + elif arg == '-s' or arg == '--safe': + safe_option = True + + +def confirm_kill_procces(process_name, times=0): + """ + Prompt the user to confirm to kill a process. + Should be used only in safe mode. + + Args: + process_name (str): Name of the process to kill + times (int) : Number of times prompt has been displayed but neither y nor n where given. Defaults to 0. + Use to limit promt attempts. + + Returns: + bool: True if user confirms the kill, False otherwise. + + Raises: + SystemExit: If the user has given invalid input more than 5 times, the program exits. + """ + if times > 5: + print('Too many invalid inputs. Exiting.') + sys.exit(1) + if times > 0: + print('Invalid input. Please enter y or n.') + print(f'Do you want to kill {process_name}? (y/n)') + answer = input() + if answer == 'y': + return True + elif answer == 'n': + return False + else: + return confirm_kill_procces(process_name, times+1) + +def detect_keyloggers(): + """ + Detect (userland) keylogger processes based on which processes have a keyboard file open (/dev/input/event*) + + The main function of the program. + Will attempt to detect keyloggers based on the config file, command line arguments and user input. + Here the control flow and logic of the program are defined. + """ + + ############################ + # 1. Setup and initialization + ############################ + global auto_kill_option, verbose_option, safe_option + global CONFIG_FILE + set_input_options() + if verbose_option: + print('[Verbose] Input options set') + + check_root() + if verbose_option: + print('[Verbose] Root access checked') + + check_packages() + if verbose_option: + print('[Verbose] Packages checked') + + config = load_config() + if verbose_option: + print('[Verbose] Config file loaded') + + white_listed_programs = config['white_listed_programs'] + auto_kill_programs = config['auto_kill_programs'] + kbd_names = config['kbd_names'] + if verbose_option: + print('[Verbose] Config file parsed') + + ############################ + # 2. Get device files mapped to keyboard + ############################ + keyboard_device_files = get_keyboard_device_files(kbd_names) + if verbose_option: + print('[Verbose] Keyboard device files found:', keyboard_device_files) + + ############################ + # 3. Get pids using keyboard device files + ############################ + pids = [] + for device_file in keyboard_device_files: + pids.append(get_pids_using_file(device_file)) + pids = sorted(list(set(pids))) + if verbose_option: + print('[Verbose] Process IDs using keyboard device files:', pids) + + ############################ + # 4. Get process names using pids + ############################ + process_names = [] + name_pid_dict = {} + for pid in pids: + name = get_process_name(pid) + process_names.append(name) + name_pid_dict[name].add(pid) + process_names = sorted(list(set(process_names))) + if verbose_option: + print('[Verbose] Process names using keyboard device files:', process_names) + + ############################ + # 5.If auto_kill option is set, kill auto-killable processes + ############################ + if auto_kill_option: + for name in process_names: + if name in auto_kill_programs: + if verbose_option: + print('[Verbose] Auto-killable process found:', name) + if safe_option: + if confirm_kill_procces(name): + kill_process(name_pid_dict[name]) + else: + kill_process(name_pid_dict[name]) + + ############################ + # 6. Identify suspicious processes, i.e. those not whitelisted + ############################ + suspicious_processes = [] + for name in process_names: + if name not in white_listed_programs: + suspicious_processes.append(name) + if verbose_option: + print('[Verbose] Suspicious processes found:', suspicious_processes) + print('[Verbose] Suspicious processes not killed:', [name for name in suspicious_processes if name not in auto_kill_programs]) + print('[Verbose] Suspicious processes killed:', [name for name in suspicious_processes if name in auto_kill_programs]) + + + ############################ + # 6.1 If no suspicious processes are found, exit + ############################ + if len(suspicious_processes) == 0: + print("[+] No suspicious processes found") + sys.exit(0) + + ############################ + # 7. Prompt user to chose which processes (not covered by auto kill if set) to kill + ############################ + print('[-]The following suspicious processes were found:') + for name in suspicious_processes: + print(f'\t{name}') + print('Please enter the names of the processes to kill, separated by a space.') + print('To not kill any just hit enter.') + if safe_option: + print('[Info] You are in safe mode. In safe mode you will be asked to confirm each kill.') + else: + print('[Info] Please be aware that killing an important process may cause your system to crash.') + + to_kill = input().split() + if len(to_kill) == 0: + print('[+] No processes killed.') + sys.exit(0) + + if verbose_option: + print('[Verbose] Processes to kill:', to_kill) + if safe_option: + for name in to_kill: + for pid in name_pid_dict[name]: + if confirm_kill_procces(name): + kill_process(id) + if verbose_option: + print('[Verbose] Process killed:', name) + else: + for name in to_kill: + for pid in name_pid_dict[name]: + kill_process(id) + if verbose_option: + print('[Verbose] Process killed:', name) + + + to_kill = list(set(to_kill)) + auto_kill_programs = list(set(auto_kill_programs)).append(to_kill) + config['auto_kill_programs'] = auto_kill_programs + white_listed_programs = list(set(white_listed_programs)) + config['white_listed_programs'] = white_listed_programs + kbd_names = list(set(kbd_names)) + config['kbd_names'] = kbd_names + save_config(config, CONFIG_FILE) + if verbose_option: + print('[Verbose] Config file saved') + +if __name__ == '__main__': + detect_keyloggers() + + + + + + + + diff --git a/src/kldetect.py b/src/legacy/kldetect.py similarity index 90% rename from src/kldetect.py rename to src/legacy/kldetect.py index 04a03d2..90a85e3 100755 --- a/src/kldetect.py +++ b/src/legacy/kldetect.py @@ -13,37 +13,6 @@ auto_kill_option = False verbose_option = False safe_option = False - -# Load Configurations -def load_config(): - - config = {} - - # Check if file exists - if os.path.exists(CONFIG_FILE): - try: - with open(CONFIG_FILE, 'r') as file: - config = json.load(file) - except: - print("[-] Error: Failed to load config file") - else: - config = { - 'white_listed_programs': [], - 'auto_kill_programs': [], - 'kbd_names': ['kbd'] - } - save_config(config) # Save the default configuration - - return config - -# Save new configurations to json file -def save_config(config): - try: - with open(CONFIG_FILE, 'w') as file: - json.dump(config, file) - except IOError as e: - print(f"[-] Error! Failed to save config file: {e}") - # Check if the user is in sudo mode def check_sudo(): if os.geteuid() != 0: diff --git a/src/utils.py b/src/utils.py new file mode 100755 index 0000000..b7cb085 --- /dev/null +++ b/src/utils.py @@ -0,0 +1,113 @@ +import os # for path operations, getuid, kill +import subprocess # for executing shell commands +import signal # for sending signals to processes +import sys # for exit + +def check_root(): + """ + Check if script is run as root(sudo). + + Raises: + SystemExit: If not run as root. + """ + if os.getuid() != 0: + print("[-] Please run as root.") + sys.exit(1) + +def check_packages(): + """ + Check if all required packages are installed. + + Raises: + SystemExit: If any packges is missing. + """ + packages = ['fuser', 'which'] + missing_packages = [] + + for package in packages: + if subprocess.call(['which', package]) != 0: + missing_packages.append(package) + if len(missing_packages) > 0: + print("[-] Missing packages: {}".format(', '.join(missing_packages))) + sys.exit(1) + +def get_keyboard_device_files(names): + """ + Get paths corresponding to keyboard device files by searching /dev/input/by-path. + Uses get_real_path() to resolve symlinks. + + Args: + names (list): List of strings to use for searching. e.g.['kbd'] + + Returns: + str: Path to keyboard device file. + + """ + keyboard_device_files = [] + for root, dirs, files in os.walk('/dev/input/by-path'): + for file in files: + if any(name in files for name in names): + keyboard_device_files.append(get_real_path(os.path.join(root, file))) + return keyboard_device_files + +def get_real_path(path): + """ + Resolve a path of a file. + Args: + path (str): Path to a file. Possibly a symlink. + + Returns: + str: The resolved (real) path. + """ + if os.path.islink(path): + return os.path.realpath(path) + else: + return path + +def get_pids_using_file(path): + """ + Get all process IDs using a file. (Essentially a wrapper for fuser.) + + Args: + path (str): Path to a file. Usually /dev/input/eventX. + + Returns: + list: List of process IDs. + + Raises: + SystemExit: If fuser fails to run. + """ + try: + pids = subprocess.check_output(['fuser', path]).decode('utf-8').split() + except subprocess.CalledProcessError: + print("[-] Error: fuser failed to run on", path) + sys.exit(1) + return pids + +def get_process_name(pid): + """ + Get the name of a process. + + Args: + pid (int): Process ID. + + Returns: + str: Name of the process. + """ + with open('/proc/{}/comm'.format(pid)) as f: + return f.read().strip() + +def kill_processes(pids): + """ + Kill processes. + + Args: + pids (list): List of process IDs. + """ + + for pid in pids: + try: + os.kill(int(pid), signal.SIGKILL) + except ProcessLookupError: + print("[-] Process {} not found.".format(pid)) + From cde1ccea8adde39e574c29fe4fc0105de2c57427 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 00:57:03 +0200 Subject: [PATCH 05/18] Update dev_journal.md --- doc/dev_journal.md | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/doc/dev_journal.md b/doc/dev_journal.md index 4318938..5a87771 100644 --- a/doc/dev_journal.md +++ b/doc/dev_journal.md @@ -128,3 +128,38 @@ Configuration is now done with json to keep it all central. Test with json configuration works. Killing a process still doesn't work: ''' TypeError: 'str' object cannot be interpreted as integer ''' +## Wednesday, 7. June 2023 +### Sebastian +This is the latest output aftert a test run where actually 3 processes has keyloggers runnig. +''' +[kldetect@fedora src]$ sudo ./keylogger_detector.py +[sudo] password for kldetect: +/usr/sbin/fuser +/usr/bin/which +[+] No suspicious processes found +[kldetect@fedora src]$ sudo ./keylogger_detector.py +/usr/sbin/fuser +/usr/bin/which +[+] No suspicious processes found +[kldetect@fedora src]$ cat config. +cat: config.: No such file or directory +[kldetect@fedora src]$ cat config.json +{"white_listed_programs": ["systemd", "gnome-shell"], "auto_kill_programs": ["skeylogger", "skeylogger", "skeylogger", "skeylogger", "skeylogger"], "kbd_names": ["kbd"]}[kldetect@fedora src]$ sudo ./keylogger_detector.py -v +[Verbose] Input options set +[Verbose] Root access checked +/usr/sbin/fuser +/usr/bin/which +[Verbose] Packages checked +[Verbose] Config file loaded +[Verbose] Config file parsed +[Verbose] Keyboard device files found: [] +[Verbose] Process IDs using keyboard device files: [] +[Verbose] Process names using keyboard device files: [] +[Verbose] Suspicious processes found: [] +[Verbose] Suspicious processes not killed: [] +[Verbose] Suspicious processes killed: [] +[+] No suspicious processes found +''' +This is after extensivly refactoring because I was starting to loose oversight over the code. So I split it up into utils, config and keylogger_detector. +#### TODO: +1. Ivestigate and bug fix From 06787801a27931e01a9dbacc85d34e3c478e3fb8 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 01:14:10 +0200 Subject: [PATCH 06/18] DEBUG --- src/keylogger_detector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index 22860e2..5676eb9 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -218,7 +218,8 @@ def detect_keyloggers(): to_kill = list(set(to_kill)) - auto_kill_programs = list(set(auto_kill_programs)).append(to_kill) + auto_kill_programs = list(set(auto_kill_programs)) + auto_kill_programs.extend(to_kill) config['auto_kill_programs'] = auto_kill_programs white_listed_programs = list(set(white_listed_programs)) config['white_listed_programs'] = white_listed_programs From befe4814cf9192a8ca7ad6cfcd2f7995e7c0eb6b Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 01:31:39 +0200 Subject: [PATCH 07/18] DEBUG --- src/keylogger_detector.py | 2 +- src/utils.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index 5676eb9..c8739a8 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -131,7 +131,7 @@ def detect_keyloggers(): pids = [] for device_file in keyboard_device_files: pids.append(get_pids_using_file(device_file)) - pids = sorted(list(set(pids))) + pids = sorted(list(set(int(pid) for pid in pids))) if verbose_option: print('[Verbose] Process IDs using keyboard device files:', pids) diff --git a/src/utils.py b/src/utils.py index b7cb085..1f953af 100755 --- a/src/utils.py +++ b/src/utils.py @@ -46,7 +46,7 @@ def get_keyboard_device_files(names): keyboard_device_files = [] for root, dirs, files in os.walk('/dev/input/by-path'): for file in files: - if any(name in files for name in names): + if any(name in file for name in names): keyboard_device_files.append(get_real_path(os.path.join(root, file))) return keyboard_device_files From b31e335dc5137dfc6c2ad92f59f093010dab9ff4 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 02:00:47 +0200 Subject: [PATCH 08/18] Final commit before snoooooooooze --- src/keylogger_detector.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index c8739a8..ee27fe3 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -130,8 +130,8 @@ def detect_keyloggers(): ############################ pids = [] for device_file in keyboard_device_files: - pids.append(get_pids_using_file(device_file)) - pids = sorted(list(set(int(pid) for pid in pids))) + pids += get_pids_using_file(device_file) + pids = sorted(list(set(pids))) if verbose_option: print('[Verbose] Process IDs using keyboard device files:', pids) From e475378dfa1d449a755d10eade3ae6b43dd0a0c1 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 16:57:07 +0200 Subject: [PATCH 09/18] Fixes multiple Issues. --- src/config.json | 12 +++++++- src/keylogger_detector.py | 59 +++++++++++++++++++++++++++++++-------- src/utils.py | 12 ++++++++ 3 files changed, 71 insertions(+), 12 deletions(-) diff --git a/src/config.json b/src/config.json index b7d81a2..6b89f8c 100644 --- a/src/config.json +++ b/src/config.json @@ -1 +1,11 @@ -{"white_listed_programs": ["systemd", "gnome-shell"], "auto_kill_programs": [], "kbd_names": ["kbd"]} +{ + "white_listed_programs": [ + "systemd", + "gnome-shell", + "systemd_logind" + ], + "auto_kill_programs": [], + "kbd_names": [ + "kbd" + ] +} \ No newline at end of file diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index ee27fe3..402e1cd 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -17,16 +17,23 @@ from utils import ( auto_kill_option = False verbose_option = False safe_option = False +add_white_list_option = False +debug_option = False # Functions +def debug(option, to_print): + if option: + print('[Debug]', to_print) def print_help(): print('Usage: python3 keylogger_detector.py [OPTIONS]') print('Options:') print(' -h, --help\t\t\tPrint this help message') - print(' -v, --verbose\t\t\tVerbose mode. Will cause additional information to be printed during execution') + print(' -v, --verbose\t\t\tVerbose mode. Informative information will be displayed duting execution') print(' -a, --auto-kill\t\tAutomatically kill blacklisted processes') print(' -s, --safe\t\t\tSafe mode. Asked to confirm before killing a process') + print(' -w, --add-white-list\t\t\tActivate prompt to add program names to the whitelist') #For some reason this line gets messed up in display + print(' -d, --debug\t\t\tDebug mode. Print debug statements') def set_input_options(): """ @@ -38,9 +45,11 @@ def set_input_options(): SystemExit: If -h or --help is passed as an argument, the help message is printed and the program exits """ - global auto_kill_option, verbose_option, safe_option + global auto_kill_option, verbose_option, safe_option, add_white_list_option + global debug_option if len(sys.argv) > 1: for arg in sys.argv[1:]: + print(arg) if arg == '-h' or arg == '--help': print_help() sys.exit(0) @@ -50,6 +59,10 @@ def set_input_options(): auto_kill_option = True elif arg == '-s' or arg == '--safe': safe_option = True + elif arg == '-w' or arg == '--add-white-list' : + add_white_list_option = True + elif arg == '-d' or arg == '--debug': + debug_option = True def confirm_kill_procces(process_name, times=0): @@ -73,7 +86,7 @@ def confirm_kill_procces(process_name, times=0): sys.exit(1) if times > 0: print('Invalid input. Please enter y or n.') - print(f'Do you want to kill {process_name}? (y/n)') + print('Do you want to kill {}? (y/n)'.format(process_name)) answer = input() if answer == 'y': return True @@ -94,6 +107,7 @@ def detect_keyloggers(): ############################ # 1. Setup and initialization ############################ + debug(True, str(sys.argv)) # Set manually to debug if args are being read global auto_kill_option, verbose_option, safe_option global CONFIG_FILE set_input_options() @@ -118,6 +132,8 @@ def detect_keyloggers(): if verbose_option: print('[Verbose] Config file parsed') + debug(debug_option, 'Whitelist option: ' + str(add_white_list_option)) + debug(debug_option, 'Vebose option: ' + str(verbose_option)) ############################ # 2. Get device files mapped to keyboard ############################ @@ -143,7 +159,7 @@ def detect_keyloggers(): for pid in pids: name = get_process_name(pid) process_names.append(name) - name_pid_dict[name].add(pid) + name_pid_dict.setdefault(name, []).append(pid) process_names = sorted(list(set(process_names))) if verbose_option: print('[Verbose] Process names using keyboard device files:', process_names) @@ -188,20 +204,22 @@ def detect_keyloggers(): print('[-]The following suspicious processes were found:') for name in suspicious_processes: print(f'\t{name}') + + if safe_option: + print('[Safe] You are in safe mode. In safe mode you will be asked to confirm each kill.') + print('[Safe] Please be aware that killing an important process may cause your system to crash.') + print('Please enter the names of the processes to kill, separated by a space.') print('To not kill any just hit enter.') - if safe_option: - print('[Info] You are in safe mode. In safe mode you will be asked to confirm each kill.') - else: - print('[Info] Please be aware that killing an important process may cause your system to crash.') to_kill = input().split() if len(to_kill) == 0: - print('[+] No processes killed.') - sys.exit(0) + print('[+] No processes to kill.') if verbose_option: print('[Verbose] Processes to kill:', to_kill) + + # If the safe_option is set, prompt the user to confirm each kill if safe_option: for name in to_kill: for pid in name_pid_dict[name]: @@ -217,6 +235,23 @@ def detect_keyloggers(): print('[Verbose] Process killed:', name) + ############################ + # 8. Update whitelist if option set + ############################ + debug(debug_option, 'Whitelist option:' + str(add_white_list_option)) + if add_white_list_option: + print('Please type the names of any process to whitelist, separated by a spcace.') + to_whitelist = input().split() + if len(to_whitelist) == 0 and verbose_option: + print('[Verbose] No processes chosen to whitelist.') + else: + white_listed_programs += to_whitelist + if verbose_option: + print('[Verbose] Newly whitelisted programs: ', to_whitelist) + + ########################### + # 9. Cleanup + ########################### to_kill = list(set(to_kill)) auto_kill_programs = list(set(auto_kill_programs)) auto_kill_programs.extend(to_kill) @@ -225,9 +260,11 @@ def detect_keyloggers(): config['white_listed_programs'] = white_listed_programs kbd_names = list(set(kbd_names)) config['kbd_names'] = kbd_names - save_config(config, CONFIG_FILE) + save_config(config) if verbose_option: print('[Verbose] Config file saved') + + print('[+] Program completed. Exiting.') if __name__ == '__main__': detect_keyloggers() diff --git a/src/utils.py b/src/utils.py index 1f953af..cabaf39 100755 --- a/src/utils.py +++ b/src/utils.py @@ -111,3 +111,15 @@ def kill_processes(pids): except ProcessLookupError: print("[-] Process {} not found.".format(pid)) +def kill_process(pid): + """ + Kill single process. + + Args: + pid (int): Process ID of process to kill + """ + try: + os.kill(int(pid), signal.SIGKILL) + except ProcessLookupError: + print("[-] Process {} not found.".format(pid)) + From 1be0bf59e0dd5538551b90802c16b84f67fdaf92 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 17:30:00 +0200 Subject: [PATCH 10/18] Some Fixes --- src/config.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/config.json b/src/config.json index 6b89f8c..12569c3 100644 --- a/src/config.json +++ b/src/config.json @@ -1,8 +1,8 @@ { "white_listed_programs": [ - "systemd", + "systemd_logind", "gnome-shell", - "systemd_logind" + "systemd" ], "auto_kill_programs": [], "kbd_names": [ From cef0f5c7dc4cf93fe57b55ce0de777034d4703c9 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:25:59 +0200 Subject: [PATCH 11/18] Add kill_process import statement to keylogger_detector.py --- src/keylogger_detector.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index 402e1cd..7ad53b6 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -9,7 +9,8 @@ from utils import ( get_real_path, get_pids_using_file, get_process_name, - kill_processes + kill_processes, + kill_process ) # Global variables From 6e9f236c08e7db022a84cddf717da66ea56db17b Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:28:23 +0200 Subject: [PATCH 12/18] DEBUG --- src/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.py b/src/utils.py index cabaf39..de88229 100755 --- a/src/utils.py +++ b/src/utils.py @@ -116,10 +116,10 @@ def kill_process(pid): Kill single process. Args: - pid (int): Process ID of process to kill + pid (): Process ID of process to kill # TODO: check if pid is int """ try: - os.kill(int(pid), signal.SIGKILL) + os.kill(pid, signal.SIGKILL) except ProcessLookupError: print("[-] Process {} not found.".format(pid)) From 5c558768780b744265635e6e246ed271eff48998 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:33:48 +0200 Subject: [PATCH 13/18] DEBUG --- src/keylogger_detector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index 7ad53b6..7862f87 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -225,6 +225,8 @@ def detect_keyloggers(): for name in to_kill: for pid in name_pid_dict[name]: if confirm_kill_procces(name): + debug(debug_option, 'Killing process: ' + name) + debug(debug_option, 'type(id): ' + str(type(id))) kill_process(id) if verbose_option: print('[Verbose] Process killed:', name) From f63c033c92b8d6f5e60bb9e83d09aa2607bda875 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:36:40 +0200 Subject: [PATCH 14/18] DEBUG --- src/keylogger_detector.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index 7862f87..dc0ac83 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -233,6 +233,8 @@ def detect_keyloggers(): else: for name in to_kill: for pid in name_pid_dict[name]: + debug(debug_option, 'Killing process: ' + name) + debug(debug_option, 'type(id): ' + str(type(id))) kill_process(id) if verbose_option: print('[Verbose] Process killed:', name) From 7af207253a0ec675c6c847e6c654c1515970ff78 Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:39:15 +0200 Subject: [PATCH 15/18] DEBUG some variables where named id instead of pid --- src/keylogger_detector.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/keylogger_detector.py b/src/keylogger_detector.py index dc0ac83..99fdc76 100755 --- a/src/keylogger_detector.py +++ b/src/keylogger_detector.py @@ -226,7 +226,7 @@ def detect_keyloggers(): for pid in name_pid_dict[name]: if confirm_kill_procces(name): debug(debug_option, 'Killing process: ' + name) - debug(debug_option, 'type(id): ' + str(type(id))) + debug(debug_option, 'type(id): ' + str(type(pid))) kill_process(id) if verbose_option: print('[Verbose] Process killed:', name) @@ -234,8 +234,8 @@ def detect_keyloggers(): for name in to_kill: for pid in name_pid_dict[name]: debug(debug_option, 'Killing process: ' + name) - debug(debug_option, 'type(id): ' + str(type(id))) - kill_process(id) + debug(debug_option, 'type(id): ' + str(type(pid))) + kill_process(pid) if verbose_option: print('[Verbose] Process killed:', name) From 05d600da719dc3e2a5c77826016001264e08af8e Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:44:12 +0200 Subject: [PATCH 16/18] DEBUG pid in kill_process() arrive as string. so convert to int --- src/utils.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/utils.py b/src/utils.py index de88229..67e12bb 100755 --- a/src/utils.py +++ b/src/utils.py @@ -116,10 +116,10 @@ def kill_process(pid): Kill single process. Args: - pid (): Process ID of process to kill # TODO: check if pid is int + pid (str): Process ID of process to kill """ try: - os.kill(pid, signal.SIGKILL) + os.kill(str(pid), signal.SIGKILL) # pid apparently arrives as string except ProcessLookupError: print("[-] Process {} not found.".format(pid)) From c58196dad8a854be6352ded10614f590ccb9e66c Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 18:45:04 +0200 Subject: [PATCH 17/18] Accidentally converted to str. now convert to int. --- src/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils.py b/src/utils.py index 67e12bb..65db448 100755 --- a/src/utils.py +++ b/src/utils.py @@ -119,7 +119,7 @@ def kill_process(pid): pid (str): Process ID of process to kill """ try: - os.kill(str(pid), signal.SIGKILL) # pid apparently arrives as string + os.kill(int(pid), signal.SIGKILL) # pid apparently arrives as string except ProcessLookupError: print("[-] Process {} not found.".format(pid)) From a6099943a4ffbb81521ff47da65d3fade3c075bf Mon Sep 17 00:00:00 2001 From: Sebastian Lenzlinger <74497638+sebaschi@users.noreply.github.com> Date: Wed, 7 Jun 2023 19:39:43 +0200 Subject: [PATCH 18/18] Update dev_journal.md --- doc/dev_journal.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/doc/dev_journal.md b/doc/dev_journal.md index 5a87771..3004614 100644 --- a/doc/dev_journal.md +++ b/doc/dev_journal.md @@ -128,7 +128,7 @@ Configuration is now done with json to keep it all central. Test with json configuration works. Killing a process still doesn't work: ''' TypeError: 'str' object cannot be interpreted as integer ''' -## Wednesday, 7. June 2023 +## Wednesday, 7. June 2023, night ### Sebastian This is the latest output aftert a test run where actually 3 processes has keyloggers runnig. ''' @@ -163,3 +163,13 @@ cat: config.: No such file or directory This is after extensivly refactoring because I was starting to loose oversight over the code. So I split it up into utils, config and keylogger_detector. #### TODO: 1. Ivestigate and bug fix +## Wednesday, 7. June 2023, day +### Sebastian +VirtualBox stopped working so after much pain I decided to switch to Boxes. There the install of Fedora 37 went smoothly. +Then Started testing the userland detector on [simple-key-logger](https://github.com/gsingh93/simple-key-logger/tree/maste), [logkeys](https://github.com/kernc/logkeys). +[pykeylogger](https://github.com/amoffat/pykeylogger) produced a segmentation fault, after I finaly got it to run. Trying to run [py-keylogger](https://github.com/hiamandeep/py-keylogger), turns out it only runs on X11 it seem (so we'd not catch it anyway). +[keylog](https://github.com/SCOTPAUL/keylog) was succesfully detected and removed. +All in all, the main functionality works as intended. Basically now would be the refinement phase to add more options or to have a way to configure the config.json file more easily. +#### TODO +1. Write report +2. Add functionality to userspace detector