Клавиатурные шпионы. Варианты реализации кейлоггеров в ОС Windows. Часть II
Данная статья является продолжением предыдущей обзорной статьи , посвященной кейлоггерам, и представляет детальный анализ технических аспектов реализации кейлоггеров. Как было сказано в первой статье, принципиальная идея кейлоггера состоит в том, чтобы внедриться между любыми двумя звеньями в цепи прохождения сигнала от нажатия клавиш на клавиатуре до появления символов на экране. Мы проанализируем, какие звенья содержатся в данной цепи, а также рассмотрим варианты построения программных и аппаратных кейлоггеров.
Статья рассчитана на технических специалистов и квалифицированных пользователей. Остальным читателям достаточно запомнить, что в ОС Windows существует множество способов перехватить вводимые с клавиатуры данные, хотя большинство реально существующих кейлоггеров использует лишь два их них (см. раздел «Принципы построения кейлоггеров» первой части статьи).
Сразу оговоримся: в данной статье не будет приведено никаких примеров исходных вариантов реализации кейлоггеров. Мы не разделяем мнение некоторых экспертов по безопасности, которые считают возможным публиковать подобные коды.
Обработка клавиатурного ввода в OC Windows
Существует несколько базовых технологий перехвата нажатий клавиш и событий мыши, на основе которых создано множество кейлоггеров. Однако перед тем как рассматривать конкретные типы кейлоггеров, необходимо познакомиться со схемой обработки клавиатурного ввода, реализованной в ОС Windows. Для описания всей схемы — от нажатия клавиши на клавиатуре до срабатывания системного прерывания контроллера клавиатуры и получения активным окном сообщения WM_KEYDOWN — мы воспользуемся тремя источниками:
- Книгой «Аппаратное обеспечение IBM PC». Александр Фролов, Григорий Фролов, Том 2, книга 1, М.: Диалог-МИФИ, 1992. Глава 2 «Клавиатура» описывает принципы работы клавиатуры, используемые порты и аппаратное прерывание клавиатуры;
- Разделом Human Input Devices библиотеки MSDN Library, в котором описана низкоуровневая (драйверная) часть схемы обработки клавиатурного ввода;
- Книгой Джеффри Рихтера «Создание эффективных WIN32-приложений с учетом специфики 64-разрядной версии Windows». В главе 27 «Модель аппаратного ввода и локальное состояние ввода» приведено описание высокоуровневой части схемы обработки клавиатурного ввода (реализованной в пользовательском режиме).
Принципы работы клавиатуры как физического устройства
В настоящее время большинство клавиатур выполнено в виде отдельного устройства, подключаемого к компьютеру с помощью одного из разъемов, чаще всего PS/2 или USB. Существуют два микроконтроллера, обеспечивающие процесс обработки клавиатурного ввода: один — на материнской плате ПК, второй — в самой клавиатуре. Таким образом, клавиатура персонального компьютера сама по себе является компьютерной системой. Она построена на основе микроконтроллера 8042, который постоянно сканирует нажатия клавиш на клавиатуре — независимо от активности на центральном процессоре x86.
За каждой клавишей клавиатуры закреплен определенный номер, однозначно связанный с распайкой клавиатурной матрицы и не зависящий напрямую от обозначений, нанесенных на поверхность клавиш. Этот номер называется скан-кодом (название подчеркивает тот факт, что компьютер сканирует клавиатуру для поиска нажатой клавиши). Скан-код — это случайное значение, выбранное IBM еще тогда, когда она создавала первую клавиатуру для ПК. Скан-код не соответствует ASCII-коду клавиши, одной и той же клавише могут соответствовать несколько значений ASCII-кода. Таблицу скан-кодов можно посмотреть, например, в двадцатой главе книги The Art of Assembly Language Programming.
На самом деле клавиатура генерирует два скан-кода для каждой клавиши — когда пользователь нажимает клавишу и когда отпускает. Наличие двух скан-кодов важно, так как некоторые клавиши имеют смысл только тогда, когда они нажаты (Shift, Control, Alt).
Рисунок 1. Упрощенная схема клавиатуры
Когда пользователь нажимает клавишу на клавиатуре, он замыкает электрический контакт. В результате при следующем сканировании микроконтроллер фиксирует нажатие определенной клавиши и посылает в центральный компьютер скан-код нажатой клавиши и запрос на прерывание. Аналогичные действия выполняются и тогда, когда оператор отпускает нажатую ранее клавишу.
Второй микроконтроллер получает скан-код, производит преобразование скан-кода, делает его доступным на порту ввода-вывода 60h и затем генерирует аппаратное прерывание центрального процессора. После этого процедура обработки прерывания может получить скан-код из указанного порта ввода-вывода.
Следует отметить, что клавиатура содержит внутренний 16-байтовый буфер, через который она осуществляет обмен данными с компьютером.
Низкоуровневое взаимодействие с клавиатурой через порты ввода-вывода
Взаимодействие с системным контроллером клавиатуры происходит через порт ввода-вывода 64h. Считав байт из этого порта, можно определить статус контроллера клавиатуры, записав байт — послать контроллеру команду.
Взаимодействие с микроконтроллером в самой клавиатуре происходит с помощью портов ввода-вывода 60h и 64h. Биты 0 и 1 в байте статуса (порт 64h в режиме чтения) предоставляют возможность управлять процедурой взаимодействия: перед записью данных в эти порты бит 0 порта 64h должен быть выставлен в 0. Когда данные доступны для чтения из порта 60h, бит 1 порта 64h равен 1. Биты включения/выключения клавиатуры в командном байте (порт 64h в режиме записи) определяют, является ли клавиатура активной, и будет ли контроллер клавиатуры вызывать прерывание в системе, когда пользователь нажмет клавишу.
Байты, записанные в порт 60h, посылаются контроллеру клавиатуры, а байты, записанные в порт 64h, посылаются системному контроллеру клавиатуры. Списки разрешенных команд, которые можно послать контроллеру клавиатуры, представлены, например, в документе «8042 Keyboard Controller IBM Technical Reference Manual» или в двадцатой главе книги The Art of Assembly Language Programming.
Байты, считываемые из порта 60h, приходят от клавиатуры. Порт 60h при чтении содержит скан-код последней нажатой клавиши, а в режиме записи он используется для расширенного управления клавиатурой. При использовании порта 60h на запись программа дополнительно получает следующие возможности:
- установка времени ожидания перед переходом клавиатуры в режим автоповтора;
- установка периода генерации скан-кода в режиме автоповтора;
- управление светодиодами, расположенными на лицевой панели клавиатуры — Scroll Lock, Num Lock, Caps Lock.
Резюмируя, отметим, что для чтения данных, вводимых с клавиатуры, достаточно уметь считывать значения портов ввода-вывода 60h и 64h. Однако в ОС Windows приложениям пользовательского режима запрещено работать с портами, поэтому эту задачу выполняют драйвера операционной системы.
Архитектура «интерактивных устройств ввода»
Кто же обрабатывает аппаратное прерывание, которое генерируется при появлении в порту 60h данных, полученных от клавиатуры? Очевидно, тот, кто зарегистрировал процедуру обработки аппаратного прерывания клавиатуры IRQ 1. В ОС Windows эту обработку производит драйвер i8042prt.sys. В отличие от времен MS DOS, когда каждый системный компонент был как бы «сам по себе», в Windows все компоненты построены согласно четкой архитектуре и работают по строго определенным правилам, закрепленным программными интерфейсами. Поэтому перед тем как приступить к рассмотрению драйвера i8042prt, мы рассмотрим архитектуру интерактивных устройств ввода, в рамках которой функционируют все программные компоненты, связанные с обработкой клавиатурного (и «мышиного») ввода.
Устройства, используемые для прямого управления операциями на компьютере, в ОС Windows называются интерактивными устройствами ввода. Клавиатура является одним из таких устройств, наряду с манипулятором «мышь», джойстиками, трекболами, игровыми педалями, колесами, шлемами виртуальной реальности и др.
Архитектура управления интерактивными устройствами ввода базируется на стандарте USB Human Interface Device (HID), предложенном организацией USB Implementers Forum. Однако архитектура не ограничена только USB-устройствами и поддерживает другие типы устройств ввода, в частности, bluetooth-клавиатуру, клавиатуру и мышь, подключаемые по PS/2 порту, а также устройства, подключаемые по игровому порту (I/O port 201).
Далее мы рассмотрим принципы построения стека драйверов и стека устройств для PS/2-клавиатур, так как исторически они являются первый типом устройств. USB-клавиатуры используют ряд элементов, введенных при разработке программной поддержки PS/2-клавиатур.
Стек драйверов для системных устройств ввода
Драйвера клавиатуры, независимо от схем физического подключения, используют системные драйвера класса клавиатуры для обработки не зависимых от аппаратной части операций. Данные драйвера называются драйверами класса, так как обеспечивают требуемые системой, но не зависимые от аппаратной реализации требования к конкретному классу устройств.
Соответствующий функциональный драйвер (драйвер порта) реализует зависимую от конкретного устройства поддержку выполнения операций ввода-вывода. В ОС Windows для x86-платформ реализован единый драйвер системной клавиатуры (i8042) и мыши.
Стек драйверов для Plug and Play PS/2-клавиатуры
Рисунок 3. Стек драйверов для PS/2-клавиатуры
Стек драйверов содержит (сверху вниз):
- Kbdclass — верхнеуровневый фильтр-драйвер класса клавиатуры;
- опциональный верхнеуровневый фильтр-драйвер класса клавиатуры;
- i8042prt — функциональный драйвер клавиатуры;
- корневой драйвер шины.
В ОС Windows 2000 и старше драйвером класса клавиатуры является драйвер Kbdclass, основными задачами которого являются:
- обеспечение общих и аппаратно-независимых операций класса устройств;
- поддержка Plug and Play, поддержка управления питанием и Windows Management Instrumentation (WMI);
- поддержка операций для legacy-устройств;
- одновременное выполнение операций более чем одного устройства;
- реализация class service callback routine, которая вызывается функциональным драйвером для передачи данных из входного буфера устройства в буфер данных драйвера класса устройств.
В ОС Windows 2000 и старше функциональным драйвером для устройств ввода, использующих PS/2-порт (клавиатуры и мыши), является драйвер i8042prt, основные функции которого следующие:
- обеспечение аппаратно-зависимых одновременных операций PS/2-устройств ввода (клавиатуры и мыши разделяют общие порты ввода вывода, но используют разные прерывания, процедуры обработки прерываний (ISR) и процедуры завершения обработки прерываний);
- поддержка Plug and Play, поддержка управления питанием и Windows Management Instrumentation (WMI);
- поддержка операций для legacy-устройств;
- вызов class service callback routine для классов клавиатуры и мыши для передачи данных из входного буфера данных i8042prt в буфер данных драйвера класса;
- вызов набора функций обратного вызова, которые могут реализовать драйвера-фильтры высокого уровня для гибкого управления устройством.
Рисунок 4. Список аппаратных ресурсов, используемых драйвером i8042prt
На рисунке показан список аппаратных ресурсов, используемых драйвером i8042prt. Их можно просмотреть, например, с помощью утилиты DeviceTree, разработанной компанией Open Systems Resources. Для тех, кто прочел разделы «Принципы работы клавиатуры как физического устройства» и «Низкоуровневое взаимодействие с клавиатурой через порты ввода-вывода», значения портов ввода-вывода (IO) 60h и 64h и аппаратного прерывания (IRQ) 1 не будут сюрпризом.
В показанном драйверном стеке новый драйвер-фильтр может быть добавлен, например, для специфичной обработки клавиатурного ввода поверх драйвера класса клавиатуры. Данный драйвер должен поддерживать ту же обработку всех типов запросов ввода-вывода и управляющих команд (IOCTL), что и драйвер класса клавиатуры. В этом случае перед передачей в подсистемы пользовательского режима данные будут отданы на обработку в этот драйвер-фильтр.
Рассмотрим теперь стек устройств, которые создают указанные выше драйвера в драйверном стеке клавиатуры. Ниже на рисунке слева изображен стек устройств, приведенный в MSDN Library для PS/2-клавиатуры, справа — скриншот программы DeviceTree, показывающий реальную ситуацию на машине автора статьи.
Рисунок 5. Конфигурация объектов устройств для Plug and Play PS/2-клавиатуры
В целом стек устройств (правильнее говорить о стеке объектов устройств) PS/2-клавиатуры состоит из:
- физического объекта устройства клавиатуры (PDO), созданного драйвером шины (в данном случае, шины PCI) — Device 0000066;
- функционального объекта устройства клавиатуры (FDO), созданного и присоединенного к PDO драйвером i8042prt — неименованный объект (unnamed);
- опциональных фильтр-объектов устройства клавиатуры, создающихся фильтр-драйверами клавиатуры, разрабатываемыми сторонними разработчиками;
- верхнеуровневого фильтр-объекта устройства класса клавиатуры, созданного драйвером класса Kbdclass — DeviceKeyboardClass0.
Поток необработанного ввода (получение данных от драйвера)
В предыдущем разделе мы наблюдали примеры построения клавиатурных стеков в режиме ядра операционной системы. Теперь мы рассмотрим, как происходит передача информации о нажатии клавиш приложениям пользовательского режима.
Подсистема Microsoft Win32 получает доступ к клавиатуре, используя поток необработанного ввода (Raw Input Thread, RIT), который является частью системного процесса csrss.exe. Операционная система при старте создает RIT и системную очередь аппаратного ввода (system hardware input queue, SHIQ).
RIT открывает объект «устройство» драйвера класса клавиатуры для эксклюзивного использования и с помощью функции ZwReadFile направляет ему запрос ввода-вывода (IRP) типа IRP_MJ_READ. Получив запрос, драйвер Kbdclass отмечает его как ожидающий завершения (pending), ставит в очередь и возвращает код возврата STATUS_PENDING. Потоку необработанного ввода приходится ждать завершения IRP, для чего используется вызов асинхронной процедуры (Asynchronous Procedure Call, APC).
Когда пользователь нажимает или отпускает одну из клавиш, системный контроллер клавиатуры вырабатывает аппаратное прерывание. Его обработчик вызывает специальную процедуру обработки прерывания IRQ 1 (interrupt service routine, ISR), зарегистрированную в системе драйвером i8042prt. Данная процедура считывает из внутренней очереди контроллера клавиатуры появившиеся данные. Обработка аппаратного прерывания должна быть максимально быстрой, поэтому ISR ставит в очередь вызов отложенной процедуры (Deferred Procedure Call, DPC) I8042KeyboardIsrDpc и завершает свою работу. Как только это станет возможно (IRQL понизится до DISPATCH_LEVEL), DPC будет вызвана системой. В этот момент будет вызвана процедура обратного вызова KeyboardClassServiceCallback, зарегистрированная драйвером Kbdclass у драйвера i8042prt. KeyboardClassServiceCallback извлечет из своей очереди ожидающий завершения запрос IRP, заполнит максимальное количество структур KEYBOARD_INPUT_DATA, несущих всю необходимую информацию о нажатиях/отпусканиях клавиш, и завершит IRP. Поток необработанного ввода пробуждается, обрабатывает полученную информацию и вновь посылает IRP типа IRP_MJ_READ драйверу класса, который опять ставится в очередь до следующего нажатия/отпускания клавиши. Таким образом, у стека клавиатуры всегда есть по крайней мере один ожидающий завершения IRP, и находится он в очереди драйвера Kbdclass.
Рисунок 6. Последовательность запросов от RIT к драйверу клавиатуры
С помощью утилиты IrpTracker, разработанной упоминавшейся ранее компанией Open Systems Resources, можно отследить последовательность вызовов, происходящих при обработке клавиатурного ввода.
Рисунок 7. Обработка клавиатурного ввода в пользовательском режиме
Как же RIT обрабатывает поступившую информацию? Все пришедшие клавиатурные события помещаются в системную очередь аппаратного ввода, после чего они последовательно преобразуются в сообщения Windows (типа WM_KEY*, WM_?BUTTON* или WM_MOUSEMOVE) и ставятся в конец очереди виртуального ввода (virtualized input queue, VIQ) активного потока. В сообщениях Windows скан-коды клавиш заменяются на коды виртуальных клавиш, соответствующие не расположению клавиши на клавиатуре, а действию, которое выполняет эта клавиша. Механизм преобразования кодов зависит от активной раскладки клавиатуры, одновременных нажатий других клавиш (например, SHIFT) и других факторов.
Когда пользователь входит в систему, процесс Windows Explorer порождает поток, который создает панель задач и рабочий стол (WinSta0_RIT). Этот поток привязывается к RIT. Если пользователь запускает MS Word, то его поток, создавший окно, немедленно подключится к RIT. После этого поток, принадлежащий Explorer, отключается от RIT, так как единовременно с RIT может быть связан только один поток. При нажатии клавиши в SHIQ появится соответствующий элемент, что приведет к тому, что RIT пробудится, преобразует событие аппаратного ввода в сообщение от клавиатуры и поместит его в VIQ потока приложения MS Word.
Обработка сообщений конкретным окном
Как же поток обрабатывает сообщения от клавиатуры, попавшие в его очередь сообщений?
Стандартный цикл обработки сообщений обычно выглядит следующим образом:
С помощью функции GetMessage события от клавиатуры извлекаются из очереди и перенаправляются с помощью функции DispatchMessage оконной процедуре, которая производит обработку сообщений для окна, где в данный момент сосредоточен фокус ввода. Фокус ввода — атрибут, который может присваиваться окну, созданному приложением или Windows. Если окно имеет фокус ввода, соответствующая функция этого окна получает все клавиатурные сообщения из системной очереди. Приложение может передавать фокус ввода от одного окна другому, например, при переключении на другое приложение с помощью комбинации Alt+Tab.
Перед функцией DispatchMessage обычно вызывается функция TranslateMessage, которая на основе сообщений WM_KEYDOWN, WM_KEYUP, WM_SYSKEYDOWN, WM_SYSKEYUP создает «символьные» сообщения WM_CHAR, WM_SYSCHAR, WM_DEADCHAR и WM_SYSDEADCHAR. Образованные символьные сообщения помещаются в очередь сообщений приложения, причем оригинальные клавиатурные сообщения из этой очереди не удаляются.
Массивы состояния клавиш клавиатуры
Одной из задач при разработке модели аппаратного ввода Windows было обеспечение ее отказоустойчивости. Отказоустойчивость обеспечивается независимой обработкой ввода потоками, что предотвращает неблагоприятное воздействие одного потока на другой. Но этого недостаточно для надежной изоляции потоков друг от друга, поэтому система поддерживает дополнительную концепцию — локальное состояние ввода. Каждый поток обладает собственным состоянием ввода, сведения о котором хранятся в структуре THREADINFO. В информацию об этом состоянии включаются данные об очереди виртуального ввода потока, а также группа переменных. Последние содержат управляющую информацию о состоянии ввода. Относительно клавиатуры поддерживаются следующие сведения: какое окно находится в фокусе клавиатуры, какое окно активно в данный момент, какие клавиши нажаты, каково состояние курсора ввода.
Информация о том, какие клавиши нажаты, сохраняется в массиве синхронного состояния клавиш. Этот массив включается в переменные локального состояния ввода каждого потока. В то же время массив асинхронного состояния клавиш, в котором содержится аналогичная информация, — только один, и он разделяется всеми потоками. Массивы отражают состояние всех клавиш на данный момент, и функция GetAsyncKeyState позволяет определить, нажата ли сейчас заданная клавиша. GetAsyncKeyState всегда возвращает 0 (не нажата), если ее вызывает другой поток, а не тот, который создал окно, находящееся сейчас в фокусе ввода.
Функция GetKeyState отличается от GetAsyncKeyState тем, что возвращает состояние клавиатуры на тот момент, когда из очереди потока извлечено последнее сообщение от клавиатуры. Эту функцию можно вызвать в любой момент; для нее неважно, какое именно окно в фокусе.
Клавиатурные ловушки
В операционной системе Microsoft Windows ловушкой, или хуком (hook) называется механизм перехвата событий с использованием особой функции (таких как передача сообщений Windows, ввод с мыши или клавиатуры) до того, как они дойдут до приложения. Эта функция может затем реагировать на события и, в некоторых случаях, изменять или отменять их.
Функции, получающие уведомления о событиях, называются фильтрующими функциями и различаются по типам перехватываемых ими событий. Для того чтобы Windows смогла вызывать функцию-фильтр, эта функция должна быть прикреплена к хуку (например, к клавиатурному хуку). Прикрепление одной или нескольких фильтрующих функций к какому-нибудь хуку называется установкой хука. Для установки и удаления фильтрующих функций приложения используют функции Win32 API SetWindowsHookEx и UnhookWindowsHookEx. Некоторые хуки можно устанавливать как для всей системы, так и для одного конкретного потока.
Если к одному хуку прикреплено несколько фильтрующих функций, Windows реализует очередь функций, причем функция, прикрепленная последней, оказывается в начале очереди, а самая первая функция — в ее конце. Очередь функций-фильтров (см. рисунок 8) поддерживается самой Windows, что позволяет упростить написание фильтрующих функций и улучшить производительность операционной системы.
Система поддерживает отдельные цепочки для каждого типа хуков. Цепочка хуков — это список указателей на фильтрующие функции (специальные функции обратного вызова, определяемые приложением). Когда происходит некоторое событие, связанное с конкретным типом хука, система последовательно передает сообщение каждой фильтрующей функции в цепочке хуков. Действие, которое может выполнить фильтрующая функция, зависит от типа ловушки: некоторые функции могут только отслеживать возникновение событий, другие могут модифицировать параметры сообщений или даже остановить обработку сообщений, заблокировав вызов следующей фильтрующей функции в цепочке хуков или функции обработки сообщений окна-назначения.
Рисунок 8. Цепочка функций-фильтров в Windows
Когда к хуку прикреплена одна или более функций-фильтров и происходит событие, приводящее к срабатыванию хука, ОС Windows вызывает первую функцию из очереди функций-фильтров, и на этом ее ответственность заканчивается. Далее функция ответственна за то, чтобы вызвать следующую функцию в цепочке, для чего используется функция Win32 API CallNextHookEx.
ОС поддерживает несколько типов хуков, каждый из которых предоставляет доступ к одному из аспектов механизма обработки сообщений Windows.
Для автора кейлоггера могут представлять интерес хуки почти всех типов: WH_KEYBOARD, WH_KEYBOARD_LL (перехват клавиатурных событий при добавлении их в очередь событий потока), WH_JOURNALRECORD и WH_JOURNALPLAYBACK (запись и воспроизведение клавиатурных и «мышиных» событий), WH_CBT (перехват множества событий, включая удаление клавиатурных событий из системной очереди аппаратного ввода), WH_GETMESSAGE (перехват событий, получаемых из очереди событий потока).
Общая схема обработки
Обобщим все полученные выше знания о процедуре клавиатурного ввода в едином алгоритме. Итак, алгоритм прохождения сигнала от нажатия пользователем клавиш на клавиатуре до появления символов на экране можно представить следующим образом:
- Операционная система при старте создает в системной процессе csrss.exe поток необработанного ввода и системную очередь аппаратного ввода.
- Поток необработанного ввода в цикле посылает запросы чтения драйверу класса клавиатуры, которые остаются в состоянии ожидания до появления событий от клавиатуры.
- Когда пользователь нажимает или отпускает клавишу на клавиатуре, микроконтроллер клавиатуры фиксирует нажатие/отпускание клавиши и посылает в центральный компьютер скан-код нажатой клавиши и запрос на прерывание.
- Системный контроллер клавиатуры получает скан-код, производит преобразование скан-кода, делает его доступным на порту ввода-вывода 60h и генерирует аппаратное прерывание центрального процессора.
- Контроллер прерываний вызывает процедуру обработки прерывания IRQ 1, — ISR, зарегистрированную в системе функциональным драйвером клавиатуры i8042prt.
- Процедура ISR считывает из внутренней очереди контроллера клавиатуры появившиеся данные, переводит скан-коды в коды виртуальных клавиш (независимые значения, определенные системой) и ставит в очередь вызов отложенной процедуры I8042KeyboardIsrDpc.
- Как только это становится возможным, система вызывает DPC, которая в свою очередь вызывает процедуру обратного вызова KeyboardClassServiceCallback, зарегистрированную драйвером класса клавиатуры Kbdclass.
- Процедура KeyboardClassServiceCallback извлекает из своей очереди ожидающий завершения запрос от потока необработанного ввода и возвращает в нем информацию о нажатой клавише.
- Поток необработанного ввода сохраняет полученную информацию в системной очереди аппаратного ввода и формирует на ее основе базовые клавиатурные сообщения Windows WM_KEYDOWN, WM_KEYUP, которые ставятся в конец очереди виртуального ввода VIQ активного потока.
- Цикл обработки сообщений потока удаляет сообщение из очереди и передает его соответствующей оконной процедуре для обработки. При этом может быть вызвана системная функция TranslateMessage, которая на основе базовых клавиатурных сообщений создает дополнительные «символьные» сообщения WM_CHAR, WM_SYSCHAR, WM_DEADCHAR и WM_SYSDEADCHAR.
Модель прямого ввода (Raw Input)
Описанная выше модель клавиатурного ввода имеет ряд недостатков с точки зрения разработчиков приложений. Так, для получения ввода от нестандартного устройства приложение должно выполнить значительное число операций: открыть устройство, периодически его опрашивать, заботиться о возможности параллельного использования устройства другим приложением и т.д. Поэтому в последних версиях ОС Windows была предложена альтернативная модель ввода, названная моделью прямого ввода, упрощающая разработку приложений, использующих нестандартные устройства ввода (см. рисунок 3, приложения DirectInput).
Модель прямого ввода отличается от оригинальной модели ввода Windows. В обычной модели приложение получает устройство-независимый ввод в форме сообщений (таких как WM_CHAR), которые посылаются окнам приложения. В модели прямого ввода приложение должно зарегистрировать устройства, от которых оно хочет получать ввод. Далее приложение получает пользовательский ввод через сообщение WM_INPUT. Поддерживается два способа передачи данных — стандартный и буферизированный; для интерпретации введенных данных приложению нужно получить информацию о природе устройства ввода, что можно сделать с помощью функции GetRawInputDeviceInfo.
Варианты реализации кейлоггеров
Далее мы рассмотрим основные методы реализации кейлоггеров, основываясь на описанной выше модели обработки клавиатурного ввода в ОС Windows. Зная, как устроена эта модель, проще понять, какие механизмы могут быть использованы кейлоггерами.
Кейлоггеры могут внедряться в любом месте последовательности обработки, перехватывая данные о нажатых клавишах, передаваемые одной подсистемой обработки следующей подсистеме в цепочке обработчиков. Рассматриваемые ниже методы реализации программных кейлоггеров мы раздели на методы пользовательского режима и методы режима ядра. На рисунке 9 отображены все подсистемы обработки клавиатурного ввода и их взаимосвязи. Рядом с некоторыми подсистемами расположены цифры в красных кружочках — они соответствуют разделу статьи, в которой описывается метод реализации кейлоггеров, связанный с заменой или использованием выделенной подсистемы.
Рис. 9. Общая схема обработки клавиатурного ввода в ОС Windows
В данном разделе мы не будем рассматривать аппаратные методы реализации кейлоггеров. Заметим лишь, что существуют три основных разновидности реализации аппаратных кейлоггеров: кейлоггер, встраиваемый в саму клавиатуру; кейлоггер, встраиваемый в разрыв кабеля, соединяющего клавиатуру и системный блок; кейлоггер, встраиваемый в системный блок компьютера. Самым распространенным является второй тип аппаратных кейлоггеров, один из самых известных примеров которых — KeyGhost USB Keylogger.
К сожалению, в настоящее время одним из немногих антивирусных решений, предоставляющих необходимый уровень защиты от потенциальной опасности, которую несут кейлоггеры, являются антивирусные продукты линейки 6.0 «Лаборатории Касперского (подробнее см. статью «Защита от кейлоггеров с помощью Антивируса Касперского версии 6.0 и Kaspersky Internet Security 6.0»).
1. Кейлоггеры пользовательского режима
Кейлоггеры пользовательского режима — наиболее простые как для реализации, так и для обнаружения, поскольку для перехвата они используют вызовы известных и хорошо документированных функций программного интерфейса Win32 API.
Пожалуй, это самый распространенный метод реализации кейлоггеров. Посредством вызова функции SetWindowsHookEx кейлоггер устанавливает глобальную ловушку на клавиатурные события для всех потоков в системе (см. раздел «Клавиатурные ловушки»). Фильтрующая функция ловушки в этом случае располагается в отдельной динамической библиотеке, которая внедряется во все процессы системы, занимающиеся обработкой сообщений. При выборке из очереди сообщений любого потока клавиатурного сообщения система вызовет установленную фильтрующую функцию.
К достоинствам этого метода перехвата относится простота и гарантированный перехват всех нажатий клавиатуры, из недостатков можно отметить необходимость наличия отдельного файла динамической библиотеки и относительную простоту обнаружения по причине внедрения во все системные процессы.
Примером кейлоггеров, использующих данный метод реализации, являются AdvancedKeyLogger, KeyGhost, Absolute Keylogger, Actual Keylogger, Actual Spy, Family Key Logger, GHOST SPY, Haxdoor, MyDoom и другие.
Kaspersky Internet Security проактивно детектирует такого рода кейлоггеры как invader (loader) (должна быть включена опция «Внедрение оконных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
Является вторым по распространенности методом реализации кейлоггеров. Состояние всех клавиш с небольшим интервалом опрашивается с помощью функций GetAsynсKeyState или GetKeyState. Данные функции возвращают массивы асинхронного или синхронного состояния клавиш (см. раздел «Массив синхронного состояния клавиш»); анализируя их, можно понять, какие клавиши были нажаты или отпущены после последнего опроса.
Достоинства данного метода — предельная простота реализации, отсутствие дополнительного модуля (как и в случае с использованием ловушки), недостатки — отсутствие гарантии обнаружения всех нажатий, могут быть пропуски; легко обнаруживается мониторингом процессов, опрашивающих клавиатуру с высокой частотой.
Данный метод реализации используется в таких кейлоггерах, как Computer Monitor, Keyboard Guardian, PC Activity Monitor Pro, Power Spy, Powered Keylogger, Spytector, Stealth KeyLogger, Total Spy.
Kaspersky Internet Security проактивно детектирует подобные кейлоггеры как Keylogger (должна быть включена опция «Обнаружение клавиатурных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
Редко реализуемый метод реализации кейлоггера. Кейлоггер внедряется во все процессы и перехватывает в них функции GetMessage или PeekMessage (см. раздел «Обработка сообщений конкретным окном») из библиотеки user32.dll. Для этого могут быть использованы различные методы: сплайсинг (сплайсинг — метод, используемый для перехвата вызова API-функций; суть метода состоит в замене нескольких (обычно 5) первых байт функции инструкцией JMP, передающей управление коду-перехватчику), модификация таблицы адресов импортируемых функций IAT, перехват функции GetProcAddress, возвращающей адрес функции из загруженной динамической библиотеки. Кейлоггер может реализовываться в виде DLL или при помощи непосредственного внедрения кода в целевой процесс.
В результате получается следующее: когда приложение вызывает, к примеру, функцию GetMessage для получения следующего сообщения из очереди сообщений, этот вызов приходит в код перехватчика. Перехватчик вызывает исходную функцию GetMessage из user32.dll и анализирует возвращаемые результаты на предмет типа сообщений. При получении клавиатурного сообщения вся информации о нем извлекается из параметров сообщения и протоколируется кейлоггером.
К достоинствам следует отнести эффективность: ввиду малой распространенности метода лишь небольшое число программ позволяет находить подобные кейлоггеры; кроме того, против подобных кейлоггеров бесполезны стандартные экранные клавиатуры, так как посылаемые ими сообщения также перехватываются.
Недостатки: модификация таблицы IAT не гарантирует перехвата, т.к. адреса функций библиотеки user32.dll могут быть сохранены до того, как кейлоггер внедрился; сплайсинг имеет свои сложности, связанные, например, с необходимостью переписывать «на лету» тело функции.
Kaspersky Internet Security проактивно детектирует такие кейлоггеры как invader (должна быть включена опция «Внедрение в процесс (invaders)» подсистемы «Анализ активности приложений» модуля проактивной защиты).
В настоящий момент известен лишь один пример реализации данного метода — утилита тестирования защищенности системы от кейлоггеров. Суть метода заключается в использовании новой модели прямого ввода (см. раздел «Модель прямого ввода (Raw Input)»), для чего кейлоггер регистрирует клавиатуру в качестве устройства, от которого он хочет получать ввод. После этого кейлоггер начинает получать данные о нажатых и отпущенных клавишах через сообщение WM_INPUT.
Достоинства — простота реализации и эффективность: ввиду того, что данный метод стал широко известен лишь недавно, почти ни одна из защитных программ не обладает пока возможностями борьбы с подобными кейлоггерами.
Недостатки — простота обнаружения по причине необходимости специальной регистрации на получение приложением прямого ввода (по умолчанию ни один из процессов в системе его не получает).
Kaspersky Internet Security 7.0 будет проактивно детектировать такие кейлоггеры как Keylogger (должна быть включена опция «Обнаружение клавиатурных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
2. Кейлоггеры режима ядра
Кейлоггеры режима ядра значительно более сложные, чем кейлоггеры пользовательского режима — как для реализации, так и в плане обнаружения. Они требуют специальных знаний для написания, но позволяют создавать кейлоггеры, абсолютно незаметные для всех приложений пользовательского режима. Для перехвата могут использоваться как документированные в Microsoft Driver Development Kit, так и недокументированные методы.
Документированный в DDK способ перехвата (см. раздел «Стек драйверов для Plug and Play PS/2-клавиатуры»). Кейлоггеры, построенные на основе этого метода, перехватывают запросы к клавиатуре посредством установки фильтра поверх устройства «DeviceKeyboardClass0», созданного драйвером Kbdclass (см. раздел «Стек устройств для Plug and Play PS/2-клавиатуры»). Фильтруются только запросы типа IRP_MJ_READ, поскольку именно они позволяют получить коды нажатых и отпущенных клавиш.
Достоинства — гарантированный перехват всех нажатий, невозможность обнаружения без использования драйвера, благодаря чему не все антикейлоггеры их определяют. Недостатки — необходимость установки собственного драйвера.
Самыми известными кейлоггерами, использующими данный метод реализации, являются ELITE Keylogger и Invisible KeyLogger Stealth.
Kaspersky Internet Security проактивно детектирует такие кейлоггеры как Keylogger путем мониторинга клавиатурного стека устройств (должна быть включена опция «Обнаружение клавиатурных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
Документированный в DDK способ перехвата. Кейлоггеры, построенные на основе этого метода, перехватывают запросы к клавиатуре посредством установки фильтра поверх безымянного устройства, создаваемого драйвером i8042prt под устройством DeviceKeyboardClass0 (см. раздел «Стек устройств для Plug and Play PS/2-клавиатуры»). Драйвер i8042prt предоставляет собой программный интерфейс для добавления дополнительной функции обработки прерывания IRQ1 (IsrRoutine), в которой можно произвести анализ полученных от клавиатуры данных.
Достоинства и недостатки аналогичны описанным для метода, в предыдущем пункте. Но есть и дополнительный недостаток — зависимость от типа клавиатуры: драйвер i8042prt обрабатывает запросы только от PS/2 клавиатуры, поэтому рассматриваемый метод не пригоден для перехвата данных, вводимых через USB или беспроводную клавиатуру.
Kaspersky Internet Security проактивно детектирует такие кейлоггеры как Keylogger путем мониторинга клавиатурного стека устройств (должна быть включена опция «Обнаружение клавиатурных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
Кейлоггеры, построенные на основе этого метода, перехватывают запросы к клавиатуре, подменяя точку входа IRP_MJ_READ в таблице обработки системных запросов (dispatch table) драйвера Kbdclass. Функционально похож на драйвер-фильтр драйвера Kbdclass (см. раздел «2.1. Драйвера-фильтры драйвера класса kbdclass»). Особенности те же. В другом варианте используется перехват иной функции обработчика запросов — IRP_MJ_DEVICECONTROL. В этом случае кейлоггер становится подобным драйверу-фильтру драйвера i8042 (см. раздел «2.2. Драйвера-фильтры функционального драйвера i8042prt»).
Kaspersky Internet Security проактивно детектирует такие кейлоггеры как Keylogger (должна быть включена опция «Обнаружение клавиатурных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
Достаточно распространенный метод реализации кейлоггеров, функционально аналогичный методу реализации пользовательского режима, описанному в разделе «1.3. Внедрение в процесс и перехват функций GetMessage/PeekMessage». Кейлоггеры, построенные на основе этого метода, перехватывают запросы к клавиатуре посредством патчинга точки входа NtUserGetMessage/PeekMessage во второй таблице системных сервисов (KeServiceDescriptorTableShadow) драйвера win32k.sys. Информация о нажатии клавиши приходит в перехватчик, когда в каком-либо потоке завершается вызов функций GetMessage или PeekMessage.
Достоинством данного метода является сложность его обнаружения, недостатком — относительная сложность реализации (поиск самой таблицы KeServiceDescriptorTableShadow — непростая задача; помимо этого, другие драйвера могут ранее пропатчить точки входа в этой таблице) и необходимость установки отдельного драйвера.
Kaspersky Internet Security проактивно детектирует такие кейлоггеры как Keylogger (должна быть включена опция «Обнаружение клавиатурных перехватчиков» подсистемы «Анализ активности приложений» модуля проактивной защиты).
Крайне редко встречаемый тип кейлоггера. Кейлоггеры, построенные на основе этого метода, перехватывают запросы к клавиатуре, модифицируя код функций NtUserGetMessage или NtUserPeekMessage методом сплайсинга. Данные функции реализованы в ядре системы в драйвере win32k.sys и вызываются из соответствующих функций библиотеки user32.dll. Как указано в разделе «1.3. Внедрение в процесс и перехват функций GetMessage/PeekMessage», эти функции позволяют фильтровать все получаемые приложениями сообщения и получать данные о нажатии и отпускании клавиш из клавиатурных сообщений.
Достоинством данного метода является сложность его обнаружения, недостатком — трудности реализации (необходимость переписывать тело функции «на лету», зависимость от версии операционной системы и установленного программного обеспечения), а также необходимость установки драйвера.
Данный метод основан на подмене драйвера Kbdclass или одного из низкоуровневых драйверов клавиатуры драйвером собственной разработки. Явным недостатком данного метода является сложность реализации, так как заранее не известен тип применяемой пользователем клавиатуры, а потому подмену драйвера сравнительно просто обнаружить. Вследствие этого подобный метод практически не реализуется на практике.
Метод заключается в написании своего драйвера режима ядра, перехватывающего прерывание от клавиатуры (IRQ 1) и напрямую обращающегося к ее портам ввода-вывода (60h, 64h). В связи со сложностью реализации и не совсем ясным способом взаимодействия с системным обработчиком прерывания IRQ 1 (драйвером i8042prt.sys) в настоящее время метод является сугубо теоретическим.
- Рассмотренная схема клавиатурного ввода в ОС Windows достаточно сложна, и практически на любом ее этапе можно установить перехватчик. На некоторых шагах алгоритма перехватчики уже реализованы, на некоторых еще нет.
- Существует зависимость распространенности кейлоггера от сложности его реализации. Так, наиболее распространенные методы перехвата — установка ловушки на события ввода и циклический опрос состояния клавиатуры — являются в то же время самыми простыми в реализации. Написать кейлоггер, использующий данные методы, может даже человек, научившийся программировать неделю назад.
- Основная масса существующих в настоящее время кейлоггеров представляет собой достаточно простой в реализации инструмент, который можно легко использовать во вредоносных целях, в первую очередь для кражи вводимой с клавиатуры конфиденциальной информации.
- Антивирусным компаниям необходимо обеспечить пользователей защитой от угрозы, которую представляют используемые злоумышленниками кейлоггеры.
По мере совершенствования средств защиты авторам кейлоггеров придется обращаться к реализации более сложных клавиатурных шпионов, использующих драйвера ядра операционной системы Windows, — на этом поле деятельности у них остается еще много возможностей.