Compare commits
25 Commits
sebaschi-p
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
39a56a53f0 | ||
|
|
000d485da0 | ||
|
|
65733380d4 | ||
|
|
b934ffded1 | ||
|
|
efc5b26e60 | ||
|
|
e3c076b1df | ||
|
|
dfac235733 | ||
|
|
ba4bc26b2c | ||
|
|
5680143977 | ||
|
|
7d4bc93243 | ||
|
|
326f5011e0 | ||
|
|
79bad57439 | ||
|
|
33eb3c6fb4 | ||
|
|
b59c659553 | ||
|
|
ec19a08e63 | ||
|
|
689508282c | ||
|
|
6fac965976 | ||
|
|
c8fc7dc2b7 | ||
|
|
05f86101dd | ||
|
|
c30de44832 | ||
|
|
496ee97d3f | ||
|
|
1d40184e5f | ||
|
|
50c0cef1d2 | ||
|
|
e3b5b409f1 | ||
|
|
efbc9bc88f |
67
README.md
67
README.md
@@ -1,2 +1,65 @@
|
|||||||
# keylogger-detector
|
# KLDetect
|
||||||
University project for an Operating Systems lecture. The goal is to develope a keystroke-logger-detector for a Linux environment. Developement Environment: Fedora 37 VM under Gnome on VirtualBox. A project journal can be found [here](https://github.com/sebaschi/keylogger-detector/blob/main/doc/dev_journal.md)
|
KLDetect is a keylogger detector for the Linux Desktop.
|
||||||
|
It can detect processes reading from ```/dev/input/event*``` devices and kernel modules registered to listen to keyboard events.
|
||||||
|
|
||||||
|
# Dependencies
|
||||||
|
* [Python](https://www.python.org/downloads/)
|
||||||
|
* [SystemTap](https://sourceware.org/systemtap/wiki)
|
||||||
|
* [```fuser```](https://www.man7.org/linux/man-pages/man1/fuser.1.html)
|
||||||
|
* Utilities that come with [Fedora](https://fedoraproject.org/) like ```which```.
|
||||||
|
|
||||||
|
# Setup
|
||||||
|
Download or clone this repository:
|
||||||
|
```
|
||||||
|
git clone https://github.com/sebaschi/keylogger-detector.git
|
||||||
|
```
|
||||||
|
Navigate into the src directory:
|
||||||
|
```
|
||||||
|
cd keylogger-detector/src
|
||||||
|
```
|
||||||
|
Run a keylogger. KLDetect has been tested and shown to work on the following keylogger.
|
||||||
|
|
||||||
|
User progams:
|
||||||
|
* [simple-key-logger](https://github.com/gsingh93/simple-key-logger/tree/master)
|
||||||
|
* [logkeys](https://github.com/kernc/logkeys)
|
||||||
|
* [keylog](https://github.com/SCOTPAUL/keylog)
|
||||||
|
|
||||||
|
|
||||||
|
Kernel Module:
|
||||||
|
* [spy](https://github.com/jarun/spy)
|
||||||
|
|
||||||
|
# Usage
|
||||||
|
KLDetect **must** be run as root (sudo).
|
||||||
|
If KLDetect is not run as root the user is reminded to try again with root permissions.
|
||||||
|
|
||||||
|
Running without options just runs userspace detection:
|
||||||
|
```
|
||||||
|
./kldetect.py
|
||||||
|
```
|
||||||
|
To get a list of command line options:
|
||||||
|
```
|
||||||
|
./kldetect.py -h
|
||||||
|
```
|
||||||
|
To run with kernel module detection:
|
||||||
|
```
|
||||||
|
./kldetect.py -k
|
||||||
|
```
|
||||||
|
To run just kernel module detection
|
||||||
|
```
|
||||||
|
./kernel_detector.py
|
||||||
|
```
|
||||||
|
|
||||||
|
# Warning
|
||||||
|
Running any part if this program in a lightheaded manner may break your system.
|
||||||
|
Killing processes and unloading modules should be done with caution. We suggest testing it an a VM.
|
||||||
|
If one runs the KLDetect with the kernel module keylogger detection option set, make sure to update the [whitelist.txt](https://github.com/sebaschi/keylogger-detector/blob/main/src/whitelist.txt) with the safe kernel modules that you know you have on your system. In particular we highly suggest running ```lsmod > <path-to-kldetect>/whitelist.txt```, before inserting a kernel keylogger. This writes the modules currently inserted in the kernel to the whitelist. This way 'normal' modules that you already have installed on the 'clean' kernel will not accidentally be unloaded. Altough KLDetect should not unload any kernel modules currently used, better safe than sorry.
|
||||||
|
|
||||||
|
# Developers
|
||||||
|
Copyright © 2023[Michel Romancuk](https://github.com/SoulKindred), [Sebastian Lenzlinger](https://github.com/sebaschi)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
This project is Part of a Univeristy project at the [Operating Systems](https://dmi.unibas.ch/de/studium/computer-science-informatik/lehrangebot-fs23/vorlesung-operating-systems-1/) lecture at the University of Basel, Switzerland.
|
||||||
|
A project journal can be found [here](https://github.com/sebaschi/keylogger-detector/blob/main/doc/dev_journal.md).
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ This is after extensivly refactoring because I was starting to loose oversight o
|
|||||||
## Wednesday, 7. June 2023, day
|
## Wednesday, 7. June 2023, day
|
||||||
### Sebastian
|
### Sebastian
|
||||||
VirtualBox stopped working so after much pain I decided to switch to Boxes. There the install of Fedora 37 went smoothly.
|
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).
|
Then Started testing the userland detector on [simple-key-logger](https://github.com/gsingh93/simple-key-logger/tree/master), [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).
|
[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.
|
[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.
|
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.
|
||||||
|
|||||||
@@ -1,5 +1,7 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
import subprocess
|
||||||
|
import time
|
||||||
|
import os
|
||||||
import sys
|
import sys
|
||||||
from config import CONFIG_FILE, load_config, save_config
|
from config import CONFIG_FILE, load_config, save_config
|
||||||
from utils import (
|
from utils import (
|
||||||
@@ -106,6 +108,176 @@ def confirm_kill_procces(process_name, times=0):
|
|||||||
else:
|
else:
|
||||||
return confirm_kill_procces(process_name, times+1)
|
return confirm_kill_procces(process_name, times+1)
|
||||||
|
|
||||||
|
|
||||||
|
def detect_kernel(module):
|
||||||
|
"""
|
||||||
|
Start the systemtap-script.
|
||||||
|
load and unload modules twice.
|
||||||
|
load module when finished.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module(str): Path + name of the module being tested
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
String: Path + name of the module that is logging keystrokes
|
||||||
|
"""
|
||||||
|
if verbose_option:
|
||||||
|
print('[Verbose] Started kernel keylogger detection')
|
||||||
|
process = subprocess.Popen(['stap','funcall_trace.stp', '-T', '10'], stdout=subprocess.PIPE, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
for i in range(2):
|
||||||
|
subprocess.Popen(['sudo','insmod', module])
|
||||||
|
time.sleep(1)
|
||||||
|
print(".", end="")
|
||||||
|
subprocess.Popen(['sudo','rmmod', module])
|
||||||
|
time.sleep(1)
|
||||||
|
subprocess.Popen(['sudo','insmod', module])
|
||||||
|
print(".")
|
||||||
|
out = process.communicate()[0]
|
||||||
|
if verbose_option:
|
||||||
|
print('[Verbose] Started kernel keylogger detection')
|
||||||
|
|
||||||
|
print(out)
|
||||||
|
if out == "[-]":
|
||||||
|
return module
|
||||||
|
print("FAILED")
|
||||||
|
return 0
|
||||||
|
|
||||||
|
def getpath(sus_modules):
|
||||||
|
"""
|
||||||
|
Gets the path of a list of modules being tested
|
||||||
|
calls "find_file()" function
|
||||||
|
|
||||||
|
Args:
|
||||||
|
List[module(str)] List of all modules being tested
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
List[modules(str)]List of the Path of all modules being tested
|
||||||
|
"""
|
||||||
|
for i in range(len(sus_modules)):
|
||||||
|
sus_modules[i] = find_file(sus_modules[i] + ".ko")
|
||||||
|
return sus_modules
|
||||||
|
|
||||||
|
def find_file(filename):
|
||||||
|
"""
|
||||||
|
Searches for a file begining at root
|
||||||
|
|
||||||
|
Args:
|
||||||
|
filename(str) The filename one is looking for
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result_out(str) 'The Path_to_Module/Module_name'
|
||||||
|
"""
|
||||||
|
result = []
|
||||||
|
for root, dirs, files in os.walk("/"):
|
||||||
|
if filename in files:
|
||||||
|
file_path = os.path.join(root, filename)
|
||||||
|
result.append(file_path)
|
||||||
|
result_out = result
|
||||||
|
result_out = ''.join(result_out)
|
||||||
|
return result_out
|
||||||
|
|
||||||
|
def unload_mod(modules):
|
||||||
|
"""
|
||||||
|
Unloads modules.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
module(str) the module that needs to be unloaded. Has to be Path_to_Module/Module_name
|
||||||
|
"""
|
||||||
|
tmp = []
|
||||||
|
for module in modules:
|
||||||
|
result = subprocess.run(['sudo','rmmod', module],capture_output = True, text = True)
|
||||||
|
if result.returncode == 0:
|
||||||
|
if verbose_option:
|
||||||
|
print(f"[Verbose] Unloaded module: {module}")
|
||||||
|
else:
|
||||||
|
if verbose_option:
|
||||||
|
print(f"[Verbose] Failed to unloaded module: {module}")
|
||||||
|
print("[Verbose] " + result.stderr)
|
||||||
|
tmp.append(module)
|
||||||
|
result_out = compare_mods(tmp, modules)
|
||||||
|
if verbose_option:
|
||||||
|
print("[Verbose] ", end="")
|
||||||
|
print(result_out)
|
||||||
|
return result_out
|
||||||
|
|
||||||
|
|
||||||
|
def tidy_up(entries):
|
||||||
|
"""
|
||||||
|
Takes a txt file and removes everything except the first word of a line
|
||||||
|
|
||||||
|
Args:
|
||||||
|
File(.txt) in this usecase a whitelist.txt
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
clean_entries(List[str]) List of only the first wrod from each line
|
||||||
|
"""
|
||||||
|
cleaned_entries = []
|
||||||
|
for entry in entries:
|
||||||
|
modules = entry.split()
|
||||||
|
if modules:
|
||||||
|
first_mod = modules[0]
|
||||||
|
cleaned_entries.append(first_mod)
|
||||||
|
return cleaned_entries
|
||||||
|
|
||||||
|
def compare_mods(A, B):
|
||||||
|
"""
|
||||||
|
Does set-suptraction to.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
A(list[str]) List of elements one wants to ignore
|
||||||
|
B(list[str]) List of elements that one wants without all elements in A
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result(list[str] List of elements that are in B but not in A
|
||||||
|
"""
|
||||||
|
setA = set(A)
|
||||||
|
setB = set(B)
|
||||||
|
|
||||||
|
result = setB - setA
|
||||||
|
|
||||||
|
return list(result)
|
||||||
|
|
||||||
|
|
||||||
|
def get_whitelist(file_path):
|
||||||
|
"""
|
||||||
|
reads a text-file
|
||||||
|
|
||||||
|
Args:
|
||||||
|
file_path(str) Path to file one wants to read
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
lines(list[str]) List of each line from a file
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
with open(file_path, 'r') as file:
|
||||||
|
lines = file.read().splitlines()
|
||||||
|
return lines
|
||||||
|
except IOError:
|
||||||
|
print(f'Error: Failed to load whitelist{file_path}')
|
||||||
|
|
||||||
|
def list_modules(command):
|
||||||
|
"""
|
||||||
|
Calls a command in terminal
|
||||||
|
|
||||||
|
Args:
|
||||||
|
command(str) the command one wants to execute
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
result(list[std]) List of each line the command has as an output.
|
||||||
|
"""
|
||||||
|
|
||||||
|
result = subprocess.run(command, shell = True, capture_output=True, text=True)
|
||||||
|
|
||||||
|
if result.returncode == 0:
|
||||||
|
return result.stdout.strip().split('\n')
|
||||||
|
else:
|
||||||
|
print(f"Failed with error:{result.stderr}")
|
||||||
|
return[]
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
def detect_keyloggers():
|
def detect_keyloggers():
|
||||||
"""
|
"""
|
||||||
Detect (userland) keylogger processes based on which processes have a keyboard file open (/dev/input/event*)
|
Detect (userland) keylogger processes based on which processes have a keyboard file open (/dev/input/event*)
|
||||||
@@ -288,11 +460,56 @@ def detect_keyloggers():
|
|||||||
if verbose_option:
|
if verbose_option:
|
||||||
print('[Verbose] Config file saved')
|
print('[Verbose] Config file saved')
|
||||||
|
|
||||||
print('[+] Program completed. Exiting.')
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
debug(debug_option, 'Kernel detection option: ' + str(kernel_detection_option))
|
debug(debug_option, 'Kernel detection option: ' + str(kernel_detection_option))
|
||||||
|
|
||||||
|
###########################
|
||||||
|
# 10. If kernel_detection_option is set, run kernel detection
|
||||||
|
###########################
|
||||||
|
|
||||||
|
|
||||||
|
if kernel_detection_option:
|
||||||
|
whitelist = get_whitelist("whitelist.txt")
|
||||||
|
lsmod_output = list_modules("lsmod")
|
||||||
|
sus_modules = compare_mods(whitelist, lsmod_output)
|
||||||
|
sus_modules = tidy_up(sus_modules)
|
||||||
|
sus_modules = unload_mod(sus_modules)
|
||||||
|
time.sleep(1)
|
||||||
|
sus_modules = getpath(sus_modules)
|
||||||
|
suspects = []
|
||||||
|
if verbose_option:
|
||||||
|
print("[Verbose] ", end="")
|
||||||
|
print(sus_modules)
|
||||||
|
if len(sus_modules) == 0 and verbose_option:
|
||||||
|
print("[Verbose] Nothing to do")
|
||||||
|
|
||||||
|
for module in sus_modules:
|
||||||
|
if module == '': #if modules have an empty path, they are in root
|
||||||
|
break
|
||||||
|
suspects.append(detect_kernel(module))
|
||||||
|
time.sleep(1)
|
||||||
|
|
||||||
|
print("Following modules are logging your keystrokes: ")
|
||||||
|
for i in range(len(suspects)):
|
||||||
|
print( f"[{i}] {suspects[i]}")
|
||||||
|
print("Enter the number of the module you want to remove: ")
|
||||||
|
user_input = input().split()
|
||||||
|
to_remove = []
|
||||||
|
for j in user_input:
|
||||||
|
to_remove = suspects[int(j)]
|
||||||
|
subprocess.Popen(['sudo','rmmod', to_remove])
|
||||||
|
if len(to_remove) < 1:
|
||||||
|
print(f"Removed {to_remove}")
|
||||||
|
|
||||||
|
print('[+] Program completed. Exiting.')
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
detect_keyloggers()
|
detect_keyloggers()
|
||||||
Reference in New Issue
Block a user