SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORDVous pouvez désinstaller votre hook en appelant UnhookWindowsHookEx lequel accepte qu'un seul paramètre, l'handle du hook que vous voulez désinstaller. Si l'appel réussit, il renvoie une valeur non-nulle dans eax. Sinon, il renvoie le NULL (forcément).si l'appel est couronné de succès, il renvoie l'handle du hook dans eax. Si c'est pas le cas, un NULL est renvoyé dans eax. Vous devez sauvegarder l'handle du hook pouvoir 'déhooker' plus tard.
- HookType est une des valeurs inscrites ci-dessus, par exemple,WH_MOUSE, WH_KEYBOARD
- pHookProc est l'adresse de la procédure d'hook qui sera appelée pour traiter les messages du hook spécifié. Si c'est un hook éloigné, il doit résider dans un DLL. Si ce n'est pas le cas, il doit appartenir à votre prog.
- hInstance est l'instance handle du DLL dans lequel la procédure d'hook réside. Si le hook est local, cette valeur doit être NULL.
- ThreadID est l'ID du lien sur lequel vous aimeriez installer votre hook pour l'espionner. C'est ce paramètre qui détermine si le hook est local ou éloigné. Si ce paramètre est NULL, Windows interprétera votre hook comme un hook système-large éloigné qui s'attaque à tous les liens, dans le système. Si vous spécifiez l'ID d'un lien de votre propre processus (programme), cet hook est local. Si vous spécifiez l'ID d'un autre processus, votre hook est un lien-spécifique éloigné. Il y a deux exceptions à cette règle :WH_JOURNALRECORD et WH_JOURNALPLAYBACK sont toujours des 'system-wide hooks' locaux (système-large), lequels n'ont pas besoin d'être dans un DLL. Et WH_SYSMSGFILTER est toujours un 'system-wide hook' éloigné. En réalité il est identique au hookWH_MSGFILTER avec ThreadID=0 (avec pour ID du lien une valeur NULL).
WH_CALLWNDPROCVous devez consulter votre référence win32 api pour de plus amples détails à propos de la signification des paramètres et de la valeur de retour des hooks que vous souhaitez installer.WH_MOUSE
- nCode peut seulement être HC_ACTION ce qui signifie qu'il y a un message envoyé à une fenêtre.
- wParam contient le message qui vient d'être envoyé, si ce n'est pas le cas il prendra la valeur zéro.
- lParam pointe sur la structure CWPSTRUCT.
- La valeur de retour: si on ne l'emploie pas , on gardera un retour NULL.
- nCode peut être soit HC_ACTION soit HC_NOREMOVE.
- wParam contient le message de la souris.
- lParam pointe sur la structure MOUSEHOOKSTRUCT.
- La valeur de retour: est '0' si le message doit être traité. Et '1' si le message doit être abandonné.
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORDUn point important sur les Hooks éloignés : la procédure d'hook doit résider dans un DLL lequel sera 'mappé' (configuré) dans d'autres processus. (Voir le tutorial 13). Lorsque Windows 'mappe' le DLL dans d'autres processus, il ne 'mappera' pas la section des données à l'intérieur de ces autres processus. Bref, tous les processus (les autres programmes) partagent une unique copie du code mais ils auront leur propre copie privée de la section des données du DLL! Ça peut être une surprise de taille pour les imprudents. Vous pouvez penser que lorsque vous stockez une valeur dans une variable appartenant à la section des données du DLL, que cette valeur sera partagée entres chacun des processus qui font appel à ce DLL., mais ce n'est pas tout à fait vrai. Dans une situation normale, ça se passe bien comme ça puisque on a l'illusion que chaque processus a sa propre copie du DLL. Mais ce n'est pas le cas quand hook de Windows est concerné. Nous souhaitons que le DLL soit identiques pour tous les processus, incluant les données. La solution : on doit marquer la section des données en tant que section partagée. On peut faire ça en spécifiant l'attribut de section dans le commutateur de linker. Pour MASM, vous avez besoin d'employer ce commutateur :
- hHook est l'handle de votre propre hook. La fonction emploie cet handle pour fouiller la liste et chercher la procédure d'hook qu'elle doit appeler ensuite.
- nCode, wParam et lParam sont seulement trois valeurs que vous avez la possibilité de passer à d'autres fonctions. On les reçoit après l'appel: CallNextHookEx.
/SECTION:<section name>, SLe nom de la section des données initialisées est .data et les données non initialisées sont .bss. Par exemple si vous voulez assembler un DLL qui contient une procédure d'hook et que vous voulez que la section des données non initialisée soit partagée entre plusieurs processus, vous devez employer la ligne suivante :
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........L'attribut 'S' marque la section en tant que section partagée.
;---------------------------------------------
Voici le code source du programme principal --------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\user32.inc
include \masm32\include\kernel32.inc
include mousehook.inc
includelib mousehook.lib
includelib \masm32\lib\user32.lib
includelib \masm32\lib\kernel32.lib
wsprintfA proto C :DWORD,:DWORD,:VARARG
wsprintf TEXTEQU <wsprintfA>
.const
IDD_MAINDLG
equ 101
IDC_CLASSNAME
equ 1000
IDC_HANDLE
equ 1001
IDC_WNDPROC
equ 1002
IDC_HOOK
equ 1004
IDC_EXIT
equ 1005
WM_MOUSEHOOK
equ WM_USER+6
DlgFunc PROTO :DWORD,:DWORD,:DWORD,:DWORD
.data
HookFlag dd FALSE
HookText db "&Hook",0
UnhookText db "&Unhook",0
template db "%lx",0
.data?
hInstance dd ?
hHook dd ?
.code
start:
invoke
GetModuleHandle,NULL
mov
hInstance,eax
invoke
DialogBoxParam,hInstance,IDD_MAINDLG,NULL,addr DlgFunc,NULL
invoke
ExitProcess,NULL
DlgFunc proc hDlg:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD
LOCAL
hLib:DWORD
LOCAL
buffer[128]:byte
LOCAL
buffer1[128]:byte
LOCAL
rect:RECT
.if
uMsg==WM_CLOSE
.if HookFlag==TRUE
invoke UninstallHook
.endif
invoke EndDialog,hDlg,NULL
.elseif
uMsg==WM_INITDIALOG
invoke GetWindowRect,hDlg,addr rect
invoke SetWindowPos, hDlg, HWND_TOPMOST, rect.left, rect.top, rect.right,
rect.bottom, SWP_SHOWWINDOW
.elseif
uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
.elseif
uMsg==WM_COMMAND
.if lParam!=0
mov eax,wParam
mov edx,eax
shr edx,16
.if dx==BN_CLICKED
.if ax==IDC_EXIT
invoke SendMessage,hDlg,WM_CLOSE,0,0
.else
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
.else
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
.endif
.endif
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov
eax,TRUE
ret
DlgFunc endp
end start
;---------------------------------------------------
Et voici enfin le code source du DLL ------------------------------------
.386
.model flat,stdcall
option casemap:none
include \masm32\include\windows.inc
include \masm32\include\kernel32.inc
includelib \masm32\lib\kernel32.lib
include \masm32\include\user32.inc
includelib \masm32\lib\user32.lib
.const
WM_MOUSEHOOK equ WM_USER+6
.data
hInstance dd 0
.data?
hHook dd ?
hWnd dd ?
.code
DllEntry proc hInst:HINSTANCE,
reason:DWORD, reserved1:DWORD
.if
reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
mov
eax,TRUE
ret
DllEntry Endp
MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke
CallNextHookEx,hHook,nCode,wParam,lParam
mov
edx,lParam
assume
edx:PTR MOUSEHOOKSTRUCT
invoke
WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke
PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume
edx:nothing
xor
eax,eax
ret
MouseProc endp
InstallHook proc hwnd:DWORD
push
hwnd
pop
hWnd
invoke
SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov
hHook,eax
ret
InstallHook endp
UninstallHook proc
invoke
UnhookWindowsHookEx,hHook
ret
UninstallHook endp
End DllEntry
;--------------------------------C'est le makefile du DLL (le moyen de l'assembler)------------------------------
NAME=mousehook
$(NAME).dll: $(NAME).obj
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS /LIBPATH:c:\masm\lib
$(NAME).obj
$(NAME).obj: $(NAME).asm
ml /c /coff /Cp $(NAME).asm
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
Le programme maintient un 'Flag' nommé HookFlag, pour rendre compte de l'état du hook. S'il est 'FALSE'(faux) alors c'est que le hook n'est pas activé. Au contraire s'il est TRUE(vrai) c'est qu'il est actif.
Quand l'utilisateur appuie sur le bouton 'hook', le programme vérifie s'il est déjà installé (actif). Si ce n'est pas le cas, on appelle la fonction InstallHook du hook du DLL pour l'installer. Notez que nous passons l'handle de notre dialogBox principale en tant que paramètre de la fonction pour que le DLL (contenant la fonction d'hook) puisse envoyer les différents messages WM_MOUSEHOOK vers la bonne fenêtre, c'est-à-dire la nôtre.
A partir du moment où le programme est chargé, le DLL est chargé lui aussi. En réalité, les DLLs sont immédiatement chargés après que le programme ait été mis en mémoire. L'entrypoint du DLL est appelé avant même que la première instruction du programme principal ne soit exécuté. (EntryPoint=première ligne de code lue dans un programme=début) Ainsi quand le programme principal se met en route, le(s) DLL(s) est/sont déjà initialisé(s). Nous écrivons le code suivant pour l'entrypoint du DLL :
.if
reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
Ce code sauvegarde juste l'instance handle du DLL dans une variable globale nommée hInstance pour pouvoir l'utiliser dans la fonction InstallHook. Puisque l'entrypoint du DLL est appelé avant les autres fonctions dans le DLL, l'hInstance est toujours valable. On définit hInstance dans la section .data pour que cette valeur soit prête "pou le processus". Le DLL est 'mappé'(configuré) dans le process, avant même que le curseur de souris ne plane au-dessus des fenêtres ou objets qu'elle doit hooker. Imaginez-vous que par hasard il y ait déjà un DLL qui occupe l'adresse de chargement destinée au DLL (contenant notre hook), alors dans ce cas le DLL serait reconfiguré à une autre adresse. La valeur de 'hInstance' serait remise à jour en prenant en compte la nouvelle adresse de chargement. Lorsque l'utilisateur presse le bouton 'Unhook' puis par la suite le bouton 'Hook', SetWindowsHookEx sera de nouveau appelé. Cependant, cette fois, il utilisera la nouvelle adresse de chargement en tant qu' instance handle mais qui malheureusement sera faux puisque dans notre programme d'exemple, l'adresse de chargement du DLL n'aura pas été changée. Le Hook sera local, c'est pourquoi vous pourrez uniquement hooker les événements de la souris qui arriveront dans votre propre fenêtre. À peine désirable.
InstallHook proc hwnd:DWORD
push
hwnd
pop
hWnd
invoke
SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov
hHook,eax
ret
InstallHook endp
La fonction InstallHook est très simple en elle-même. Elle sauvegarde l'handle de la fenêtre dans une variable globale nommée hWnd pour l'utiliser plus tard. On appelle alors SetWindowsHookEx pour installer (activer) le hook sous la souris. La valeur de retour de SetWindowsHookEx est stockée dans une variable globale nommée hHook pour l'utiliser avec UnhookWindowsHookEx au moment où on souhaite désactiver ce hook.
Après qu'on ait appelé SetWindowsHookEx, le hook sous la souris est fonctionnel. Chaque fois qu'un événement souris arrive dans le système, MouseProc (votre procédure d'hook) est appelé.
MouseProc proc nCode:DWORD,wParam:DWORD,lParam:DWORD
invoke CallNextHookEx,hHook,nCode,wParam,lParam
mov edx,lParam
assume edx:PTR MOUSEHOOKSTRUCT
invoke WindowFromPoint,[edx].pt.x,[edx].pt.y
invoke PostMessage,hWnd,WM_MOUSEHOOK,eax,0
assume edx:nothing
xor eax,eax
ret
MouseProc endp
La première chose à faire, c'est d'appeler CallNextHookEx pour donner à d'autres Hooks la chance de traiter de nouveaux événements souris. Après ça, on appelle la fonction WindowFromPoint pour retrouver l'handle de la fenêtre (ou de l'objet) auxquelles sont indiquées les coordonnée (écran) de la souris. Remarquez que nous employons POINT dans la structure MOUSEHOOKSTRUCT représenté par lParam en tant que coordonnées actuelles de la souris. Après ça nous envoyons l'handle de la fenêtre (ou de l'objet) au programme principal via PostMessage avec le message WM_MOUSEHOOK. Une chose à se rappeler, c'est que : vous ne devez pas employer SendMessage à l'intérieur de la procédure d'hook, ceci peut provoquer une impasse pour les messages. Je recommande PostMessage. La structure MOUSEHOOKSTRUCT est définie ainsi:
MOUSEHOOKSTRUCT STRUCT DWORD
pt
POINT <>
hwnd
DWORD ?
wHitTestCode
DWORD ?
dwExtraInfo
DWORD ?
MOUSEHOOKSTRUCT ENDS
.elseif
uMsg==WM_MOUSEHOOK
invoke GetDlgItemText,hDlg,IDC_HANDLE,addr buffer1,128
invoke wsprintf,addr buffer,addr template,wParam
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_HANDLE,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer1,128
invoke GetClassName,wParam,addr buffer,128
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,addr buffer
.endif
invoke GetDlgItemText,hDlg,IDC_WNDPROC,addr buffer1,128
invoke GetClassLong,wParam,GCL_WNDPROC
invoke wsprintf,addr buffer,addr template,eax
invoke lstrcmpi,addr buffer,addr buffer1
.if eax!=0
invoke SetDlgItemText,hDlg,IDC_WNDPROC,addr buffer
.endif
Pour éviter des scintillements, nous vérifions le texte qui est déjà inscrit dans les commandes d'édition avec le texte que nous souhaitons placer à leur place. s'ils sont identiques, on saute par dessus l'instruction de réécriture.
Nous retrouvons le nom de classe (la Class Name) en appelant GetClassName, et l'adresse de la procédure de fenêtre (La Window Proc) en appelant GetClassLong avec GCL_WNDPROC puis les mettons ensuite sous forme de chaînes de caractères pour les placer dans les contrôles d'édition appropriés.
invoke UninstallHook
invoke SetDlgItemText,hDlg,IDC_HOOK,addr HookText
mov HookFlag,FALSE
invoke SetDlgItemText,hDlg,IDC_CLASSNAME,NULL
invoke SetDlgItemText,hDlg,IDC_HANDLE,NULL
invoke SetDlgItemText,hDlg,IDC_WNDPROC,NULL
Quand l'utilisateur appuie sur le bouton 'Unhook', le programme appelle la fonction UninstallHook qui se trouve dans le DLL (avec la fonction hook). UninstallHook appelle uniquement UnhookWindowsHookEx. Après ça, le texte du bouton se retransforme en "Hook", le flaf HookFlag est remis à 'False' et on bloque le contenu des contrôles d'édition.
Notez comment est décrit le linker dans le makefile.
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS
La section .bss est définie comme une section partagée pour faire en sorte que plusieurs processus (programmes) puissent utiliser les données non initialisée de ce DLL en même temps. Sans ce commutateur, votre DLL ne fonctionnerait pas correctement.