本課中我們將要學(xué)習(xí)WINDOWS鉤子函數(shù)的使用方法。WINDOWS鉤子函數(shù)的功能非常強大,有了它您可以探測其它進程并且改變其它進程的行為。
理論:
WINDOWS的鉤子函數(shù)可以認(rèn)為是WINDOWS的主要特性之一。利用它們,您可以捕捉您自己進程或其它進程發(fā)生的事件。通過“鉤掛”,您可以給WINDOWS一個處理或過濾事件的回調(diào)函數(shù),該函數(shù)也叫做“鉤子函數(shù)”,當(dāng)每次發(fā)生您感興趣的事件時,WINDOWS都將調(diào)用該函數(shù)。一共有兩種類型的鉤子:局部的和遠(yuǎn)程的。
局部鉤子僅鉤掛您自己進程的事件。
遠(yuǎn)程的鉤子還可以將鉤掛其它進程發(fā)生的事件。遠(yuǎn)程的鉤子又有兩種:
基于線程的 它將捕獲其它進程中某一特定線程的事件。簡言之,就是可以用來觀察其它進程中的某一特定線程將發(fā)生的事件。
系統(tǒng)范圍的 將捕捉系統(tǒng)中所有進程將發(fā)生的事件消息。
安裝鉤子函數(shù)將會影響系統(tǒng)的性能。監(jiān)測“系統(tǒng)范圍事件”的系統(tǒng)鉤子特別明顯。因為系統(tǒng)在處理所有的相關(guān)事件時都將調(diào)用您的鉤子函數(shù),這樣您的系統(tǒng)將會明顯的減慢。所以應(yīng)謹(jǐn)慎使用,用完后立即卸載。還有,由于您可以預(yù)先截獲其它進程的消息,所以一旦您的鉤子函數(shù)出了問題的話必將影響其它的進程。記住:功能強大也意味著使用時要負(fù)責(zé)任。
在正確使用鉤子函數(shù)前,我們先講解鉤子函數(shù)的工作原理。當(dāng)您創(chuàng)建一個鉤子時,WINDOWS會先在內(nèi)存中創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu),該數(shù)據(jù)結(jié)構(gòu)包含了鉤子的相關(guān)信息,然后把該結(jié)構(gòu)體加到已經(jīng)存在的鉤子鏈表中去。新的鉤子將加到老的前面。當(dāng)一個事件發(fā)生時,如果您安裝的是一個局部鉤子,您進程中的鉤子函數(shù)將被調(diào)用。如果是一個遠(yuǎn)程鉤子,系統(tǒng)就必須把鉤子函數(shù)插入到其它進程的地址空間,要做到這一點要求鉤子函數(shù)必須在一個動態(tài)鏈接庫中,所以如果您想要使用遠(yuǎn)程鉤子,就必須把該鉤子函數(shù)放到動態(tài)鏈接庫中去。當(dāng)然有兩個例外:工作日志鉤子和工作日志回放鉤子。這兩個鉤子的鉤子函數(shù)必須在安裝鉤子的線程中。原因是:這兩個鉤子是用來監(jiān)控比較底層的硬件事件的,既然是記錄和回放,所有的事件就當(dāng)然都是有先后次序的。所以如果把回調(diào)函數(shù)放在DLL中,輸入的事件被放在幾個線程中記錄,所以我們無法保證得到正確的次序。故解決的辦法是:把鉤子函數(shù)放到單個的線程中,譬如安裝鉤子的線程。
鉤子一共有14種,以下是它們被調(diào)用的時機:
WH_CALLWNDPROC 當(dāng)調(diào)用SendMessage時
WH_CALLWNDPROCRET 當(dāng)SendMessage的調(diào)用返回時
WH_GETMESSAGE 當(dāng)調(diào)用GetMessage 或 PeekMessage時
WH_KEYBOARD 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊列中查詢WM_KEYUP 或 WM_KEYDOWN 消息時
WH_MOUSE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊列中查詢鼠標(biāo)事件消息時
WH_HARDWARE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊列種查詢非鼠標(biāo)、鍵盤消息時
WH_MSGFILTER 當(dāng)對話框、菜單或滾動條要處理一個消息時。該鉤子是局部的。它時為那些有自己的消息處理過程的控件對象設(shè)計的。
WH_SYSMSGFILTER 和WH_MSGFILTER一樣,只不過是系統(tǒng)范圍的
WH_JOURNALRECORD 當(dāng)WINDOWS從硬件隊列中獲得消息時
WH_JOURNALPLAYBACK 當(dāng)一個事件從系統(tǒng)的硬件輸入隊列中被請求時
WH_SHELL 當(dāng)關(guān)于WINDOWS外殼事件發(fā)生時,譬如任務(wù)條需要重畫它的按鈕.
WH_CBT 當(dāng)基于計算機的訓(xùn)練(CBT)事件發(fā)生時
WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的應(yīng)用程序很少使用
WH_DEBUG 用來給鉤子函數(shù)除錯
現(xiàn)在我們知道了一些基本的理論,現(xiàn)在開始講解如何安裝/卸載一個鉤子。
要安裝一個鉤子,您可以調(diào)用SetWindowHookEx函數(shù)。該函數(shù)的原型如下:
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
HookType 是我們上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD
pHookProc 是鉤子函數(shù)的地址。如果使用的是遠(yuǎn)程的鉤子,就必須放在一個DLL中,否則放在本身代碼中
hInstance 鉤子函數(shù)所在DLL的實例句柄。如果是一個局部的鉤子,該值為NULL
ThreadID 是您安裝該鉤子函數(shù)后想監(jiān)控的線程的ID號。該參數(shù)可以決定該鉤子是局部的還是系統(tǒng)范圍的。如果該值為NULL,那么該鉤子將被解釋成系統(tǒng)范圍內(nèi)的,那它就可以監(jiān)控所有的進程及它們的線程。如果您指定了您自己進程中的某個線程ID 號,那該鉤子是一個局部的鉤子。如果該線程ID是另一個進程中某個線程的ID,那該鉤子是一個全局的遠(yuǎn)程鉤子。這里有兩個特殊情況:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK總是代表局部的系統(tǒng)范圍的鉤子,之所以說是局部,是因為它們沒有必要放到一個DLL中。WH_SYSMSGFILTER 總是一個系統(tǒng)范圍內(nèi)的遠(yuǎn)程鉤子。其實它和WH_MSGFILTER鉤子類似,如果把參數(shù)ThreadID設(shè)成0的話,它們就完全一樣了。
如果該函數(shù)調(diào)用成功的話,將在eax中返回鉤子的句柄,否則返回NULL。您必須保存該句柄,因為后面我們還要它來卸載鉤子。
要卸載一個鉤子時調(diào)用UnhookWidowHookEx函數(shù),該函數(shù)僅有一個參數(shù),就是欲卸載的鉤子的句柄。如果調(diào)用成功的話,在eax中返回非0值,否則返回NULL。
現(xiàn)在您知道了如何安裝和卸載一個鉤子了,接下來我們將看看鉤子函數(shù)。.
只要您安裝的鉤子的消息事件類型發(fā)生,WINDOWS就將調(diào)用鉤子函數(shù)。譬如您安裝的鉤子是WH_MOUSE類型,那么只要有一個鼠標(biāo)事件發(fā)生時,該鉤子函數(shù)就會被調(diào)用。不管您安裝的時那一類型鉤子,鉤子函數(shù)的原型都時是一樣的:
HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
nCode 指定是否需要處理該消息
wParam 和 lParam 包含該消息的附加消息
HookProc 可以看作是一個函數(shù)名的占位符。只要函數(shù)的原型一致,您可以給該函數(shù)取任何名字。至于以上的幾個參數(shù)及返回值的具體含義各種類型的鉤子都不相同。譬如:
WH_CALLWNDPROC
nCode 只能是HC_ACTION,它代表有一個消息發(fā)送給了一個窗口
wParam 如果非0,代表正被發(fā)送的消息
lParam 指向CWPSTRUCT型結(jié)構(gòu)體變量的指針
return value: 未使用,返回0
WH_MOUSE
nCode 為HC_ACTION 或 HC_NOREMOVE
wParam 包含鼠標(biāo)的事件消息
lParam 指向MOUSEHOOKSTRUCT型結(jié)構(gòu)體變量的指針
return value: 如果不處理返回0,否則返回非0值
所以您必須查詢您的WIN32 API 指南來得到不同類型的鉤子的參數(shù)的詳細(xì)定義以及它們返回值的意義。這里還有一個問題需要注意:所有的鉤子都串在一個鏈表上,近加入的鉤子放在鏈表的頭部。當(dāng)一個事件發(fā)生時,WINDOWS將按照從鏈表頭到鏈表尾調(diào)用的順序。所以您的鉤子函數(shù)有責(zé)任把消息傳到下一個鏈中的鉤子函數(shù)。當(dāng)然您可以不這樣做,但是您好明白這時這么做的原因。在大多數(shù)的情況下,好把消息事件傳遞下去以便其它的鉤子都有機會獲得處理這一消息的機會。調(diào)用下一個鉤子函數(shù)可以調(diào)用函數(shù)CallNextHookEx。該函數(shù)的原型如下:
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
hHook 時是您自己的鉤子函數(shù)的句柄。利用該句柄可以遍歷鉤子鏈。
nCode, wParam and lParam 您只要把傳入的參數(shù)簡單傳給CallNextHookEx即可。
請注意:對于遠(yuǎn)程鉤子,鉤子函數(shù)必須放到DLL中,它們將從DLL中映射到其它的進程空間中去。當(dāng)WINDOWS映射DLL到其它的進程空間中去時,不會把數(shù)據(jù)段也進行映射。簡言之,所有的進程僅共享DLL的代碼,至于數(shù)據(jù)段,每一個進程都將有其單獨的拷貝。這是一個很容易被忽視的問題。您可能想當(dāng)然的以為,在DLL中保存的值可以在所有映射該DLL的進程之間共享。在通常情況下,由于每一個映射該DLL的進程都有自己的數(shù)據(jù)段,所以在大多數(shù)的情況下您的程序運行得都不錯。但是鉤子函數(shù)卻不是如此。對于鉤子函數(shù)來說,要求DLL的數(shù)據(jù)段對所有的進程也必須相同。這樣您就必須把數(shù)據(jù)段設(shè)成共享的,這可以通過在鏈接開關(guān)中指定段的屬性來實現(xiàn)。在MASM中您可以這么做:
/SECTION:, S
已初期化的段名是.data,未初始化的段名是.bss。`加入您想要寫一個包含鉤子函數(shù)的DLL,而且想使它的未初始化的數(shù)據(jù)段在所有進程間共享,您必須這么做:
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
S 代表該段是共享段。例子:
一共有兩個模塊:一個是GUI部分,另一個是安裝和卸載鉤子的DLL。
;--------------------------------------------- 主程序的源代碼部分--------------------------------------
.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
.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_MOST, 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
;----------------------------------------------------- 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
;---------------------------------------------- DLL的Makefile文件 ----------------------------------------------
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
分析:
該應(yīng)用程序的主窗口中包括三個編輯控件,它們將分別顯示當(dāng)前鼠標(biāo)光標(biāo)所在位置的窗口類名、窗口句柄和窗口過程的地址。還有兩個按鈕:“Hook”和“Eixt”。當(dāng)您按下Hook時,應(yīng)用程序?qū)^掛鼠標(biāo)輸入的事件消息,該按鈕的文本將變成“Unhook”。當(dāng)您把鼠標(biāo)關(guān)標(biāo)滑過一個窗口時,該窗口的有關(guān)消息將顯示在主窗口中。當(dāng)您按下“Unhook”時,應(yīng)用程序?qū)⑿遁d鉤子。 主窗口使用一個對話框來作為它的主窗口。它自定義了一個消息WM_MOUSEHOOK,用來在主窗口和DLL之間傳遞消息。當(dāng)主窗口接收到該消息時,wParam中包含了光標(biāo)所在位置的窗口的句柄。當(dāng)然這是我們做的安排。我這么做只是為了方便。您可以使用您自己的方法在主應(yīng)用程序和DLL之間進行通訊。
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
該應(yīng)用程序有一個全局變量,HookFlag,它用來監(jiān)視鉤子的狀態(tài)。如果安裝來鉤子它就是TRUE,否則是FALSE。 當(dāng)用戶按下Hook按鈕時,應(yīng)用程序檢查鉤子是否已經(jīng)安裝。如果還沒有的話,它將調(diào)用DLL中引出的函數(shù)InstallHook來安裝它。注意我們把主對話框的句柄傳遞給了DLL,這樣這個鉤子DLL就可以把WM_MOUSEHOOK消息傳遞給正確的窗口了。當(dāng)應(yīng)用程序加載時,鉤子DLL也同時加載。時機上當(dāng)主程序一旦加載到內(nèi)存中后,DLL就立即加載。DLL的入口點函數(shù)載主程序的第一條語句執(zhí)行前就前執(zhí)行了。所以當(dāng)主程序執(zhí)行時,DLL已經(jīng)初始化好了。我們載入口點處放入如下代碼:
.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
該段代碼把DLL自己的實例句柄放到一個全局變量中保存。由于入口點函數(shù)是在所有函數(shù)調(diào)用前被執(zhí)行的,所以hInstance總是有效的。我們把該變量放到.data中,使得每一個進程都有自己一個該變量的值。因為當(dāng)鼠標(biāo)光標(biāo)停在一個窗口上時,鉤子DLL被映射進進程的地址空間。加入在DLL缺省加載的地址處已經(jīng)加載其它的DLL,那鉤子DLL將要被映射到其他的地址。hInstance將被更新成其它的值。當(dāng)用戶按下Unhook再按下Hook時,SetWindowsHookEx將被再次調(diào)用。這,它將把新的地址作為實例句柄。而在例子中這是錯誤的,DLL裝載的地址并沒有變。這個鉤子將變成一個局部的,您只能鉤掛發(fā)生在您窗口中的鼠標(biāo)事件,這是很難讓人滿意的 。
InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp
InstallHook 函數(shù)非常簡單。它把傳遞過來的窗口句柄保存在hWnd中以備后用。接著調(diào)用SetWindowsHookEx函數(shù)來安裝一個鼠標(biāo)鉤子。該函數(shù)的返回值放在全局變量hHook中,將來在UnhookWindowsHookEx中還要使用。在調(diào)用SetWindowsHookEx后,鼠標(biāo)鉤子就開始工作了。無論什么時候發(fā)生了鼠標(biāo)事件,MouseProc函數(shù)都將被調(diào)用:
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
鉤子函數(shù)首先調(diào)用CallNextHookEx函數(shù)讓其它的鉤子處理該鼠標(biāo)事件。然后,調(diào)用WindowFromPoint函數(shù)來得到給定屏幕坐標(biāo)位置處的窗口句柄。注意:我們用lParam指向的MOUSEHOOKSTRUCT型結(jié)構(gòu)體變量中的POINT成員變量作為當(dāng)前的鼠標(biāo)位置。在我們調(diào)用PostMessage函數(shù)把WM_MOUSEHOOK消息發(fā)送到主程序。您必須記住的一件事是:在鉤子函數(shù)中不要使用SendMessage函數(shù),它會引起死鎖。MOUSEHOOKSTRUCT的定義如下:
MOUSEHOOKSTRUCT STRUCT DWORD
pt POINT <>
hwnd DWORD ?
wHitTestCode DWORD ?
dwExtraInfo DWORD ?
MOUSEHOOKSTRUCT ENDS
pt 是當(dāng)前鼠標(biāo)所在的屏幕位置。
hwnd 是將接收鼠標(biāo)消息的窗口的句柄。通常它是鼠標(biāo)所在處的窗口,但是如果窗口調(diào)用了SetCapture,鼠標(biāo)的輸入將到向到這個窗口。因我們不用該成員變量而是用WindowFromPoint函數(shù)。
wHitTestCode 指定hit-test值,該值給出了更多的鼠標(biāo)位置值。它指定了鼠標(biāo)在窗口的那個部位。該值的完全列表,請參考WIN32 API 指南中的WM_NCHITTEST消息。
dwExtraInfo 該值包含了相關(guān)的信息。一般該值由mouse_event函數(shù)設(shè)定,可以調(diào)用GetMessageExtraInfo來獲得。
當(dāng)主窗口接收到WM_MOUSEHOOK 消息時,它用wParam參數(shù)中的窗口句柄來查詢窗口的消息。
.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
為了避免重繪文本時的抖動,我們把已經(jīng)在編輯空間中線時的文本和我們將要顯示的對比。如果相同,就可以忽略掉。得到類名調(diào)用GetClassName,得到窗口過程調(diào)用GetClassLong并傳入GCL_WNDPROC標(biāo)志,然后把它們格式化成文本串并放到相關(guān)的編輯空間中去。
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
當(dāng)用戶按下Unhook后,主程序調(diào)用DLL中的UninstallHook函數(shù)。該函數(shù)調(diào)用UnhookWindowsHookEx函數(shù)。然后,它把按鈕的文本換回“Hook”,HookFlag的值設(shè)成FALSE再清除掉編輯控件中的文本。
鏈接器的開關(guān)選項如下:
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS
它指定.bss段作為一個共享段以便所有映射該DLL的進程共享未初始化的數(shù)據(jù)段。如果不用該開關(guān),您DLL中的鉤子就不能正常工作了。
理論:
WINDOWS的鉤子函數(shù)可以認(rèn)為是WINDOWS的主要特性之一。利用它們,您可以捕捉您自己進程或其它進程發(fā)生的事件。通過“鉤掛”,您可以給WINDOWS一個處理或過濾事件的回調(diào)函數(shù),該函數(shù)也叫做“鉤子函數(shù)”,當(dāng)每次發(fā)生您感興趣的事件時,WINDOWS都將調(diào)用該函數(shù)。一共有兩種類型的鉤子:局部的和遠(yuǎn)程的。
局部鉤子僅鉤掛您自己進程的事件。
遠(yuǎn)程的鉤子還可以將鉤掛其它進程發(fā)生的事件。遠(yuǎn)程的鉤子又有兩種:
基于線程的 它將捕獲其它進程中某一特定線程的事件。簡言之,就是可以用來觀察其它進程中的某一特定線程將發(fā)生的事件。
系統(tǒng)范圍的 將捕捉系統(tǒng)中所有進程將發(fā)生的事件消息。
安裝鉤子函數(shù)將會影響系統(tǒng)的性能。監(jiān)測“系統(tǒng)范圍事件”的系統(tǒng)鉤子特別明顯。因為系統(tǒng)在處理所有的相關(guān)事件時都將調(diào)用您的鉤子函數(shù),這樣您的系統(tǒng)將會明顯的減慢。所以應(yīng)謹(jǐn)慎使用,用完后立即卸載。還有,由于您可以預(yù)先截獲其它進程的消息,所以一旦您的鉤子函數(shù)出了問題的話必將影響其它的進程。記住:功能強大也意味著使用時要負(fù)責(zé)任。
在正確使用鉤子函數(shù)前,我們先講解鉤子函數(shù)的工作原理。當(dāng)您創(chuàng)建一個鉤子時,WINDOWS會先在內(nèi)存中創(chuàng)建一個數(shù)據(jù)結(jié)構(gòu),該數(shù)據(jù)結(jié)構(gòu)包含了鉤子的相關(guān)信息,然后把該結(jié)構(gòu)體加到已經(jīng)存在的鉤子鏈表中去。新的鉤子將加到老的前面。當(dāng)一個事件發(fā)生時,如果您安裝的是一個局部鉤子,您進程中的鉤子函數(shù)將被調(diào)用。如果是一個遠(yuǎn)程鉤子,系統(tǒng)就必須把鉤子函數(shù)插入到其它進程的地址空間,要做到這一點要求鉤子函數(shù)必須在一個動態(tài)鏈接庫中,所以如果您想要使用遠(yuǎn)程鉤子,就必須把該鉤子函數(shù)放到動態(tài)鏈接庫中去。當(dāng)然有兩個例外:工作日志鉤子和工作日志回放鉤子。這兩個鉤子的鉤子函數(shù)必須在安裝鉤子的線程中。原因是:這兩個鉤子是用來監(jiān)控比較底層的硬件事件的,既然是記錄和回放,所有的事件就當(dāng)然都是有先后次序的。所以如果把回調(diào)函數(shù)放在DLL中,輸入的事件被放在幾個線程中記錄,所以我們無法保證得到正確的次序。故解決的辦法是:把鉤子函數(shù)放到單個的線程中,譬如安裝鉤子的線程。
鉤子一共有14種,以下是它們被調(diào)用的時機:
WH_CALLWNDPROC 當(dāng)調(diào)用SendMessage時
WH_CALLWNDPROCRET 當(dāng)SendMessage的調(diào)用返回時
WH_GETMESSAGE 當(dāng)調(diào)用GetMessage 或 PeekMessage時
WH_KEYBOARD 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊列中查詢WM_KEYUP 或 WM_KEYDOWN 消息時
WH_MOUSE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊列中查詢鼠標(biāo)事件消息時
WH_HARDWARE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊列種查詢非鼠標(biāo)、鍵盤消息時
WH_MSGFILTER 當(dāng)對話框、菜單或滾動條要處理一個消息時。該鉤子是局部的。它時為那些有自己的消息處理過程的控件對象設(shè)計的。
WH_SYSMSGFILTER 和WH_MSGFILTER一樣,只不過是系統(tǒng)范圍的
WH_JOURNALRECORD 當(dāng)WINDOWS從硬件隊列中獲得消息時
WH_JOURNALPLAYBACK 當(dāng)一個事件從系統(tǒng)的硬件輸入隊列中被請求時
WH_SHELL 當(dāng)關(guān)于WINDOWS外殼事件發(fā)生時,譬如任務(wù)條需要重畫它的按鈕.
WH_CBT 當(dāng)基于計算機的訓(xùn)練(CBT)事件發(fā)生時
WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的應(yīng)用程序很少使用
WH_DEBUG 用來給鉤子函數(shù)除錯
現(xiàn)在我們知道了一些基本的理論,現(xiàn)在開始講解如何安裝/卸載一個鉤子。
要安裝一個鉤子,您可以調(diào)用SetWindowHookEx函數(shù)。該函數(shù)的原型如下:
SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD
HookType 是我們上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD
pHookProc 是鉤子函數(shù)的地址。如果使用的是遠(yuǎn)程的鉤子,就必須放在一個DLL中,否則放在本身代碼中
hInstance 鉤子函數(shù)所在DLL的實例句柄。如果是一個局部的鉤子,該值為NULL
ThreadID 是您安裝該鉤子函數(shù)后想監(jiān)控的線程的ID號。該參數(shù)可以決定該鉤子是局部的還是系統(tǒng)范圍的。如果該值為NULL,那么該鉤子將被解釋成系統(tǒng)范圍內(nèi)的,那它就可以監(jiān)控所有的進程及它們的線程。如果您指定了您自己進程中的某個線程ID 號,那該鉤子是一個局部的鉤子。如果該線程ID是另一個進程中某個線程的ID,那該鉤子是一個全局的遠(yuǎn)程鉤子。這里有兩個特殊情況:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK總是代表局部的系統(tǒng)范圍的鉤子,之所以說是局部,是因為它們沒有必要放到一個DLL中。WH_SYSMSGFILTER 總是一個系統(tǒng)范圍內(nèi)的遠(yuǎn)程鉤子。其實它和WH_MSGFILTER鉤子類似,如果把參數(shù)ThreadID設(shè)成0的話,它們就完全一樣了。
如果該函數(shù)調(diào)用成功的話,將在eax中返回鉤子的句柄,否則返回NULL。您必須保存該句柄,因為后面我們還要它來卸載鉤子。
要卸載一個鉤子時調(diào)用UnhookWidowHookEx函數(shù),該函數(shù)僅有一個參數(shù),就是欲卸載的鉤子的句柄。如果調(diào)用成功的話,在eax中返回非0值,否則返回NULL。
現(xiàn)在您知道了如何安裝和卸載一個鉤子了,接下來我們將看看鉤子函數(shù)。.
只要您安裝的鉤子的消息事件類型發(fā)生,WINDOWS就將調(diào)用鉤子函數(shù)。譬如您安裝的鉤子是WH_MOUSE類型,那么只要有一個鼠標(biāo)事件發(fā)生時,該鉤子函數(shù)就會被調(diào)用。不管您安裝的時那一類型鉤子,鉤子函數(shù)的原型都時是一樣的:
HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD
nCode 指定是否需要處理該消息
wParam 和 lParam 包含該消息的附加消息
HookProc 可以看作是一個函數(shù)名的占位符。只要函數(shù)的原型一致,您可以給該函數(shù)取任何名字。至于以上的幾個參數(shù)及返回值的具體含義各種類型的鉤子都不相同。譬如:
WH_CALLWNDPROC
nCode 只能是HC_ACTION,它代表有一個消息發(fā)送給了一個窗口
wParam 如果非0,代表正被發(fā)送的消息
lParam 指向CWPSTRUCT型結(jié)構(gòu)體變量的指針
return value: 未使用,返回0
WH_MOUSE
nCode 為HC_ACTION 或 HC_NOREMOVE
wParam 包含鼠標(biāo)的事件消息
lParam 指向MOUSEHOOKSTRUCT型結(jié)構(gòu)體變量的指針
return value: 如果不處理返回0,否則返回非0值
所以您必須查詢您的WIN32 API 指南來得到不同類型的鉤子的參數(shù)的詳細(xì)定義以及它們返回值的意義。這里還有一個問題需要注意:所有的鉤子都串在一個鏈表上,近加入的鉤子放在鏈表的頭部。當(dāng)一個事件發(fā)生時,WINDOWS將按照從鏈表頭到鏈表尾調(diào)用的順序。所以您的鉤子函數(shù)有責(zé)任把消息傳到下一個鏈中的鉤子函數(shù)。當(dāng)然您可以不這樣做,但是您好明白這時這么做的原因。在大多數(shù)的情況下,好把消息事件傳遞下去以便其它的鉤子都有機會獲得處理這一消息的機會。調(diào)用下一個鉤子函數(shù)可以調(diào)用函數(shù)CallNextHookEx。該函數(shù)的原型如下:
CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD
hHook 時是您自己的鉤子函數(shù)的句柄。利用該句柄可以遍歷鉤子鏈。
nCode, wParam and lParam 您只要把傳入的參數(shù)簡單傳給CallNextHookEx即可。
請注意:對于遠(yuǎn)程鉤子,鉤子函數(shù)必須放到DLL中,它們將從DLL中映射到其它的進程空間中去。當(dāng)WINDOWS映射DLL到其它的進程空間中去時,不會把數(shù)據(jù)段也進行映射。簡言之,所有的進程僅共享DLL的代碼,至于數(shù)據(jù)段,每一個進程都將有其單獨的拷貝。這是一個很容易被忽視的問題。您可能想當(dāng)然的以為,在DLL中保存的值可以在所有映射該DLL的進程之間共享。在通常情況下,由于每一個映射該DLL的進程都有自己的數(shù)據(jù)段,所以在大多數(shù)的情況下您的程序運行得都不錯。但是鉤子函數(shù)卻不是如此。對于鉤子函數(shù)來說,要求DLL的數(shù)據(jù)段對所有的進程也必須相同。這樣您就必須把數(shù)據(jù)段設(shè)成共享的,這可以通過在鏈接開關(guān)中指定段的屬性來實現(xiàn)。在MASM中您可以這么做:
/SECTION:
已初期化的段名是.data,未初始化的段名是.bss。`加入您想要寫一個包含鉤子函數(shù)的DLL,而且想使它的未初始化的數(shù)據(jù)段在所有進程間共享,您必須這么做:
link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS ..........
S 代表該段是共享段。例子:
一共有兩個模塊:一個是GUI部分,另一個是安裝和卸載鉤子的DLL。
;--------------------------------------------- 主程序的源代碼部分--------------------------------------
.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
.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_MOST, 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
;----------------------------------------------------- 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
;---------------------------------------------- DLL的Makefile文件 ----------------------------------------------
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
分析:
該應(yīng)用程序的主窗口中包括三個編輯控件,它們將分別顯示當(dāng)前鼠標(biāo)光標(biāo)所在位置的窗口類名、窗口句柄和窗口過程的地址。還有兩個按鈕:“Hook”和“Eixt”。當(dāng)您按下Hook時,應(yīng)用程序?qū)^掛鼠標(biāo)輸入的事件消息,該按鈕的文本將變成“Unhook”。當(dāng)您把鼠標(biāo)關(guān)標(biāo)滑過一個窗口時,該窗口的有關(guān)消息將顯示在主窗口中。當(dāng)您按下“Unhook”時,應(yīng)用程序?qū)⑿遁d鉤子。 主窗口使用一個對話框來作為它的主窗口。它自定義了一個消息WM_MOUSEHOOK,用來在主窗口和DLL之間傳遞消息。當(dāng)主窗口接收到該消息時,wParam中包含了光標(biāo)所在位置的窗口的句柄。當(dāng)然這是我們做的安排。我這么做只是為了方便。您可以使用您自己的方法在主應(yīng)用程序和DLL之間進行通訊。
.if HookFlag==FALSE
invoke InstallHook,hDlg
.if eax!=NULL
mov HookFlag,TRUE
invoke SetDlgItemText,hDlg,IDC_HOOK,addr UnhookText
.endif
該應(yīng)用程序有一個全局變量,HookFlag,它用來監(jiān)視鉤子的狀態(tài)。如果安裝來鉤子它就是TRUE,否則是FALSE。 當(dāng)用戶按下Hook按鈕時,應(yīng)用程序檢查鉤子是否已經(jīng)安裝。如果還沒有的話,它將調(diào)用DLL中引出的函數(shù)InstallHook來安裝它。注意我們把主對話框的句柄傳遞給了DLL,這樣這個鉤子DLL就可以把WM_MOUSEHOOK消息傳遞給正確的窗口了。當(dāng)應(yīng)用程序加載時,鉤子DLL也同時加載。時機上當(dāng)主程序一旦加載到內(nèi)存中后,DLL就立即加載。DLL的入口點函數(shù)載主程序的第一條語句執(zhí)行前就前執(zhí)行了。所以當(dāng)主程序執(zhí)行時,DLL已經(jīng)初始化好了。我們載入口點處放入如下代碼:
.if reason==DLL_PROCESS_ATTACH
push hInst
pop hInstance
.endif
該段代碼把DLL自己的實例句柄放到一個全局變量中保存。由于入口點函數(shù)是在所有函數(shù)調(diào)用前被執(zhí)行的,所以hInstance總是有效的。我們把該變量放到.data中,使得每一個進程都有自己一個該變量的值。因為當(dāng)鼠標(biāo)光標(biāo)停在一個窗口上時,鉤子DLL被映射進進程的地址空間。加入在DLL缺省加載的地址處已經(jīng)加載其它的DLL,那鉤子DLL將要被映射到其他的地址。hInstance將被更新成其它的值。當(dāng)用戶按下Unhook再按下Hook時,SetWindowsHookEx將被再次調(diào)用。這,它將把新的地址作為實例句柄。而在例子中這是錯誤的,DLL裝載的地址并沒有變。這個鉤子將變成一個局部的,您只能鉤掛發(fā)生在您窗口中的鼠標(biāo)事件,這是很難讓人滿意的 。
InstallHook proc hwnd:DWORD
push hwnd
pop hWnd
invoke SetWindowsHookEx,WH_MOUSE,addr MouseProc,hInstance,NULL
mov hHook,eax
ret
InstallHook endp
InstallHook 函數(shù)非常簡單。它把傳遞過來的窗口句柄保存在hWnd中以備后用。接著調(diào)用SetWindowsHookEx函數(shù)來安裝一個鼠標(biāo)鉤子。該函數(shù)的返回值放在全局變量hHook中,將來在UnhookWindowsHookEx中還要使用。在調(diào)用SetWindowsHookEx后,鼠標(biāo)鉤子就開始工作了。無論什么時候發(fā)生了鼠標(biāo)事件,MouseProc函數(shù)都將被調(diào)用:
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
鉤子函數(shù)首先調(diào)用CallNextHookEx函數(shù)讓其它的鉤子處理該鼠標(biāo)事件。然后,調(diào)用WindowFromPoint函數(shù)來得到給定屏幕坐標(biāo)位置處的窗口句柄。注意:我們用lParam指向的MOUSEHOOKSTRUCT型結(jié)構(gòu)體變量中的POINT成員變量作為當(dāng)前的鼠標(biāo)位置。在我們調(diào)用PostMessage函數(shù)把WM_MOUSEHOOK消息發(fā)送到主程序。您必須記住的一件事是:在鉤子函數(shù)中不要使用SendMessage函數(shù),它會引起死鎖。MOUSEHOOKSTRUCT的定義如下:
MOUSEHOOKSTRUCT STRUCT DWORD
pt POINT <>
hwnd DWORD ?
wHitTestCode DWORD ?
dwExtraInfo DWORD ?
MOUSEHOOKSTRUCT ENDS
pt 是當(dāng)前鼠標(biāo)所在的屏幕位置。
hwnd 是將接收鼠標(biāo)消息的窗口的句柄。通常它是鼠標(biāo)所在處的窗口,但是如果窗口調(diào)用了SetCapture,鼠標(biāo)的輸入將到向到這個窗口。因我們不用該成員變量而是用WindowFromPoint函數(shù)。
wHitTestCode 指定hit-test值,該值給出了更多的鼠標(biāo)位置值。它指定了鼠標(biāo)在窗口的那個部位。該值的完全列表,請參考WIN32 API 指南中的WM_NCHITTEST消息。
dwExtraInfo 該值包含了相關(guān)的信息。一般該值由mouse_event函數(shù)設(shè)定,可以調(diào)用GetMessageExtraInfo來獲得。
當(dāng)主窗口接收到WM_MOUSEHOOK 消息時,它用wParam參數(shù)中的窗口句柄來查詢窗口的消息。
.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
為了避免重繪文本時的抖動,我們把已經(jīng)在編輯空間中線時的文本和我們將要顯示的對比。如果相同,就可以忽略掉。得到類名調(diào)用GetClassName,得到窗口過程調(diào)用GetClassLong并傳入GCL_WNDPROC標(biāo)志,然后把它們格式化成文本串并放到相關(guān)的編輯空間中去。
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
當(dāng)用戶按下Unhook后,主程序調(diào)用DLL中的UninstallHook函數(shù)。該函數(shù)調(diào)用UnhookWindowsHookEx函數(shù)。然后,它把按鈕的文本換回“Hook”,HookFlag的值設(shè)成FALSE再清除掉編輯控件中的文本。
鏈接器的開關(guān)選項如下:
Link /SECTION:.bss,S /DLL /DEF:$(NAME).def /SUBSYSTEM:WINDOWS
它指定.bss段作為一個共享段以便所有映射該DLL的進程共享未初始化的數(shù)據(jù)段。如果不用該開關(guān),您DLL中的鉤子就不能正常工作了。