|
windows應(yīng)用程序是基于消息驅(qū)動(dòng)的。各種應(yīng)用程序?qū)Ω鞣N消息作出響應(yīng)從而實(shí)現(xiàn)各種功能。 windows鉤子是windows消息處理機(jī)制的一個(gè)監(jiān)視點(diǎn),通過安裝鉤子可以達(dá)到監(jiān)視指定窗口某種類型的消息的功能。所謂的指定窗口并不局限于當(dāng)前進(jìn)程的窗口,也可以是其他進(jìn)程的窗口。當(dāng)監(jiān)視的某一消息到達(dá)指定的窗口時(shí),在指定的窗口處理消息之前,鉤子函數(shù)將截獲此消息,鉤子函數(shù)既可以加工處理該消息,也可以不作任何處理繼續(xù)傳遞該消息。使用鉤子是實(shí)現(xiàn)dll注入的方法之一。其他常用的方法有:注冊(cè)表注入,遠(yuǎn)程線程注入。 鉤子函數(shù)是一個(gè)處理消息的程序段。是在安裝鉤子的時(shí)候向系統(tǒng)注冊(cè)的。 關(guān)于windows鉤子要清楚以下三點(diǎn): 1:鉤子是用來截獲系統(tǒng)中的消息流的。利用鉤子可以處理任何我們感興趣的消息,當(dāng)然包括其他進(jìn)程的消息。 2:截獲該消息后,用于處理該消息的程序叫做鉤子函數(shù)。它是自定義的函數(shù),在安裝鉤子時(shí)將此函數(shù)的地址告訴windows。 3:系統(tǒng)同一時(shí)間可能有多個(gè)進(jìn)程安裝鉤子,多個(gè)鉤子構(gòu)成鉤子鏈。所以截獲消息并處理后,應(yīng)該將此消息繼續(xù)傳遞下去,以便其他鉤子處理這一消息。 注意:使用鉤子會(huì)使系統(tǒng)變慢,因?yàn)樗黾恿讼到y(tǒng)對(duì)每個(gè)消息的處理量。所以要僅在必要的時(shí)候才安裝鉤子。不需要時(shí)要及時(shí)卸載。 安裝鉤子: 1: [cpp] view plaincopy
WH_CALLWNDPROC //目標(biāo)線程調(diào)用SendMessage發(fā)送消息時(shí),鉤子函數(shù)被調(diào)用。 WH_CALLWNDPROCRET //當(dāng)SendMessage返回時(shí),鉤子函數(shù)被調(diào)用。 WH_KEYBOARD //從消息隊(duì)列中查詢WM_KEYUP或WM_KEYDOWN時(shí)。 WH_GETMESSAGE //目標(biāo)線程調(diào)用GetMessage或PeekMessage時(shí) WH_MOUSE //查詢消息隊(duì)列中鼠標(biāo)事件消息時(shí)。 WH_MSGFILTER //以下請(qǐng)參考MSDN。 WH_SYSMSGFILTER WH_JORNALRECORD WHJORNALPLAYBACK WH_SHELL WH_CBT WH_FOREGROUNDIDLE WH_DEBUG 2 : lpfn是鉤子函數(shù)的地址。鉤子安裝后如果有相應(yīng)的消息發(fā)生,windows將調(diào)用此參數(shù)指向的函數(shù)。一般鉤子函數(shù)都是位于一個(gè)DLL中。當(dāng)為其他進(jìn)程內(nèi)的線程安裝鉤子時(shí),如果鉤子函數(shù)在DLL中,系統(tǒng)會(huì)把DLL映射到那個(gè)進(jìn)程內(nèi),使他能在該進(jìn)程內(nèi)被調(diào)用。 注意:鉤子函數(shù)多是被其他進(jìn)程內(nèi)的線程調(diào)用,而不一定是安裝鉤子的線程。 鉤子函數(shù)被調(diào)用的過程: 當(dāng)進(jìn)程A一個(gè)線程準(zhǔn)備向一個(gè)窗口發(fā)送一個(gè)消息,系統(tǒng)檢查該線程是否被安裝了鉤子,如果該線程被安裝了鉤子且該消息與鉤子要截獲的消息類型一致,此消息將被截獲。系統(tǒng)檢查該鉤子的鉤子函數(shù)所在的DLL是否已經(jīng)被映射進(jìn)程A的地址空間中。如果尚未映射,系統(tǒng)會(huì)強(qiáng)制將該DLL映射到進(jìn)程A的地址空間。然后獲得鉤子函數(shù)在進(jìn)程A的虛擬地址,并調(diào)用鉤子函數(shù)。我們可以在鉤子函數(shù)內(nèi)定義我們對(duì)該消息處理的過程。 注意:當(dāng)系統(tǒng)把鉤子函數(shù)所在的DLL映射到某個(gè)進(jìn)程地址空間時(shí),會(huì)映射整個(gè)DLL,而不僅僅是鉤子函數(shù),這也就說我們可以使用該DLL中的所有導(dǎo)出函數(shù)。 3:hmod參數(shù)是鉤子函數(shù)所在dll的實(shí)例句柄,也是該dll在進(jìn)程內(nèi)的虛擬地址。如果鉤子函數(shù)在當(dāng)前進(jìn)程中,此參數(shù)應(yīng)被指定為NULL. 4:dwThreadid指定要被安裝鉤子的線程的ID號(hào)。如果被設(shè)為0,就會(huì)為系統(tǒng)內(nèi)的所有GUI線程安裝鉤子。 5:鉤子函數(shù) 鉤子被安裝后,如果有相應(yīng)的消息發(fā)生,windows將調(diào)用鉤子函數(shù)。以下為鉤子函數(shù)的原型:
[cpp] view plaincopy
HookProc為鉤子函數(shù)的名稱。 nCode指定是否必須處理該消息。如果它為HC_ACTION,那么鉤子函數(shù)就必須處理該消息。如果小于0,鉤子函數(shù)就必須將該消息傳遞給CallNextHookEx,不對(duì)該消息進(jìn)行處理,并返回CallNextHookEx的返回值。 CallNextHookEx用于把消息傳遞到鉤子鏈中下一個(gè)鉤子函數(shù)。 wParam和lParam的值依賴于具體的鉤子類型。請(qǐng)參考MSDN。 卸載鉤子。 BOOL UnhookWindowsHookEx(HHOOK hhk); hhk為要卸載的鉤子句柄。
下面將要實(shí)現(xiàn)一個(gè)例子,實(shí)現(xiàn)對(duì)鍵盤按鍵的監(jiān)控。一旦有鍵盤被按下,就在主程序窗口顯示一條信息指示哪一個(gè)鍵被按下。 程序外觀:
首先要實(shí)現(xiàn)DLL: 在dll內(nèi)實(shí)現(xiàn)鉤子函數(shù)這是毫無疑問的。而安裝鉤子和卸載鉤子的函數(shù)既可以寫在主程序內(nèi),也可以寫在DLL內(nèi)。寫在主程序內(nèi)時(shí)只可以在主程序內(nèi)安裝鉤子。而在dll內(nèi)實(shí)現(xiàn)則可以讓所有載入該dll的程序安裝鉤子。如當(dāng)某進(jìn)程將該DLL載入的時(shí)候,可以在DllMain中創(chuàng)建一個(gè)線程,讓他調(diào)用安裝鉤子的函數(shù),實(shí)現(xiàn)為此進(jìn)程內(nèi)的線程安裝鉤子的目的。為了拓展程序的功能,實(shí)現(xiàn)代碼重用,最好是將鉤子函數(shù)寫在DLL內(nèi)。另外這也可以實(shí)現(xiàn)模塊化。一旦需求發(fā)生更改可以只修改DLL內(nèi)的代碼,而不需要改變主程序。 當(dāng)鉤子函數(shù)被調(diào)用的時(shí)候,也就是我們被攔截的消息已被觸發(fā),如何讓主程序得到這個(gè)通知呢 ? 我們可以在其他進(jìn)程內(nèi)的鉤子函數(shù)內(nèi)給主程序的窗口發(fā)送消息。但如何發(fā)送呢? PostMessage可以實(shí)現(xiàn)這個(gè)功能。 看原型: [cpp] view plaincopy
Msg為要發(fā)送的消息。 wParam和lParam為消息的附加參數(shù)。 雖然可以使用PostMessage實(shí)現(xiàn)向主程序的窗口發(fā)送消息,但是我們?nèi)绾潍@得主程序的窗口句柄呢?我們知道鉤子函數(shù)是在DLL內(nèi)實(shí)現(xiàn)的,而DLL會(huì)被加載到各個(gè)進(jìn)程內(nèi)。在其他進(jìn)程要想得到主程序的窗口句柄這是一個(gè)問題。 在《windows核心編程系列》談?wù)剝?nèi)存映射文件中,我們談到了在可執(zhí)行文件內(nèi)使用共享段,可以實(shí)現(xiàn)同一個(gè)可執(zhí)行文件的多個(gè)實(shí)例共享共享段內(nèi)的數(shù)據(jù)的目的。那么在DLL使用共享段呢?哈哈,或許你已經(jīng)猜出來了,由于DLL被映射到了各個(gè)進(jìn)程,將數(shù)據(jù)放在DLL的共享段,可以實(shí)現(xiàn)在各個(gè)進(jìn)程內(nèi)共享DLL內(nèi)共享段數(shù)據(jù)的目的。 我們的解決方法就是:在DLL內(nèi)建立共享段,將主程序的窗口句柄放在共享段中。在主程序調(diào)用安裝鉤子的函數(shù)時(shí)可以將共享段內(nèi)的窗口句柄賦為主程序的窗口句柄。從而達(dá)到在各個(gè)進(jìn)程內(nèi)共享數(shù)據(jù)的目的。到此,我們又學(xué)習(xí)一種在進(jìn)程間共享數(shù)據(jù)的方法,另一種方法是利用內(nèi)存映射文件。 建立和設(shè)置共享段的代碼:可以參考《windows核心編程》談?wù)剝?nèi)存映射文件。
[cpp] view plaincopy
怎么多了個(gè)hHook,hHook是創(chuàng)建的鉤子的句柄。由于在鉤子函數(shù)中會(huì)調(diào)用CallNextHookEx將消息傳給鉤子鏈的下一結(jié)點(diǎn)。二者都是在其他進(jìn)程調(diào)用的,因此我們也必須把鉤子的句柄設(shè)為共享。
DLL內(nèi)創(chuàng)建鉤子的代碼: [cpp] view plaincopy
[cpp] view plaincopy
[cpp] view plaincopy
[cpp] view plaincopy
[cpp] view plaincopy
創(chuàng)建的鉤子類型為WH_KEYBOARD,他可以攔截WM_KEYDOWN 和WM_KEYUP 消息。具體請(qǐng)參考MSDN. 創(chuàng)建鉤子函數(shù)功能很簡(jiǎn)單,僅僅安裝鉤子和設(shè)置共享段內(nèi)的數(shù)據(jù)。Thread為要安裝鉤子的線程。主程序在調(diào)用時(shí)傳入0,表示為所有線程安裝鉤子。
再看鉤子函數(shù): [cpp] view plaincopy
[cpp] view plaincopy
在鉤子函數(shù)中首先判斷nCode的值,當(dāng)他小于零時(shí)應(yīng)該直調(diào)用CallNextHookEx,除此之外它也可以有以下取值: ACTION:說明wParam和lParam包含按鍵消息的信息,可以處理。 HC_NOREMOVE:說明wParam和lParam包含按鍵消息的信息,但該消息沒有被從消息隊(duì)列中移除。即程序是調(diào)用PeekMessage來查詢消息隊(duì)列內(nèi)的消息的。 ( 與GetMessage的區(qū)別與聯(lián)系:他們都從消息隊(duì)列內(nèi)查詢消息,有消息時(shí)將此消息發(fā)送出去,GetMessage在消息隊(duì)列沒有消息時(shí)會(huì)一直等待,直到有消息到達(dá)時(shí)才返回。而PeekMessage無論消息隊(duì)列中是否有消息都立即返回。) 因此當(dāng)檢測(cè)到nCode小于0或者為WH_NOREMOVE時(shí)不能對(duì)消息進(jìn)行處理而要直接調(diào)用CallNextHookEx。lParam的第30位為1時(shí)說明此時(shí)鍵被按下,為零時(shí)說明鍵被彈起。此處進(jìn)行了判斷,僅在鍵被按下時(shí)向窗口發(fā)送消息。防止消息每次擊鍵發(fā)送兩次消息。
當(dāng)某消息到達(dá)時(shí)我們給主程序窗口發(fā)送的消息為用戶自定義消息:WM_KEY 在主程序內(nèi)我們必須自己實(shí)現(xiàn)相應(yīng)此消息的消息處理函數(shù)。 原型為: [cpp] view plaincopy
實(shí)現(xiàn): [cpp] view plaincopy
到此為止各主要函數(shù)都介紹完畢,剩下都是如何創(chuàng)建dll。此處不再介紹。例子程序2011年12月2日下午實(shí)現(xiàn)。
總結(jié):以上程序花了近三個(gè)小時(shí)實(shí)現(xiàn),此程序看似容易但一旦自己動(dòng)手實(shí)現(xiàn)各種問題接踵而至。所以以后要經(jīng)常動(dòng)手實(shí)現(xiàn)一些看似容易的程序,不要眼高手低。打這些字的時(shí)候鍵盤監(jiān)控程序仍在工作,顯示著我按下的每一個(gè)鍵。有明顯的電腦感覺速度比平常慢了不少,看來使用鉤子,尤其是系統(tǒng)范圍內(nèi)的鉤子會(huì)導(dǎo)致很大的overhead。 |
|
|