Introduction
eBPF is a virtual machine implemented inside the kernel. It gives user-space programs the possibility to inject kernel code that runs in a safe environment (sandbox). The injected code has access to kernel structures like any regular kernel code, but it can't harm or break the system (an in-kernel verifier does a static analysis of the code, if the check doesn't pass the code is not accepted and you get an error, before running any code at all).
BCC (BPF Compiler Collection) is a front-end to eBPF, it provides a set of very useful tools on top of eBPF that allow to do amazing things (tracing, profiling, code inspection, etc.).
The problem
Let's pretend we don't know anything about the kernel and we want to write a keylogger.
The very first thing that we need to do is to figure out how the kernel receives keys from the keyboard. Knowing a little bit how computers work, we may guess that keys are received as interrupts.
So, let's try to use a tool in BCC called
funccount.py
. This tool counts how many times one (or more) function(s), passed as argument, are called in the kernel, all at runtime. It accepts wildcards (similar to file globbing), so let's start to count all functions that are called "*interrupt*"
(because I guess an IRQ handler of a keyboard interrupt should be called "something...interrupt...something else"):
# ./funccout.py '*interrupt*'
While funccount.py is running we press some random keys on the keyboard, then we stop it with CTRL+C and we get an output like the following:
FUNC COUNT
wait_for_completion_interruptible 1
arch_show_interrupts 1
ww_mutex_lock_interruptible.part.10 14
ahci_handle_port_interrupt 26
ww_mutex_lock_interruptible 31
atkbd_interrupt 70
i915_mutex_lock_interruptible 71
ath9k_btcoex_handle_interrupt 80
ath9k_hw_kill_interrupts 84
ath9k_hw_resume_interrupts 85
__ath9k_hw_enable_interrupts 109
show_interrupts 489
psmouse_interrupt 633
i8042_interrupt 774
mutex_lock_interruptible_nested 875
serio_interrupt 906
note_interrupt 930
add_interrupt_randomness 1030
hrtimer_interrupt 2459
get_next_timer_interrupt 14771
__next_timer_interrupt 55993
generic_smp_call_function_single_interrupt 63830
We've got many interrupt handlers here... but, among those, atkbd_interrupt
looks really interesting for our purpose. It's likely the interrupt handler that we were looking for. Now, inspecting the kernel source code we find that the prototype of this function is the following:
static irqreturn_t atkbd_interrupt(struct serio *serio, unsigned char data, unsigned int flags)
We got it! It looks like data
is what we need to intercept all keys that are pressed on the keyboard.
To do so we use another tool provided by BCC:
trace.py
. This tool allows to trace any function in the kernel and inspect its argument:
# ./trace.py 'atkbd_interrupt(struct serio *serio, unsigned char data, unsigned int flags) "data=0x%x" data'
...
PID TID COMM FUNC -
0 0 swapper/3 atkbd_interrupt data=0x1e
0 0 swapper/3 atkbd_interrupt data=0x9e
0 0 swapper/3 atkbd_interrupt data=0x31
0 0 swapper/3 atkbd_interrupt data=0xb1
0 0 swapper/3 atkbd_interrupt data=0x20
0 0 swapper/3 atkbd_interrupt data=0xa0
0 0 swapper/3 atkbd_interrupt data=0x13
0 0 swapper/3 atkbd_interrupt data=0x93
0 0 swapper/3 atkbd_interrupt data=0x12
0 0 swapper/3 atkbd_interrupt data=0x92
0 0 swapper/3 atkbd_interrupt data=0x1e
0 0 swapper/3 atkbd_interrupt data=0x9e
...
And here's our keylogger, just two commands, knowing a very little about kernel and programming in general.
NOTE: if you test this simple example on your box you may notice that any time you press a key on the keyboard you get 2 interrupts: 1 when the key is pressed and another when the key is released. The one when the key is released returns a value with the most significant bit set: this can be used to distinguish between a "key pressed" event and a "key released" event.
At this point the only thing that you need is to log these data somewhere and decode each number (scancode) into the corresponding key... and you have your keylogger.
Happy hacking!
Intersting!
ReplyDeleteWhat I like about people is that they have already done everything you can think of. Having read your article, I thought it would be great if not only on Linux there was such a thing, but also on Windows. And here. I come across Refog. A fairly good keylogger, you can look at the link: https://www.refog.com/
ReplyDelete