SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORDYou can uninstall a hook by calling UnhookWindowsHookEx which accepts only one parameter, the handle of the hook you want to uninstall. If the call succeeds, it returns a non-zero value in eax. Otherwise, it returns NULL.If the call is successful, it returns the hook handle in eax. If not, NULL is returned. You must save the hook handle for unhooking later.
- HookType is one of the values listed above, e.g., WH_MOUSE, WH_KEYBOARD
- pHookProc is the address of the hook procedure that will be called to process the messages for the specified hook. If the hook is a remote one, it must reside in a DLL. If not, it must be in your process.
- hInstance is the instance handle of the DLL in which the hook procedure resides. If the hook is a local one, this value must be NULL
- ThreadID is the ID of the thread you want to install the hook to spy on. This parameter is the one that determines whether a hook is local or remote. If this parameter is NULL, Windows will interpret the hook as a system-wide remote hook that affects all threads in the system. If you specify the thread ID of a thread in your own process, this hook is a local one. If you specify the thread ID from other process, the hook is a thread-specific remote one. There are two exceptions to this rule: WH_JOURNALRECORD and WH_JOURNALPLAYBACK are always local system-wide hooks that are not required to be in a DLL. And WH_SYSMSGFILTER is always a system-wide remote hook. Actually it is identical to WH_MSGFILTER hook with ThreadID==0.
WH_CALLWNDPROCThe bottom line is: you must consult your win32 api reference for details about the meanings of the parameters and return value of the hook you want to install.WH_MOUSE
- nCode can be only HC_ACTION which means there is a message sent to a window
- wParam contains the message being sent, if it's not zero
- lParam points to a CWPSTRUCT structure
- return value: not used, return zero
- nCode can be HC_ACTION or HC_NOREMOVE
- wParam contains the mouse message
- lParam points to a MOUSEHOOKSTRUCT structure
- return value: zero if the message should be processed. 1 if the message should be discarded.
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORDAn important note about remote hooks: the hook procedure must reside in a DLL which will be mapped into other processes. When Windows maps the DLL into other processes, it will not map the data section(s) into the other processes. In short, all processes share a single copy of code but they will have their own private copy of the DLL's data section! This can be a big surprise to the unwary. You may think that when you store a value into a variable in the data section of a DLL, that value will be shared among all processes that load the DLL into their process address space. It's simply not true. In normal situation, this behavior is desirable since it provides the illusion that each process has its own copy of the DLL. But not when Windows hook is concerned. We want the DLL to be identical in all processes, including the data. The solution: you must mark the data section as shared. You can do this by specifying the section(s) attribute in the linker switch. For MASM, you need to use this switch:
- hHook is your own hook handle. The function uses this handle to traverse the linked list and search for the hook procedure it should call next.
- nCode, wParam and lParam you can just pass those three values you receive from Windows to CallNextHookEx.
/SECTION:<section name>, SThe name of the initialized data section is .data and the uninitialized data is .bss. For example if you want to assemble a DLL which contains a hook procedure and you want the uninitialized data section to be shared amoung processes, you must use the following line:
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........S attribute marks the section as shared.
;---------------------------------------------
This is the source code of the main program --------------------------------------
.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
;-----------------------------------------------------
This is the source code of the 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
;---------------------------------------------- This is the makefile of the DLL ----------------------------------------------
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
The program maintains a flag,
HookFlag, to monitor the state of the hook. It's FALSE if the hook is not
installed and TRUE if the hook is installed.
When the user presses Hook
button, the program checks if the hook is already installed. If it is not,
it call InstallHook function in the hook DLL to install it. Note that we
pass the handle of the main dialog as the parameter of the function so
the hook DLL can send the WM_MOUSEHOOK messages to the right window i.e.
our own.
When the program is loaded,
the hook DLL is loaded too. Actually, DLLs are loaded immediately after
the program is in memory. The DLL entrypoint function is called before
the first instruction in the main program is execute even. So when the
main program executes the DLL(s) is/are initialized. We put the following
code in the DLL entrypoint function of the hook DLL:
.if
reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
The code just saves the instance handle of the hook DLL itself to a global variable named hInstance for use within the InstallHook function. Since the DLL entrypoint function is called before other functions in the DLL are called , hInstance is always valid. We put hInstance in .data section so that this value is kept on per-process basis. Since when the mouse cursor hovers over a window, the hook DLL is mapped into the process. Imagine that there is already a DLL that occupies the intended load address of the hook DLL, the hook DLL would be remapped to another address. The value of hInstance will be updated to those of the new load address. When the user presses Unhook button and then Hook button, SetWindowsHookEx will be called again. However, this time, it will use the new load address as the instance handle which will be wrong because in the example process, the hook DLL's load address hasn't been changed. The hook will be a local one where you can hook only the mouse events that occur in your own window. Hardly desirable.
InstallHook proc hwnd:DWORD
push
hwnd
pop
hWnd
invoke
SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov
hHook,eax
ret
InstallHook endp
The InstallHook function
itself is very simple. It saves the window handle passed as its parameter
to a global variable named hWnd for future use. It then calls SetWindowsHookEx
to install a mouse hook. The return value of SetWindowsHookEx is stored
in a global variable named hHook for use with UnhookWindowsHookEx.
After SetWindowsHookEx is
called, the mouse hook is functional. Whenever a mouse event occurs in
the system, MouseProc ( your hook procedure) is called.
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
The first thing it does is to call CallNextHookEx to give the other hooks the chance to process the mouse event. After that, it calls WindowFromPoint function to retrieve the handle of the window at the specified screen coordinate. Note that we use the POINT structure in the MOUSEHOOKSTRUCT structure pointed to by lParam as the current mouse coordinate. After that we send the window handle to the main program via PostMessage with WM_MOUSEHOOK message. One thing you should remember is that: you should not use SendMessage inside the hook procedure, it can cause message deadlock. PostMessage is recommended. The MOUSEHOOKSTRUCT structure is defined below:
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
To avoid flickers, we check
the text already in the edit controls and the text we will put into them
if they are identical. If they are, we skip them.
We retrieve the class name
by calling GetClassName, the address of the window procedure by calling
GetClassLong with GCL_WNDPROC and then format them into strings and put
them into the appropriate edit controls.
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
When the user presses Unhook
button, the program calls UninstallHook function in the hook DLL. UninstallHook
just calls UnhookWindowsHookEx. After that, it changes the text of the
button back to "Hook", HookFlag to FALSE and clears the content of the
edit controls.
Note the linker switch in
the makefile.
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS
It specifies .bss section
as a shared section to make all processes share the same uninitialized
data section of the hook DLL. Without this switch, your hook DLL will not
function correctly.