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▲
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▲
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▲
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▲
Déclaration :
[DllImport(
"user32"
)]
private
static
extern
int
ToAscii
(
int
uVirtKey,
int
uScanCode,
byte
[]
lpbKeyState,
byte
[]
lpwTransKey,
int
fuState);
II-A-1-e. GetKeyboardState▲
Déclaration :
[DllImport(
"user32"
)]
private
static
extern
int
GetKeyboardState
(
byte
[]
pbKeyState);
II-A-1-f. GetKeyState▲
Déclaration :
[DllImport(
"user32.dll"
, CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
private
static
extern
short
GetKeyState
(
int
vKey);
II-A-1-g. HookProc▲
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▲
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▲
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 :
- 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 :
- OnKeyDown ;
- OnKeyUp ;
- 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)