Wednesday, December 12, 2018

Linux: easy keylogger with eBPF

In this article I'd like to show you how we can use eBPF as a tool to learn the kernel.

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!

No comments: