|
WINDOWS的鉤子函數(shù)可以認(rèn)為是WINDOWS的主要特性之一。利用它們,您可以捕捉您自己進(jìn)程或其它進(jìn)程發(fā)生的事件。通過“鉤掛”,您可以給WINDOWS一個(gè)處理或過濾事件的回調(diào)函數(shù),該函數(shù)也叫做“鉤子函數(shù)”,當(dāng)每次發(fā)生您感興趣的事件時(shí),WINDOWS都將調(diào)用該函數(shù)。一共有兩種類型的鉤子:局部的和遠(yuǎn)程的。 局部鉤子僅鉤掛您自己進(jìn)程的事件。 遠(yuǎn)程的鉤子還可以將鉤掛其它進(jìn)程發(fā)生的事件。遠(yuǎn)程的鉤子又有兩種: 基于線程的 它將捕獲其它進(jìn)程中某一特定線程的事件。簡(jiǎn)言之,就是可以用來觀察其它進(jìn)程中的某一特定線程將發(fā)生的事件。 系統(tǒng)范圍的 將捕捉系統(tǒng)中所有進(jìn)程將發(fā)生的事件消息。 安裝鉤子函數(shù)將會(huì)影響系統(tǒng)的性能。監(jiān)測(cè)“系統(tǒng)范圍事件”的系統(tǒng)鉤子特別明顯。因?yàn)橄到y(tǒng)在處理所有的相關(guān)事件時(shí)都將調(diào)用您的鉤子函數(shù),這樣您的系統(tǒng)將會(huì)明顯的減慢。所以應(yīng)謹(jǐn)慎使用,用完后立即卸載。還有,由于您可以預(yù)先截獲其它進(jìn)程的消息,所以一旦您的鉤子函數(shù)出了問題的話必將影響其它的進(jìn)程。記?。汗δ軓?qiáng)大也意味著使用時(shí)要負(fù)責(zé)任。 在正確使用鉤子函數(shù)前,我們先講解鉤子函數(shù)的工作原理。當(dāng)您創(chuàng)建一個(gè)鉤子時(shí),WINDOWS會(huì)先在內(nèi)存中創(chuàng)建一個(gè)數(shù)據(jù)結(jié)構(gòu),該數(shù)據(jù)結(jié)構(gòu)包含了鉤子的相關(guān)信息,然后把該結(jié)構(gòu)體加到已經(jīng)存在的鉤子鏈表中去。新的鉤子將加到老的前面。當(dāng)一個(gè)事件發(fā)生時(shí),如果您安裝的是一個(gè)局部鉤子,您進(jìn)程中的鉤子函數(shù)將被調(diào)用。如果是一個(gè)遠(yuǎn)程鉤子,系統(tǒng)就必須把鉤子函數(shù)插入到其它進(jìn)程的地址空間,要做到這一點(diǎn)要求鉤子函數(shù)必須在一個(gè)動(dòng)態(tài)鏈接庫中,所以如果您想要使用遠(yuǎn)程鉤子,就必須把該鉤子函數(shù)放到動(dòng)態(tài)鏈接庫中去。當(dāng)然有兩個(gè)例外:工作日志鉤子和工作日志回放鉤子。這兩個(gè)鉤子的鉤子函數(shù)必須在安裝鉤子的線程中。原因是:這兩個(gè)鉤子是用來監(jiān)控比較底層的硬件事件的,既然是記錄和回放,所有的事件就當(dāng)然都是有先后次序的。所以如果把回調(diào)函數(shù)放在DLL中,輸入的事件被放在幾個(gè)線程中記錄,所以我們無法保證得到正確的次序。故解決的辦法是:把鉤子函數(shù)放到單個(gè)的線程中,譬如安裝鉤子的線程。 鉤子一共有14種,以下是它們被調(diào)用的時(shí)機(jī): WH_CALLWNDPROC 當(dāng)調(diào)用SendMessage時(shí) WH_CALLWNDPROCRET 當(dāng)SendMessage的調(diào)用返回時(shí) WH_GETMESSAGE 當(dāng)調(diào)用GetMessage 或 PeekMessage時(shí) WH_KEYBOARD 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊(duì)列中查詢WM_KEYUP 或 WM_KEYDOWN 消息時(shí) WH_MOUSE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊(duì)列中查詢鼠標(biāo)事件消息時(shí) WH_HARDWARE 當(dāng)調(diào)用GetMessage 或 PeekMessage 來從消息隊(duì)列種查詢非鼠標(biāo)、鍵盤消息時(shí) WH_MSGFILTER 當(dāng)對(duì)話框、菜單或滾動(dòng)條要處理一個(gè)消息時(shí)。該鉤子是局部的。它時(shí)為那些有自己的消息處理過程的控件對(duì)象設(shè)計(jì)的。 WH_SYSMSGFILTER 和WH_MSGFILTER一樣,只不過是系統(tǒng)范圍的 WH_JOURNALRECORD 當(dāng)WINDOWS從硬件隊(duì)列中獲得消息時(shí) WH_JOURNALPLAYBACK 當(dāng)一個(gè)事件從系統(tǒng)的硬件輸入隊(duì)列中被請(qǐng)求時(shí) WH_SHELL 當(dāng)關(guān)于WINDOWS外殼事件發(fā)生時(shí),譬如任務(wù)條需要重畫它的按鈕. WH_CBT 當(dāng)基于計(jì)算機(jī)的訓(xùn)練(CBT)事件發(fā)生時(shí) WH_FOREGROUNDIDLE 由WINDOWS自己使用,一般的應(yīng)用程序很少使用 WH_DEBUG 用來給鉤子函數(shù)除錯(cuò) 現(xiàn)在我們知道了一些基本的理論,現(xiàn)在開始講解如何安裝/卸載一個(gè)鉤子。 要安裝一個(gè)鉤子,您可以調(diào)用SetWindowHookEx函數(shù)。該函數(shù)的原型如下: SetWindowsHookEx proto HookType:DWORD, pHookProc:DWORD, hInstance:DWORD, ThreadID:DWORD HookType 是我們上面列出的值之一,譬如: WH_MOUSE, WH_KEYBOARD pHookProc 是鉤子函數(shù)的地址。如果使用的是遠(yuǎn)程的鉤子,就必須放在一個(gè)DLL中,否則放在本身代碼中 hInstance 鉤子函數(shù)所在DLL的實(shí)例句柄。如果是一個(gè)局部的鉤子,該值為NULL ThreadID 是您安裝該鉤子函數(shù)后想監(jiān)控的線程的ID號(hào)。該參數(shù)可以決定該鉤子是局部的還是系統(tǒng)范圍的。如果該值為NULL,那么該鉤子將被解釋成系統(tǒng)范圍內(nèi)的,那它就可以監(jiān)控所有的進(jìn)程及它們的線程。如果您指定了您自己進(jìn)程中的某個(gè)線程ID 號(hào),那該鉤子是一個(gè)局部的鉤子。如果該線程ID是另一個(gè)進(jìn)程中某個(gè)線程的ID,那該鉤子是一個(gè)全局的遠(yuǎn)程鉤子。這里有兩個(gè)特殊情況:WH_JOURNALRECORD 和 WH_JOURNALPLAYBACK總是代表局部的系統(tǒng)范圍的鉤子,之所以說是局部,是因?yàn)樗鼈儧]有必要放到一個(gè)DLL中。WH_SYSMSGFILTER 總是一個(gè)系統(tǒng)范圍內(nèi)的遠(yuǎn)程鉤子。其實(shí)它和WH_MSGFILTER鉤子類似,如果把參數(shù)ThreadID設(shè)成0的話,它們就完全一樣了。 如果該函數(shù)調(diào)用成功的話,將在eax中返回鉤子的句柄,否則返回NULL。您必須保存該句柄,因?yàn)楹竺嫖覀冞€要它來卸載鉤子。 要卸載一個(gè)鉤子時(shí)調(diào)用UnhookWidowHookEx函數(shù),該函數(shù)僅有一個(gè)參數(shù),就是欲卸載的鉤子的句柄。如果調(diào)用成功的話,在eax中返回非0值,否則返回NULL。 現(xiàn)在您知道了如何安裝和卸載一個(gè)鉤子了,接下來我們將看看鉤子函數(shù)。. 只要您安裝的鉤子的消息事件類型發(fā)生,WINDOWS就將調(diào)用鉤子函數(shù)。譬如您安裝的鉤子是WH_MOUSE類型,那么只要有一個(gè)鼠標(biāo)事件發(fā)生時(shí),該鉤子函數(shù)就會(huì)被調(diào)用。不管您安裝的時(shí)那一類型鉤子,鉤子函數(shù)的原型都時(shí)是一樣的: HookProc proto nCode:DWORD, wParam:DWORD, lParam:DWORD nCode 指定是否需要處理該消息 wParam 和 lParam 包含該消息的附加消息 HookProc 可以看作是一個(gè)函數(shù)名的占位符。只要函數(shù)的原型一致,您可以給該函數(shù)取任何名字。至于以上的幾個(gè)參數(shù)及返回值的具體含義各種類型的鉤子都不相同。譬如: WH_CALLWNDPROC nCode 只能是HC_ACTION,它代表有一個(gè)消息發(fā)送給了一個(gè)窗口 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ì)定義以及它們返回值的意義。這里還有一個(gè)問題需要注意:所有的鉤子都串在一個(gè)鏈表上,最近加入的鉤子放在鏈表的頭部。當(dāng)一個(gè)事件發(fā)生時(shí),WINDOWS將按照從鏈表頭到鏈表尾調(diào)用的順序。所以您的鉤子函數(shù)有責(zé)任把消息傳到下一個(gè)鏈中的鉤子函數(shù)。當(dāng)然您可以不這樣做,但是您最好明白這時(shí)這么做的原因。在大多數(shù)的情況下,最好把消息事件傳遞下去以便其它的鉤子都有機(jī)會(huì)獲得處理這一消息的機(jī)會(huì)。調(diào)用下一個(gè)鉤子函數(shù)可以調(diào)用函數(shù)CallNextHookEx。該函數(shù)的原型如下: CallNextHookEx proto hHook:DWORD, nCode:DWORD, wParam:DWORD, lParam:DWORD hHook 時(shí)是您自己的鉤子函數(shù)的句柄。利用該句柄可以遍歷鉤子鏈。 nCode, wParam and lParam 您只要把傳入的參數(shù)簡(jiǎn)單傳給CallNextHookEx即可。 請(qǐng)注意:對(duì)于遠(yuǎn)程鉤子,鉤子函數(shù)必須放到DLL中,它們將從DLL中映射到其它的進(jìn)程空間中去。當(dāng)WINDOWS映射DLL到其它的進(jìn)程空間中去時(shí),不會(huì)把數(shù)據(jù)段也進(jìn)行映射。簡(jiǎn)言之,所有的進(jìn)程僅共享DLL的代碼,至于數(shù)據(jù)段,每一個(gè)進(jìn)程都將有其單獨(dú)的拷貝。這是一個(gè)很容易被忽視的問題。您可能想當(dāng)然的以為,在DLL中保存的值可以在所有映射該DLL的進(jìn)程之間共享。在通常情況下,由于每一個(gè)映射該DLL的進(jìn)程都有自己的數(shù)據(jù)段,所以在大多數(shù)的情況下您的程序運(yùn)行得都不錯(cuò)。但是鉤子函數(shù)卻不是如此。對(duì)于鉤子函數(shù)來說,要求DLL的數(shù)據(jù)段對(duì)所有的進(jìn)程也必須相同。這樣您就必須把數(shù)據(jù)段設(shè)成共享的,這可以通過在鏈接開關(guān)中指定段的屬性來實(shí)現(xiàn)。在MASM中您可以這么做: /SECTION:<section name>, S 已初期化的段名是.data,未初始化的段名是.bss。`加入您想要寫一個(gè)包含鉤子函數(shù)的DLL,而且想使它的未初始化的數(shù)據(jù)段在所有進(jìn)程間共享,您必須這么做: link /section:.bss,S /DLL /SUBSYSTEM:WINDOWS .......... S 代表該段是共享段。 |
|
|