Данный опус является переводом статьи из
летнего, 59-ого выпуска журнала Phrack. Я вполне
допускаю, что в нее могли закрасться некие
неточности, поэтому в случае возникновения
вопросов обращайтесь к оригиналу: http://www.phrack.org/show.php?p=59&a=14.
Ну, понеслась.
Как работает драйвер клавиатуры
Согласись, для того что бы написать
грамотный keylogger нужно знать, каким образом
нажатие клавиши на клавиатуре
регистрируется в системе. Смотри схему:
handle_scancode -> put_queue -> tty_queue -> receive_buf ->
буфер tty_ldisc -> tty_read -> /dev/ttyX -> sys_read ->
процесс пользователя
Сначала, когда вы нажимаете клавишу на
клавиатуре, устройство посылает
соответствующий скан код клавиши
клавиатурному драйверу. Единичное нажатие
может произвести последовательность до
шести кодов. Функция handle_scancode() в
клавиатурном драйвере обрабатывает поток
кодов и конвертирует их (при помощи функции
перевода kbd_translate()) в серию событий типа "клавиша
нажата", "клавиша отпущена".
Каждый баттон имеет свой уникальный код от 1
до 127. Нажатие клавиши генерирует этот код, а
отпускание - этот код + 128 (например когда вы
нажимаете 'a' то на гора выдается код 30, а
когда отпускаете - 158).
Дальше код клавиши в соответствии с
картой клавиш конвертится обратно в сам
символ (вообще говоря при этом учитываются
нажатые функциональные клавиши - Shift , AltGr,
Control, Alt, ShiftL, ShiftR, CtrlL и CtrlR, комбинации
активных "модификаторов" - CapsLock
например) и передается по цепочке буферов и
очередей к терминалу.
Драйвер клавиатуры может работать в
четырех режимах:
- scancode (RAW MODE): приложенице получает
сканкод из потока и дальше обрабатывает
его собственным драйвером (пример тому - Х11). - keycode (MEDIUMRAW MODE): программа получает код
клавиши, переведенный из скан кода
драйвером в соответствии с собственной
таблицей. - ASCII (XLATE MODE): код клавиши преобразуется в
ASCII-код символа или некоторую
последовательность ASCII-кодов символов в
соответствии с таблицей раскладки
клавиатуры, которая хранится в памяти/диске
виде отдельного файла (можно поменять
раскладку клавиатуры командой kbdconfig, она
прописывает новое значение в файл /etc/sysconfig/keyboard
и загружает указанную таблицу в
оперативную память). - Unicode (UNICODE MODE): этот режим отличается от
предыдущего другим форматом вывода
символов - UTF8, а не ASCII.
Эти режимы в конце концов и определяют,
что приложение получит в результате
нажатия клавиши. (Кстати говоря: режим
работы драйвера клавиатуры можно узнать
или изменить с помощью kbd_mode(). Но
помни! Смена режима может вообще оставить
тебя без клавиатуры!)
Kernel based keylogger
В общем говоря, мы можем перехватить поток
кодов двумя способами: написать обработчик
прерывания клавиатуры или вмешаться в
передачу на одном из описанных выше этапов.
Обработчик прерывания
Гляди как реализуется первый вариант. В
архитектуре Intel для контроллера клавиатуры
выделено первое прерывание. Когда оно
возникает наш обработчик должен прочитать
сканкод и статус клавиатуры. События
клавиатуры можно получить на 0х60 порту (Keyboard
data register), а ее состояние - на 0х64 (Keyboard status
register).
/* below code is intel specific */
#define KEYBOARD_IRQ 1
#define KBD_STATUS_REG 0x64
#define KBD_CNTL_REG 0x64
#define KBD_DATA_REG 0x60
#define kbd_read_input() inb(KBD_DATA_REG)
#define kbd_read_status() inb(KBD_STATUS_REG)
#define kbd_write_output(val) outb(val, KBD_DATA_REG)
#define kbd_write_command(val) outb(val, KBD_CNTL_REG)
/* register our own IRQ handler */
request_irq(KEYBOARD_IRQ, my_keyboard_irq_handler, 0, "my keyboard",
NULL);
В my_keyboard_irq_handler():
scancode = kbd_read_input();
key_status = kbd_read_status();
log_scancode(scancode);
Этот обработчик канает только для
архитектуры Intel, так что в случае переноса
на другую платформу он работать
естественно не будет. Хотя, честно говоря,
где ты у нас видел другие платформы...
Замена функций
Это начальная функция драйвера
клавиатуры (keyboard.c), она обрабатывает скан
коды, получаемые от клавиатуры:
# /usr/src/linux/drives/char/keyboard.c
void handle_scancode(unsigned char scancode, int down);
Мы можем заменить настоящий handle_scancode()
собственной процедурой, которая помимо
всего прочего будет записывать коды клавиш.
(Продолжение следует)