- Linux operating system
- Rust toolchain installed
- Root/sudo access (required to read
/dev/inputdevices)
In linux events are stored in files available at /dev/input/eventN. Keyboard events are recorded in one of these file. To make a keylogger we'll need to know which file is responsible for keyboard events.
In order to know which file is responsible for keyboard events we will first get the contents of /proc/bus/input/devices and look for the event file mapped to the keyboard.
This information is not directly readable - we need to parse the contents to identify the correct eventN file. The file contains input device data separated by \n\n in a format like below:
I: Bus=0011 Vendor=0001 Product=0001 Version=ab83
N: Name="AT Translated Set 2 keyboard"
P: Phys=isa0060/serio0/input0
S: Sysfs=/devices/platform/i8042/serio0/input/input3
U: Uniq=
H: Handlers=sysrq kbd leds event3
B: PROP=0
B: EV=120013
B: KEY=6000000000000020 0 0 110500f02100007 ff803078f900d401 feffffdfffcfffff fffffffffffffffe
B: MSC=10
B: LED=7
So we'll need to separate each input block and find out which belongs to the keyboard. We'll be able to know this after parsing and understanding the key mapping:
B: KEY=6000000000000020 0 0 110500f02100007 ff803078f900d401 feffffdfffcfffff fffffffffffffffe
This data contains the keys that are mapped to this input device stored as hexes. So we'll convert each hex chunk to binary and see which input block will have the most keys mapped to it which will be our keyboard.
- We will split on the whitespaces to get each hex chunk.
let chunks: Vec<&str> = hex.split_whitespace().collect();- Convert each hex chunk to u64 integer.
let num = u64::from_str_radix(chunk,16);- Count the number of activated keycodes by counting the number of one bits.
num.count_ones();Now with this we'll get the file responsible for keyboard events.
When a keyboard event is recorded its written into the eventN file. Each event is 24 bytes. The 24 bytes are arranged as:
- Bytes 0-15 (Timestamp): struct timeval — 8 bytes for seconds, 8 bytes for microseconds
- Bytes 16-17 (Type): Event type (u16) — value of 1 indicates EV_KEY (keyboard event)
- Bytes 18-19 (Code): Key code (u16) — identifies which key
- Bytes 20-23 (Value): Event value (i32) — 1 for key press, 0 for key release, 2 for key repeat
We create a function that opens the eventN file and continuously reads 24-byte chunks in a loop. We filter for events where:
- Type == 1 (EV_KEY, keyboard events)
- Value == 1 (key press only, ignoring key release)
To improve performance and avoid blocking the main thread, the keylogger uses a multi-threaded approach. A dedicated thread is spawned to read from the event file. This thread sends the keycodes to the main thread through a channel (mpsc).
Linux keycodes are standardized numbers (e.g., 30 = 'a', 57 = space, 28 = Enter). We use a match statement for a fast and efficient keycode-to-character mapping. This is more performant than a HashMap for this use case.
We then save the key presses to a file keylog.txt.
For maximum performance, we:
- Use a dedicated thread for reading keyboard events.
- Use
BufWriterwith a large buffer (64KB) to minimize disk I/O - Accumulate keypresses in memory
- Flush to
keylog.txtperiodically rather than on every keystroke
- Build the project:
cargo build --release- Run with sudo (required for /dev/input access):
sudo ./target/release/keylogger- View captured keystrokes:
cat keylog.txt