Le hook du clavier et de la souris

NICOLAS HUMANN

N'hésitez pas à commenter cet article ! Commentez ♪

- Titre : Le hook du clavier et de la souris

- Auteur : NICOLAS HUMANN

- Parution : 18 décembre 2013

- Licence : Les sources présentées sur cette page sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005 Nicolas Humann . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et jusqu'à 300 000 E de dommages et intérêts. Cette page est déposée à la SACD.

Sommaire

- I. Introduction

- I-A. Définition

- I-B. Exemple d'utilisation

- II. Classe du Hook

- II-A. P/Invoke

- II-A-1. Déclaration des fonctions Win32

- II-A-1-a. SetWindowsHookEx

- II-A-1-b. UnhookWindowsHookEx

- II-A-1-c. CallNextHookEx

- II-A-1-d. ToAscii

- II-A-1-e. GetKeyboardState

- II-A-1-f. GetKeyState

- II-A-1-g. HookProc

- II-A-2. Déclaration des structures

- II-A-2-a. POINT

- II-A-2-b. MouseHookStruct

- II-A-2-c. MouseLLHookStruct

- II-A-2-d. KeyboardHookStruct

- II-A-3. Constantes Windows

- II-B. Hook de la Souris et du clavier

- II-B-1. Méthode StartMouseHook

- II-B-2. Méthode StartKeyHook

- III. Conclusion

I - Introduction

Dans ce tutoriel, nous allons voir comment réaliser un hook sur le clavier et la souris en .net. Certain d'entre vous penserons « enfin », car de très nombreux exemples sont en C/C++.

Le langage utilisé lors du tutoriel est le C#.

I-A - Définition

Un programme qui fait un hook clavier / souris, intercepte toutes les touches du clavier sur lesquelles vous tapez et les boutons de la souris, quelle que soit l'application.

I-B - Exemple d'utilisation

L'utilisation d'un hook peut être assez variable. Le plus souvent, ce sera pour faire du Key Logging, c'est-à-dire, d'enregistrer dans un fichier la frappe du clavier, et ainsi, récupérer des mots de passe.

Sinon, intercepter un clic pour notre utilisation.

II - Classe du Hook

II-A - P/Invoke

Pour réaliser un hook, nous devons appeler des fonctions des apis Win32.

Ces fonctions Windows, étant réalisées en C/C++, nous devons faire, ce que l'on appelle du P/Invoke (Plateform Invoke). Pour plus d'informations, je vous renvoie sur l'article de morpheus : https://morpheus.developpez.com/dlldotnet/

II-A-1 - Déclaration des fonctions Win32

Nous allons utiliser des fonctions de la dll user32.dll.

II-A-1-a - SetWindowsHookEx

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/setwindowshookex.asp

Déclaration :

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int SetWindowsHookEx(
int idHook,
HookProc lpfn,
IntPtr hMod,
int dwThreadId);

II-A-1-b - UnhookWindowsHookEx

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/unhookwindowshookex.asp

Déclaration :

[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int UnhookWindowsHookEx(int idHook);

II-A-1-c - CallNextHookEx

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/callnexthookex.asp

Déclaration :

[DllImport("user32.dll", CharSet = CharSet.Auto,
CallingConvention = CallingConvention.StdCall, SetLastError = true)]
private static extern int CallNextHookEx(
int idHook,
int nCode,
int wParam,
IntPtr lParam);

II-A-1-d - ToAscii

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/toascii.asp

Déclaration :

[DllImport("user32")]
private static extern int ToAscii(
int uVirtKey,
int uScanCode,
byte[] lpbKeyState,
byte[] lpwTransKey,
int fuState);

II-A-1-e - GetKeyboardState

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/getkeyboardstate.asp

Déclaration :

[DllImport("user32")]
private static extern int GetKeyboardState(byte[] pbKeyState);

II-A-1-f - GetKeyState

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/keyboardinput/keyboardinputreference/keyboardinputfunctions/getkeystate.asp

Déclaration :

[DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private static extern short GetKeyState(int vKey);

II-A-1-g - HookProc

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookfunctions/callwndproc.asp

Déclaration :

private delegate int HookProc(int nCode, int wParam, IntPtr lParam);

II-A-2 - Déclaration des structures

Ces fonctions travaillent avec des structures de données. Voici leurs déclarations :

II-A-2-a - POINT

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/rectangl_0tiq.asp

Déclaration :

[StructLayout(LayoutKind.Sequential)]
private class POINT
{
/// <summary>
/// Coordonnée X.
/// </summary>
public int x;
/// <summary>
/// Coordonnée Y.
/// </summary>
public int y;
}

II-A-2-b - MouseHookStruct

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookstructures/cwpstruct.asp

Déclaration :

[StructLayout(LayoutKind.Sequential)]
private class MouseHookStruct
{
/// <summary>
/// Structure POINT pour les coordonnées
/// </summary>
public POINT pt;
/// <summary>
/// Handle de la window
/// </summary>
public int hwnd;
/// <summary>
/// Specifies the hit-test value. For a list of hit-test values, see the description of the WM_NCHITTEST message.
/// </summary>
public int wHitTestCode;
/// <summary>
/// Specifies extra information associated with the message.
/// </summary>
public int dwExtraInfo;
}

II-A-2-c - MouseLLHookStruct

Déclaration :

[StructLayout(LayoutKind.Sequential)]
private class MouseLLHookStruct
{
/// <summary>
/// Structure POINT.
/// </summary>
public POINT pt;
public int mouseData;
public int flags;
public int time;
public int dwExtraInfo;
}

II-A-2-d - KeyboardHookStruct

MSDN:http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/windowing/hooks/hookreference/hookstructures/cwpstruct.asp

Déclaration :

[StructLayout(LayoutKind.Sequential)]
private class KeyboardHookStruct
{
/// <summary>
/// Key code virtuel, la valeur doit être entre 1 et 254.
/// </summary>
public int vkCode;
public int scanCode;
public int flags;
public int time;
public int dwExtraInfo;
}

II-A-3 - Constantes Windows

Voici la liste des constantes qui nous seront utiles.

//Valeurs issues de Winuser.h du SDK de Microsoft.
/// <summary>
/// Windows NT/2000/XP: Installe un hook pour la souris
/// </summary>
private const int WH_MOUSE_LL       = 14;
/// <summary>
/// Windows NT/2000/XP: Installe un hook pour le clavier
/// </summary>
private const int WH_KEYBOARD_LL    = 13;

private const int WH_MOUSE          = 7;

private const int WH_KEYBOARD       = 2;

/// <summary>
/// Le message WM_MOUSEMOVE est envoyé quand la souris bouge
/// </summary>
private const int WM_MOUSEMOVE      = 0x200;
/// <summary>
/// Le message WM_LBUTTONDOWN est envoyé lorsque le bouton gauche est pressé
/// </summary>
private const int WM_LBUTTONDOWN    = 0x201;
/// <summary>
/// Le message WM_RBUTTONDOWN est envoyé lorsque le bouton droit est pressé
/// </summary>
private const int WM_RBUTTONDOWN    = 0x204;
/// <summary>
/// Le message WM_MBUTTONDOWN est envoyé lorsque le bouton central est pressé
/// </summary>
private const int WM_MBUTTONDOWN    = 0x207;
/// <summary>
/// Le message WM_LBUTTONUP est envoyé lorsque le bouton gauche est relevé
/// </summary>
private const int WM_LBUTTONUP      = 0x202;
/// <summary>
/// Le message WM_RBUTTONUP est envoyé lorsque le bouton droit est relevé
/// </summary>
private const int WM_RBUTTONUP      = 0x205;

private const int WM_MBUTTONUP      = 0x208;

private const int WM_LBUTTONDBLCLK  = 0x203;

private const int WM_RBUTTONDBLCLK  = 0x206;

private const int WM_MBUTTONDBLCLK  = 0x209;

private const int WM_MOUSEWHEEL     = 0x020A;

private const int WM_KEYDOWN = 0x100;

private const int WM_KEYUP = 0x101;

private const int WM_SYSKEYDOWN = 0x104;

private const int WM_SYSKEYUP = 0x105;

private const byte VK_SHIFT     = 0x10;
private const byte VK_CAPITAL   = 0x14;
private const byte VK_NUMLOCK   = 0x90;

II-B - Hook de la Souris et du clavier

II-B-1 - Méthode StartMouseHook

Dans cette méthode, nous allons initialiser le hook de la souris. Pour cela, nous utilisons la fonction Win32 SetWindowsHookEx.

public void StartMouseHook()
{
// Faire une instance de HookProc.
MouseHookProcedure = new HookProc(MouseHookProc);
//installer le hook
int hook = SetWindowsHookEx(
WH_MOUSE_LL,
MouseHookProcedure,
Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[0]),
0);
//Si hook est a 0, donc la fonction SetWindowsHookEx a échoué
if (hook == 0)
{
//Retourn le code d'erreur
int errorCode = Marshal.GetLastWin32Error();
//Lance une exception du type Win32
throw new Win32Exception(errorCode);
}
}

Maintenant nous allons déclarer notre événement pour que notre code puisse s'y attacher.

Pour la souris, nous avons un événement :

1. OnMouseClick

OnMouseClick

private MouseEventHandler _onMouseClick;

public event MouseEventHandler OnMouseClick
{
add
{
_onMouseClick += value;
}
remove
{
_onMouseClick -= value;
}
}

Maintenant nous allons remplir la méthode MouseHookProc. Cette méthode sera appelée dès qu'un bouton de la souris sera appuyé.

private int MouseHookProc(int nCode, int wParam, IntPtr lParam)
{
bool processNextHook = true;

// Verifions si nCode est différent de 0 et que nos événements sont bien attachés
if ((nCode >= 0) && (_onMouseClick != null))
{
//Remplissage de la structure MouseLLHookStruct à partir d'un pointeur
MouseLLHookStruct mouseHookStruct = (MouseLLHookStruct)Marshal.PtrToStructure(lParam, typeof(MouseLLHookStruct));

//Detection du bouton clicker
MouseButtons button = MouseButtons.None;
switch (wParam)
{
case WM_LBUTTONDOWN:
button = MouseButtons.Left;
break;
case WM_RBUTTONDOWN:
button = MouseButtons.Right;
break;
}

//parametre de notre event
MouseEventArgs e = new MouseEventArgs(
button,
1,
mouseHookStruct.pt.x,
mouseHookStruct.pt.y,
0);
//On appelle notre event
OnMouseClick(this, e);
}
//Si processNextHook == true alors on transmet le clic au destinataire, sinon, on le garde pour nous (
if (processNextHook == true)
{
return CallNextHookEx(hMouseHook, nCode, wParam, lParam);
}
else
return 1;
}

II-B-2 - Méthode StartKeyHook

Dans cette méthode, nous allons initialiser le hook du clavier. Pour cela, nous utilisons toujours la fonction Win32 SetWindowsHookEx.

public void StartKeyHook()
{
KeyboardHookProcedure = new HookProc(KeyHookProc);
//Installer le hook
int hook = SetWindowsHookEx(
WH_KEYBOARD_LL,
KeyboardHookProcedure,
Marshal.GetHINSTANCE(
Assembly.GetExecutingAssembly().GetModules()[0]),
0);
//Si SetWindowsHookEx échoue
if (hook == 0)
{
int errorCode = Marshal.GetLastWin32Error();
throw new Win32Exception(errorCode);
}
}

Maintenant nous allons déclarer nos événements pour que notre code puisse s'y attacher.

Pour le clavier, nous avons trois événements :

1. OnKeyDown ;

2. OnKeyUp ;

3. OnKeyPress.

OnKeyDown

private KeyPressEventHandler _onKeyPress;

public event KeyPressEventHandler OnKeyPress
{
add
{
_onKeyPress += value;
}
remove
{
_onKeyPress -= value;
}
}

private KeyEventHandler _onKeyUp;

public event KeyEventHandler OnKeyUp
{
add
{
_onKeyUp += value;
}
remove
{
_onKeyUp -= value;
}
}

private KeyEventHandler _onKeyDown;

public event KeyEventHandler OnKeyDown
{
add
{
_onKeyDown += value;
}
remove
{
_onKeyDown -= value;
}
}

Maintenant nous allons remplir la méthode KeyHookProc. Cette méthode sera appelée dès qu'une touche du clavier sera appuyée.

private int KeyboardHookProc(int nCode, Int32 wParam, IntPtr lParam)
{
bool handled = false;
//On verifie si tous est ok
if ((nCode >= 0) && (_onKeyDown != null || _onKeyUp != null || _onKeyPress != null))
{
//Remplissage de la structure KeyboardHookStruct à partir d'un pointeur
KeyboardHookStruct MyKeyboardHookStruct = (KeyboardHookStruct)Marshal.PtrToStructure(lParam, typeof(KeyboardHookStruct));
//KeyDown
if (_onKeyDown != null && (wParam == WM_KEYDOWN || wParam == WM_SYSKEYDOWN))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
_onKeyDown(this, e);
handled = handled || e.Handled;
}

// KeyPress
if (_onKeyPress != null && wParam == WM_KEYDOWN)
{
// Si la touche Shift est appuyée
bool isShift = ((GetKeyState(VK_SHIFT) & 0x80) == 0x80 ? true : false);
// Si la touche CapsLock est appuyée
bool isCapslock = (GetKeyState(VK_CAPITAL) != 0 ? true : false);

byte[] keyState = new byte[256];
GetKeyboardState(keyState);
byte[] inBuffer = new byte[2];
if (ToAscii(MyKeyboardHookStruct.vkCode,
MyKeyboardHookStruct.scanCode,
keyState,
inBuffer,
MyKeyboardHookStruct.flags) == 1)
{
char key = (char)inBuffer[0];
if ((isCapslock ^ isShift) && Char.IsLetter(key))
key = Char.ToUpper(key);
KeyPressEventArgs e = new KeyPressEventArgs(key);
_onKeyPress(this, e);
handled = handled || e.Handled;
}
}

// KeyUp
if (_onKeyUp != null && (wParam == WM_KEYUP || wParam == WM_SYSKEYUP))
{
Keys keyData = (Keys)MyKeyboardHookStruct.vkCode;
KeyEventArgs e = new KeyEventArgs(keyData);
_onKeyUp(this, e);
handled = handled || e.Handled;
}

}

// si handled est a true, on ne transmet pas le message au destinataire
if (handled)
return 1;
else
return CallNextHookEx(hKeyboardHook, nCode, wParam, lParam);
}

III - Conclusion

Voilà, c'est terminé. Vous pouvez à présent intercepter toutes les actions de l'utilisateur.

Personnellement, je suis en train de me faire un nouveau menu démarrer en interceptant le clic sur le bouton démarrez. (http://www.nicolas-humann.com/index.php?2006/10/13/42-nstart-le-nouveau-menu-demarrer-pour-xp)