《windows核心編程系列》十八談談windows鉤子

來源:互聯網
上載者:User

標籤:style   http   io   os   使用   ar   for   strong   檔案   

 

       windows應用程式是基於訊息驅動的。各種應用程式對各種訊息作出響應從而實現各種功能。

      windows鉤子是windows訊息處理機制的一個監視點,通過安裝鉤子能夠達到監視指定表單某種類型的訊息的功能。所謂的指定表單並不局限於當前進程的表單,也能夠是其它進程的表單。當監視的某一訊息到達指定的表單時,在指定的表單處理訊息之前,鉤子函數將截獲此訊息,鉤子函數既能夠加工處理該訊息,也能夠不作不論什麼處理繼續傳遞該訊息。使用鉤子是實現dll注入的方法之中的一個。其它經常使用的方法有:注冊表注入,遠程線程注入。

       鉤子函數是一個處理訊息的程式段。是在安裝鉤子的時候向系統注冊的。

關於windows鉤子要清楚下面三點:

     1:鉤子是用來截獲系統中的訊息流程的。利用鉤子能夠處理不論什麼我們感興趣的訊息,當然包含其它進程的訊息。

    2:截獲該訊息後,用於處理該訊息的程式叫做鉤子函數。它是自己定義的函數,在安裝鉤子時將此函數的地址告訴windows。

3:系統同一時間可能有多個進程安裝鉤子,多個鉤子構成鉤子鏈。所以截獲訊息並處理後,應該將此訊息繼續傳遞下去,以便其它鉤子處理這一訊息。

      注意:使用鉤子會使系統變慢,由於它添加?了系統對每一個訊息的處理量。所以要僅在必要的時候才安裝鉤子。不須要時要及時卸載。

  安裝鉤子:

  1:

SetWindowsHookEx(         int idHook,                  //要安裝的鉤子的類型。         HOOKPROC lpfn,                  //鉤子函數的地址。         HINSTANCE hMode,               //鉤子函數所在DLL在進程內的地址。         DWORD     dwThread,            //要安裝鉤子的線程。如為0,則為全部線程安裝鉤子。         );


idHook指定要安裝鉤子的類型,他能夠是以下的值:

     WH_CALLWNDPROC           //目標線程調用SendMessage發送訊息時,鉤子函數被調用。

     WH_CALLWNDPROCRET   //當SendMessage返回時,鉤子函數被調用。

     WH_KEYBOARD                 //從訊息佇列中查詢WM_KEYUP或WM_KEYDOWN時。

     WH_GETMESSAGE           //目標線程調用GetMessage或PeekMessage時

     WH_MOUSE                      //查詢訊息佇列中滑鼠事件訊息時。

     WH_MSGFILTER              //下面請參考MSDN。

     WH_SYSMSGFILTER

     WH_JORNALRECORD

     WHJORNALPLAYBACK

     WH_SHELL

     WH_CBT

     WH_FOREGROUNDIDLE

     WH_DEBUG

    2 :

     lpfn是鉤子函數的地址。鉤子安裝後假設有對應的訊息發生,windows將調用此參數指向的函數。一般鉤子函數都是位於一個DLL中。當為其它進程內的線程安裝鉤子時,假設鉤子函數在DLL中,系統會把DLL映射到那個進程內,使他能在該進程內被調用。

     注意:鉤子函數多是被其它進程內的線程調用,而不一定是安裝鉤子的線程。

鉤子函數被調用的過程

      當進程A一個線程準備向一個表單發送一個訊息,系統檢查該線程是否被安裝了鉤子,假設該線程被安裝了鉤子且該訊息與鉤子要截獲的訊息類型一致,此訊息將被截獲。系統檢查該鉤子的鉤子函數所在的DLL是否已經被映射進程A的地址空間中。假設尚未映射,系統會強制將該DLL映射到進程A的地址空間。然後獲得鉤子函數在進程A的虛擬位址,並調用鉤子函數。我們能夠在鉤子函數內定義我們對該訊息處理的過程。

      注意:當系統把鉤子函數所在的DLL映射到某個進程地址空間時,會映射整個DLL,而不不過鉤子函數,這也就說我們能夠使用該DLL中的全部匯出函數。

     3:hmod參數是鉤子函數所在dll的執行個體控制代碼,也是該dll在進程內的虛擬位址。假設鉤子函數在當前進程中,此參數應被指定為NULL.

    4:dwThreadid指定要被安裝鉤子的線程的ID號。假設被設為0,就會為系統內的全部GUI線程安裝鉤子。

   5:鉤子函數

   鉤子被安裝後,假設有對應的訊息發生,windows將調用鉤子函數。下面為鉤子函數的原型:

  

  LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam)   {     //處理訊息的代碼。      return CallNextHookEx(hHook,nCode,wParam,lParam);   }


 

    HookProc為鉤子函數的名稱。

    nCode指定是否必須處理該訊息。假設它為HC_ACTION,那麼鉤子函數就必須處理該訊息。假設小於0,鉤子函數就必須將該訊息傳遞給CallNextHookEx,不正確該訊息進行處理,並返回CallNextHookEx的返回值。

    CallNextHookEx用於把訊息傳遞到鉤子鏈中下一個鉤子函數。

    wParam和lParam的值依賴於詳細的鉤子類型。請參考MSDN。 

    卸載鉤子

     BOOL UnhookWindowsHookEx(HHOOK hhk);

     hhk為要卸載的鉤子控制代碼。

 

     以下將要實現一個範例,實現對鍵盤按鍵的監控。一旦有鍵盤被按下,就在主程式表單顯示一條資訊指示哪一個鍵被按下。

    程式外觀:

 

    首先要實現DLL:

     在dll內實現鉤子函數這是毫無疑問的。而安裝鉤子和卸載鉤子的函數既能夠寫在主程式內,也能夠寫在DLL內。寫在主程式內時僅僅能夠在主程式內安裝鉤子。而在dll內實現則能夠讓全部載入該dll的程式安裝鉤子。如當某進程將該DLL載入的時候,能夠在DllMain中建立一個線程,讓他調用安裝鉤子的函數,實現為此進程內的線程安裝鉤子的目的。為了拓展程式的功能,實現代碼重用,最好是將鉤子函數寫在DLL內。另外這也能夠實現模組化。一旦需求發生更改能夠僅僅改動DLL內的代碼,而不須要改變主程式。

      當鉤子函數被調用的時候,也就是我們被攔截的訊息已被觸發,怎樣讓主程式得到這個通知呢 ?

      我們能夠在其它進程內的鉤子函數內給主程式的表單發送訊息。但怎樣發送呢?

      PostMessage能夠實現這個功能。

      看原型:

    BOOL WINAPI PostMessage(HWND hWnd,UINT Msg,WPARAM wparam,LPARAM lParam);


     hWnd即為要接受訊息的表單控制代碼。

     Msg為要發送的訊息。

    wParam和lParam為訊息的附加參數。

    儘管能夠使用PostMessage實現向主程式的表單發送訊息,可是我們怎樣獲得主程式的表單控制代碼呢?我們知道鉤子函數是在DLL內實現的,而DLL會被載入到各個進程內。在其它進程要想得到主程式的表單控制代碼這是一個問題。

    在《windows核心編程系列》談談記憶體對應檔裡,我們談到了在可運行檔案內使用共用段,能夠實現同一個可運行檔案的多個執行個體共用共用段內的資料的目的。那麼在DLL使用共用段呢?哈哈,也許你已經猜出來了,因為DLL被映射到了各個進程,將資料放在DLL的共用段,能夠實如今各個進程內共用DLL內共用段資料的目的。

     我們的解決方案就是:在DLL內建立共用段,將主程式的表單控制代碼放在共用段中。在主程式調用安裝鉤子的函數時能夠將共用段內的表單控制代碼賦為主程式的表單控制代碼。從而達到在各個進程內共用資料的目的。到此,我們又學習一種在進程間共用資料的方法,還有一種方法是利用記憶體對應檔。

建立和設定共用段的代碼:能夠參考《windows核心編程》談談記憶體對應檔。


 

 #pragma data_seg("shared")  HWND hWnd=NULL;  HHOOK hHook=NULL; #pragma data_seg()#pragma comment(linker,"/SECTION:shared,RWS")


 

     怎麼多了個hHook,hHook是建立的鉤子的控制代碼。因為在鉤子函數中會調用CallNextHookEx將訊息傳給鉤子鏈的下一結點。二者都是在其它進程調用的,因此我們也必須把鉤子的控制代碼設為共用。

 

     DLL內建立鉤子的代碼:

    KEYHOOKDLL_API bool SetHook(
                            bool IsInstall,//true表示安裝鉤子,false表示卸載鉤子。
                            HWND hWnd,     //主程式表單控制代碼,用於在主程式內傳入設定。
                              int ThreadId)//要安裝鉤子的線程。   {::hWnd=hWnd;//將當前表單控制代碼賦給DLL共線段內的表單控制代碼。if(IsInstall){hHook=SetWindowsHookEx( WH_KEYBOARD,KeyHookProc,GetModuleHandle  
                                           ("keyhookdll"),ThreadId);return true;}else{UnhookWindowsHookEx(hHook);return true;}}

      建立的鉤子類型為WH_KEYBOARD,他能夠攔截WM_KEYDOWNWM_KEYUP 訊息。詳細請參考MSDN.

建立鉤子函數功能非常easy,只安裝鉤子和設定共用段內的資料。Thread為要安裝鉤子的線程。主程式在調用時傳入0,表示為全部線程安裝鉤子。

 

   再看鉤子函數:

LRESULT CALLBACK KeyHookProc(int nCode ,WPARAM wParam,LPARAM lParam){if(nCode<0||nCode==HC_NOREMOVE){return CallNextHookEx(hHook,nCode,wParam,lParam);}if(lParam&0x40000000)//僅僅對WM_DOWN進行響應。      {        PostMessage(hWnd,WM_KEYDOWN,wParam,lParam);      }
        return CallNextHookEx(hHook,nCode,wParam,lParam);}

   在鉤子函數中首先推斷nCode的值,當他小於零時應該直調用CallNextHookEx,除此之外它也能夠有下面取值:

    ACTION:說明wParam和lParam包括按鍵訊息的資訊,能夠處理。

    HC_NOREMOVE:說明wParam和lParam包括按鍵訊息的資訊,但該訊息沒有被從訊息佇列中移除。即程式是調用PeekMessage來查詢訊息佇列內的訊息的。

     ( 與GetMessage的差別與聯絡:他們都從訊息佇列內查詢訊息,有訊息時將此訊息發送出去,GetMessage在訊息佇列沒有訊息時會一直等待,直到有訊息到達時才返回。而PeekMessage不管訊息佇列中是否有訊息都馬上返回。)

     因此當檢測到nCode小於0或者為WH_NOREMOVE時不能對訊息進行處理而要直接調用CallNextHookEx。lParam的第30位為1時說明此時鍵被按下,為零時說明鍵被彈起。此處進行了推斷,僅在鍵被按下時向表單發送訊息。防止訊息每次擊鍵發送兩次訊息。

 

     當某訊息到達時我們給主程式表單發送的訊息為使用者自己定義訊息:WM_KEY
     他被定義為#define WM_KEY  WM_USER+1

     在主程式內我們必須自己實現對應此訊息的訊息處理函數。

     原型為:

    afx_msg LRESULT OnKey(WPARAM wParam,LPARAM lParam);

   實現:

      char keyname[100];::GetKeyNameText(lParam,keyname,100);//獲得按鍵的鍵名。CString a;a.Format("使用者按鍵:%s\r\n",keyname);m_output+=a;UpdateData(false);::MessageBeep(MB_OK);CEdit *edit=(CEdit*)GetDlgItem(IDC_EDIT_OUTPUT);edit->LineScroll(edit->GetLineCount());return 0;

    到此為止各主要函數都介紹完成,剩下都是怎樣建立dll。此處不再介紹。範例程式2011年12月2日下午實現。

 

總結:以上程式花了近三個小時實現,此程式看似easy但一旦自己動手實現各種問題接踵而至。所以以後要常常動手實現一些看似easy的程式,不要眼高手低。打這些字的時候鍵盤監控程式仍在工作,顯示著我按下的每個鍵。有明顯的電腦感覺速度比尋常慢了不少,看來使用鉤子,尤其是系統範圍內的鉤子會導致非常大的overhead。

    windows核心編程中談到注入dll的幾種方式。當中介紹了使用windows鉤子,可是介紹的非常easy。以上內容參考自《windows核心編程》第五版,第四部分和《windows程式設計》第二版,王豔平著。如有錯誤,請指正。

《windows核心編程系列》十八談談windows鉤子

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.