Клавиатурная снифалка на C++

Николай "GorluM" Андреев (gorlum@real.xakep.ru)

За английским словом keylogger (key - клавиша, log – вносить в журнал) скрывается не что иное, как клавиатурный шпион. Так можно назвать, например, программу hookdump. Она логирует все нажатия клавиатуры, фиксирует имена открытых окон. В общем, приносит людям немало пользы :). А теперь представь, что такую же утилиту ты напишешь сам. Конечно, она будет немного попроще, но висеть в памяти и записывать все нажатия клавиш в файл она сможет.

Итак, загружай свою visual studio, она нам понадобится для создания двух модулей (проектов) программы. Первый модуль - сам ехе’шник, его ты и будешь запускать. Это Win32 Project, тип - Windows Application. Второй, самый интересный для нас - динамически подсоединяемая библиотека или, проще говоря - DLL. Она нам потребуется для того, чтобы внедриться в систему и перехватывать все нажатия клавиш для последующей их обработки, к примеру, записи в файл. Этот проект будет такой же, как и exe, только с другим типом - DLL.

Ловушки

Для перехвата любых системных и пользовательских событий в Windows существует очень интересный механизм. Механизм хуков (hook) или, попросту говоря, ловушек. Работает он следующим образом. Некоторый процесс запускает функцию установки ловушки SetWindowsHookEx и указывает в ее параметрах:

а) тип устанавливаемой ловушки (см. таблицу);

б) адрес функции, которая будет обрабатывать срабатывания ловушки (такую процедуру мы напишем чуть ниже);

с) дескриптор модуля, в котором содержится эта функция (в данном случае дескриптор DLL).

Затем система берет модуль, содержащий функцию обработки ловушки, и подгружает его ко всем доступным процессам в зависимости от прав доступа. Установив некоторые флаги у процесса, система заставляет его запускать функцию обработки всякий раз при срабатывании хука. Наша с тобой задача - написать функцию обработки, которая запихнет ее в отдельный модуль (подгружается системой), и программу, устанавливающую саму ловушку.

Основные типы ловушек

WH_KEYBOARD - рассматриваемая сегодня ловушка. Обрабатывает любые события клавиатуры.

WH_MOUSE - перехватывает все события мыши. С ее помощью можно получить текущее положение курсора (вести лог движения мыши), состояние кнопок или дескриптор окна, на котором находится курсор.

WH_GETMESSAGE - наверное, самый универсальный хук. Он позволяет перехватить и обработать любое оконное сообщение. Его можно использовать в качестве кей/маус-логгера, но не рекомендуется из-за слишком большого количества запусков процедуры хука.

WH_CBT - очень полезная системная ловушка. Позволяет обрабатывать события, срабатывающие при активации, создании, уничтожении или смене размера окна; при смене фокуса ввода, при удалении сообщений из очереди.

Пишем хук

Начнем мы с самого сложного - с написания модуля, содержащего функцию обработки. А обрабатывать нам надо событие, возникающее при нажатии на клавишу. Событие ловушки - WH_KEYBOARD. Если зайти на сайт msdn.microsoft.com или заглянуть на диск MSDN, можно найти описание этой процедуры:

LRESULT CALLBACK KeyboardProc(

int code, // hook code

WPARAM wParam, // virtual-key code

LPARAM lParam // keystroke-message information

);

То есть наша функция должна принимать три параметра, описанных так же, как в документации мелкомягких. Плюс надо сделать ее экспортируемой - она будет запихиваться в отдельный модуль. В итоге получилось вот что:

__declspec(dllexport) LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam);

При срабатывании события в эту функцию передаются некоторые параметры. В code будет содержаться либо значение HC_ACTION, либо HC_NOREMOVE, но нас интересует только первое. Мы ведь обрабатываем именно нажатия клавиш. Кстати, определить, нажата ли клавиша, можно при помощи вот такого выражения:

(!(HIWORD(lParam) & KF_DOWN))

Оно будет истинно, если клавиша нажата, и ложно, если клавиша отпущена. Так как нам не надо писать каждую клавишу по два раза (для нажатия и отпускания), то при запуске функции поставим условие, чтобы запись производилась только при нажатии:

(code == HC_ACTION)&&(!(HIWORD(lParam) & KF_UP))

Если параметры удовлетворяют условию, то можно приступить к обработке кода нажатой клавиши, который будет содержаться в параметре wParam в виде значения типа char. Как ты понимаешь, можно сразу записать это значение в лог и без обработки, но тогда мы никогда не узнаем, была ли это русская буква или латинская, и вместо нажатых ctrl и alt мы увидим просто квадратики. Нет, так дело не пойдет. Мы грамотно обработаем полученную клавишу и запишем в лог и название клавиши и, если был включен русский язык, букву в кириллице.

А сделаем это следующим образом. Когда условия параметров нас удовлетворяют, создадим блок switch, в котором проверим значение wParam на сходство с системными клавишами. Если клавиша не системная, значит это буква.

switch ((char)wParam){

case VK_ESCAPE:

strcpy(buffer,"<ESC>");

break;

case VK_RETURN:

strcpy(buffer,"<RETURN>\r\n");

break;

}

buffer - строковая переменная. В нее записываются данные, которые впоследствии будут занесены в лог. По аналогии с примером описываются все возможные значения wParam для ctrl, alt, tab и т.п. Все они определены в файле winuser.h.

Далее, если значение wParam не подпадает ни под одно из описанных выше, значит, это буква, и данное условие требует отдельного рассмотрения. Так как мы не знаем, какой был включен языковой режим, соответственно, и не сможем определить, что же записывать в файл. Для определения текущей раскладки и записи необходимого символа относительно этой раскладки мы воспользуемся двумя функциями. Первая - GetKeyboardState получает массив из 256 элементов со всеми статусами виртуальных клавиш. Вторая, ToAscii, занимается тем, что относительно текущей раскладки, полученной при помощи первой функции, вычисляет, какой символ должен соответствовать нажатой клавише, и записывает этот символ в первый элемент нашей переменной. Вот так выглядит код:

default:

BYTE keyarray[256] ;

if(GetKeyboardState(keyarray))

if(!ToAscii(wParam,(HIWORD(lParam)&(0x0000FFFF)), keyarray,(WORD*)&buffer[0],NULL))

buffer[0]='\0';

break;

Теперь осталось только открыть файл на запись, поставить с помощью функции SetFilePointer курсор в конец и добавить наш буфер. Причем открывать его надо с параметром FILE_SHARE_WRITE, иначе две программы не смогут одновременно работать с одним файлом. Системе это не понравится.

Завершив обработку полученного события, надо отправить его (событие) дальше по цепочке хуков. Делается это с помощью функции CallNextHookEx, в которой указываются все полученные в начале параметры.

 

Пишем установщик хука

После написания модуля обработки добавим к проекту DLL файл exports.def, в котором будет содержаться имя экспортируемой функции в следующем формате:

LIBRARY spy

EXPORTS

KeyboardProc

Компилируем это. Теперь все ОК. Осталось написать программу, устанавливающую нашу ловушку. Для этого берем обычный шаблон приложения (см. приложение) и начинаем в нем писать. Первое, что должна сделать программа - подгрузить к себе только что созданную DLL-ловушку. Делается это с помощью функции LoadLibrary. Единственное, что ей необходимо передать - имя подгружаемой библиотеки. Затем программа должна найти адрес в памяти процедуры нашей ловушки. Для этого мы передаем WinAPI функции GetProcAddress адрес подгруженной DLL (значение, которое вернула LoadLibrary) и название обработчика, в данном случае KeyboardProc. Теперь осталось только установить ловушку и заснуть на некоторое время. Ловушка будет работать до тех пор, пока не вызовется функция UnhookWindowsHookEx, или не уничтожится процесс, установивший ловушку. Ведь когда процесс вырубается, все его библиотеки выгружаются, в том числе и DLL с нашей ловушкой.

ХХХ ЗАГОЛОВОК ХХХ

Break

К сожалению, возможности рубрики "Кодинг" не позволяют мне описать эту технику более подробно, но вышеизложенного материала хватит на то, чтобы начать писать свой уникальный логгер с множеством полезных функций. Например, можно добавить в логгер фичу по записи времени, имени процесса или окна, в котором была введена буква. Можешь попробовать написать ловушку на мышь. В общем, здесь есть, что придумать. Если будет интересно узнать про реализацию той или иной функции в кейлоггере - пиши. Как обычно, проект для Visual Studio .NET, полный исходный текст программы и уже скомпилированный exe’шник ты можешь взять на нашем диске или скачать с сайта www.xakep.ru. На этом все.

Удачного компилирования.

 

Листинг exe-модуля

#include <windows.h>

#pragma comment(linker,"/MERGE:.rdata=.text")

#pragma comment(linker,"/SECTION:.text,EWRX")

#pragma comment(linker,"/ENTRY:WinMain")

int __stdcall WinMain(HINSTANCE ha, HINSTANCE, PTSTR, int)

{

// гружу DLL

HMODULE dllhook = LoadLibrary("spy.dll");

// Ищу функцию в DLL

HOOKPROC hook = (HOOKPROC)GetProcAddress(dllhook, "KeyboardProc");

// Устанавливаю ловушку

HHOOK hhook = SetWindowsHookEx(WH_KEYBOARD,hook,dllhook,0);

// время работы ловушки

// INFINITE - если хотите, чтобы ловушка работала "долго"

Sleep(INFINITE);

// выгружаю DLL

FreeLibrary(dllhook);

return 0;

}



Опубликовал admin
21 Янв, Среда 2004г.



Программирование для чайников.