La technique du "hooking" est entourée d'une aura un peu sulfureuse en raison des applications qu'il peut en être fait. Elle permet de changer le comportement d'une application en modifiant simplement et préalablement à son exécution, une librairie dynamique (DLL) dont dépend cette application. Le hook contenu dans cette DLL permettra alors d'intercepter des messages avant leur transfert à l'application. Ce faisant, il est possible d'exécuter des opérations dans un processus sans que l'application exécutée n'ait été modifiée. Suivant que le hook soit local ou global, il ne modifiera que le comportement du processus hôte ou aura un effet sur tous les processus.

L'exemple publié ici est un hook global sur des évènements clavier et souris. Ce hook intercepte l'ensemble de ces évènements et les envoie à une application.

Hook.cpp

Les définitions globales utilisées sont les suivantes:

#include <windows.h>

#define HOOK_BUILD_DLL // see Hook.h ...

#include "Hook.h"

// Structure definition of the shared memory zone
typedef struct _TData
{
  HHOOK MouseHookHandle;       // Handle to the mouse hook
  HHOOK KeybdHookHandle;       // Handle to the keyboard hook
  bool* pActiveHook;                   // pointer to the application hook toggle flag
} TData;

HANDLE SharedMemory;                                // Handle to the shared memory zone
TData * g_pData;                                           // Pointer to the shared memory zone
HINSTANCE g_HInst;                                      // Handle to the DLL intance
HWND g_hDestWindow;                                 // Handle to the window to which the hooks are hooked
static UINT g_MsgMouseWindowsHook = 0;   // Mouse hook message ID
static UINT g_MsgKeybdWindowsHook = 0;   // Keyboard hook message ID
unsigned char g_pKeyState[256];                   // used for key to ascii translation

// definition of the callback functions
LRESULT CALLBACK LLMouseProc(int nCode,WPARAM wParam,LPARAM lParam);
LRESULT CALLBACK LLKeyboardProc (int nCode,WPARAM wParam,LPARAM lParam);

DLLMain

Lors du chargement dynamique d'une DLL par un processus, la fonction DLLMain est appelée (DLL_PROCESS_ATTACH) de même lorsque le process se termine (DLL_PROCESS_DETACH).

Lors du DLL_PROCESS_ATTACH Il s'agit de préparer la zone de mémoire partagée entre les processus utilisant la DLL et d'enregistrer les messages au processus hôte. Pour le DLL_PROCESS_DETACH, la mémoire partagée est libérée.

int WINAPI DllMain(HINSTANCE hInstance, unsigned long command, void* lpReserved)
{
  g_HInst = hInstance;

  switch (command)
  {
    case DLL_PROCESS_ATTACH :

      // create the shared memory data chunk
      SharedMemory = CreateFileMapping((HANDLE)0xFFFFFFFFLL, NULL, PAGE_READWRITE, 0, sizeof(TData),  "Hook");       

      // maps a view of a the share memory into the address space of a calling process.
      g_pData = (TData *)(MapViewOfFile((HANDLE)SharedMemory, FILE_MAP_WRITE, 0, 0,  0));

      // register the hook messages
      g_MsgMouseWindowsHook      = RegisterWindowMessage(MSG_MOUSEHOOK);
      g_MsgKeybdWindowsHook      = RegisterWindowMessage(MSG_KEYBDHOOK);

      break;

    case DLL_PROCESS_DETACH :
      _ReleaseHooks();     
      // release the shared memory
      UnmapViewOfFile((LPVOID)g_pData);
      CloseHandle(SharedMemory);
      break;
    case DLL_THREAD_ATTACH:
    case DLL_THREAD_DETACH:
    default :
      break;
  }
  return 1;
}

InitializeHooks

Cette fonction est appelée par l'application hôte pour activer les hooks. Elle consiste à installer les deux procédures qui seront activée à chaque évènement intercepté. 

HOOK_EXP_TEMPLATE HOOK_DLL_API void _InitializeHooks(HWND hDest, bool* pActiveHook)
{
  // install a hook procedure that monitors low-level mouse input events
    g_pData->MouseHookHandle = SetWindowsHookEx(WH_MOUSE_LL,  (HOOKPROC)LLMouseProc, g_HInst, 0);

  // install a hook procedure that monitors low-level keyboard input events
    g_pData->KeybdHookHandle = SetWindowsHookEx(WH_KEYBOARD_LL,  (HOOKPROC)LLKeyboardProc, g_HInst, 0);

  // store parameters
  g_hDestWindow = hDest;
  g_pData->pActiveHook = pActiveHook;
}

ReleaseHooks

La fonction ReleaseHook sera appelée par l'application hôte afin désactive les hooks 

HOOK_EXP_TEMPLATE HOOK_DLL_API void _ReleaseHooks()
{
  if (g_pData->MouseHookHandle)
    UnhookWindowsHookEx(g_pData->MouseHookHandle);
  if (g_pData->KeybdHookHandle)
    UnhookWindowsHookEx(g_pData->KeybdHookHandle);

  g_pData->MouseHookHandle = g_pData->KeybdHookHandle = 0;
}

LowLevelMouseProc

Cette fonction est appelée à chaque évènement de la souris et ne restransmet qu'une partie d'entre eux en faisant bien attention de diffuser le message afin que le processus initialement ciblé par le message le reçoive bien (CallNextHookEx). Si ce n'est pas le cas, alors plus de souris et le redémarrage devient obligatoire.

LRESULT CALLBACK LLMouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
  if(nCode ==  HC_ACTION && g_pData->pActiveHook)
  {
    LPMSLLHOOKSTRUCT lpMouse = (LPMSLLHOOKSTRUCT)lParam;
    switch (wParam)
    {
      case WM_MOUSEMOVE :
      case WM_NCMOUSEMOVE :
      case WM_LBUTTONDOWN :
      case WM_LBUTTONUP :
      case WM_MBUTTONDOWN :
      case WM_MBUTTONUP :
      case WM_RBUTTONDOWN :
      case WM_RBUTTONUP :
        // send the message to the hook app
        PostMessage(g_hDestWindow, g_MsgMouseWindowsHook, wParam, MAKELPARAM(lpMouse->pt.x, lpMouse->pt.y));
      break;
      default:;
    }
  }
  return ::CallNextHookEx(g_pData->MouseHookHandle,nCode,wParam,lParam);
}

LowLevelKeyboardProc

comme pour LLMouseProc, la fonction LLKeyboardProc intercepte les message clavier, les envoie à l'application hôte et les diffuse vers les autres processus. Ici, cependant, un petit travail est fait pour récupérer le code ascii correspondant aux virtualKey et scanCode des touches claviers utilisées. Ce ne sont donc pas les données brut associées  au message initial qui sont envoyée au processus hôte mais seulement le code ascii (en plus du wParam).

LRESULT CALLBACK LLKeyboardProc (int nCode,WPARAM wParam,LPARAM lParam)
{
   
  if(nCode ==  HC_ACTION && *g_pData->pActiveHook)
  {
    WORD ascii = 0;
    LPKBDLLHOOKSTRUCT lpKeyboard = (LPKBDLLHOOKSTRUCT)lParam;

    // don't know why but GetKeyboardState() does not work correctly with shifted keys
    // if GetKeyState(0) is not called prior to GetKeyboardState ...
    GetKeyState(0);

    // get the status of the 256 virtual keys
    GetKeyboardState(g_pKeyState);

    if (lpKeyboard->flags & LLKHF_UP)
    {
      // retrieve the ascii code coresponding to the key sequence
      ToAscii(lpKeyboard->vkCode,lpKeyboard->scanCode, g_pKeyState, &ascii, 0);
      // send the message to the hook app
      PostMessage(g_hDestWindow, g_MsgKeybdWindowsHook, wParam, LOWORD(ascii));
    }
  }

  // passes the hook information to the next hook procedure
  return ::CallNextHookEx(g_pData->KeybdHookHandle,nCode,wParam,lParam);
}

Hook.h

Afin de permettre l'exportation et l'importation des fonctions depuis la DLL et vers l'application, hook.h contient le code suivant:

#ifndef HOOK_H
#define HOOK_H

#ifdef HOOK_BUILD_DLL
#  define HOOK_DLL_API __declspec(dllexport)
#  define HOOK_EXP_TEMPLATE extern "C"
#else
#  define HOOK_DLL_API __declspec(dllimport)
#  define HOOK_EXP_TEMPLATE extern "C"
#endif // BUILD_DLL

#ifdef UNICODE
  #define MSG_MOUSEHOOK         L"MSG_MOUSEHOOK"
  #define MSG_KEYBDHOOK         L"MSG_KEYBDHOOK"
#else
  #define MSG_MOUSEHOOK         "MSG_MOUSEHOOK"
  #define MSG_KEYBDHOOK         "MSG_KEYBDHOOK" 
#endif //UNICODE

HOOK_EXP_TEMPLATE HOOK_DLL_API void _InitializeHooks(HWND hDest, bool* pActiveHook);
HOOK_EXP_TEMPLATE HOOK_DLL_API void _ReleaseHooks();

#endif //#ifndef HOOK_H

HookApp.cpp

Les définitions globales de hookapp.cpp sont les suivantes:

HWND gHWnd     // handle to the application window (stored duing creation)
bool gLogEvents // = true if we want to receive the hook messages
unsigned int gMsgMouseWindowsHook, gMsgKeyboardWindowsHook; //message IDs
// Pointers to the init/release hook functions imported from the DLL
typedef void ( * TInitializeFunction )(HWND, bool*);
typedef void ( * TReleaseFunction )();
TInitializeFunction InitializeHooks;
TReleaseFunction    ReleaseHooks;

LoadHookDLL

L'utilisation du hook dans l'application nécessite au préalable de charger la DLL en mémoire et d'importer les fonctions InitializeHooks et ReleaseHooks.

int LoadHookDLL()
{
  HINSTANCE HHookDLL;

  HHookDLL = LoadLibrary("HookDLL.dll");
  if (!HHookDLL)
  {
    MessageBox(gHWnd, "Unable to load the DLL", "HookApp",MB_OK);
    return 0;
  }
  else
  {
    // Retrieve the DLL function adresses
    InitializeHooks = (TInitializeFunction)GetProcAddress(HHookDLL, "_InitializeHooks");
    ReleaseHooks = (TReleaseFunction)GetProcAddress(HHookDLL, "_ReleaseHooks");
  }
  return 1;
}

InitAppHooks

On peut alors activer le hook

int InitAppHooks()
{
  if (!InitializeHooks)
    return 0;

  // initalize the hooks
  InitializeHooks(gHWnd, &gLogEvents);

  // hook message IDs
  gMsgMouseWindowsHook = RegisterWindowMessage(MSG_MOUSEHOOK);
  gMsgKeyboardWindowsHook = RegisterWindowMessage(MSG_KEYBDHOOK);

  return 1;
}

ReleaseAppHooks

Il convient également de libérer les hooks en fin d'exécution

int ReleaseAppHooks()
{
  if (!ReleaseHooks)
    return 0;

  ReleaseHooks();

  return 1;
}

Interception des messages du hook

une façon de traiter les messages en provenance du hook consiste à placer dans le code de l'application hôte les lignes suivantes, soit dans la boucle des messages de WinMain soit dans la fonction WindowProc

  if (msg == gMsgMouseWindowsHook)
        return MouseWindowsHookEvent(wParam, lParam);
  else if (msg == gMsgKeyboardWindowsHook)
        return KeyboardWindowsHookEvent(wParam, lParam);

Où MouseWindowsHookEvent et KeyboardWindowsHookEvent sont les fonctions effectuant le traitement des messages reçus en provenance des hooks

Le mot de la fin

Ecrire un hook et surtout le déboguer peut s'avérer assez laborieux surtout quand il s'agit  de hooks globaux sur les clavier et souris. Alors, bon courage!