天天看點

遊戲外挂技術(一)-- HOOK基礎

1 基本概念

Windows 作業系統是建立在事件驅動機制之上的,系統各部分之間的溝通也都是通過消息的互相傳遞而實作的。但在通常情況下,應用程式隻能處理來自程序内部的消息或是從其他程序發過來的消息,如果需要對在程序外傳遞的消息進行攔截處理就必須采取一種被稱為HOOK 的技術。鈎子是 Windows 作業系統中非常重要的一種系統接口,用它可以輕松截獲并處理在其他應用程式之間傳遞的消息,并由此可以完成一些普通應用程式難以實作的特殊功能。Hook 是Windows 消息處理機制的一個平台, 應用程式可以在上面設定子程以監視指定視窗的某種消息,而且所監視的視窗可以是其他程序所建立的。當消息到達後,在目标視窗處理函數之前處理它。鈎子機制允許應用程式截獲處理 window 消息或特定事件。鈎子實際上是一個處理消息的程式段,通過系統調用,把它挂入系統。每當特定的消息發出,在沒有到達目的視窗前,鈎子程式就先捕獲該消息,亦即鈎子函數先得到控制權。這時鈎子函數即可以加工處理(改變)該消息,也可以不作處理而繼續傳遞該消息,還可以強制結束消息的傳遞。

2 運作機制

1 、鈎子連結清單和鈎子過程:

    每一個Hook 都有一個與之相關聯的指針清單,稱之為鈎子連結清單,由系統來維護。這個清單的指針指向指定的,應用程式定義的,被Hook 子程調用的回調函數,也就是該鈎子的各個處理子過程。當與指定的Hook 類型關聯的消息發生時,系統就把這個消息傳遞到Hook 子過程。一些Hook 子過程可以隻監視消息,或者修改消息,或者停止消息的前進,避免這些消息傳遞到下一個Hook 子程或者目的視窗。最近安裝的鈎子放在鍊的開始,而最早安裝的鈎子放在最後,也就是後加入的先獲得控制權。

Windows 并不要求鈎子子程的解除安裝順序一定得和安裝順序相反。每當有一個鈎子被解除安裝,Windows 便釋放其占用的記憶體,并更新整個Hook 連結清單。如果程式安裝了鈎子,但是在尚未解除安裝鈎子之前就結束了,那麼系統會自動為它做解除安裝鈎子的操作。

    鈎子是一個應用程式定義的回調函數 (CALLBACK Function), 不能定義成某個類的成員函數,隻能定義為普通的C 函數。在使用鈎子時可以根據其監視範圍的不同将其分為全局鈎子和線程鈎子兩大類,其中 線程鈎子隻能監視某個線程,而全局鈎子則可對在目前系統下運作的所有線程進行監視。顯然,線程鈎子可以看作是全局鈎子的一個子集,全局鈎子雖然功能強大但同時實作起來也比較煩瑣:其鈎子函數的實作必須封裝在動态連結庫中才可以使用。

鈎子必須按照以下的文法:

LRESULT CALLBACK HookProc        //HookProc 是應用程式定義的名字。

(

      int nCode,

      WPARAM wParam,

    LPARAM lParam

);

nCode 參數是 Hook 代碼, Hook 子程使用這個參數來确定任務。這個參數的值依賴于 Hook 類型,每一種 Hook 都有自己的 Hook 代碼特征字元集。

wParam 和 lParam 參數的值依賴于 Hook 代碼,但是它們的典型值是包含了關于發送或者接收消息的資訊。

2 、鈎子的安裝與釋放:

由于全局鈎子具有相當的廣泛性而且在功能上完全覆寫了線程鈎子,是以下面就主要對應用較多的全局鈎子的安裝與使用進行讨論。前面已經提過,作業系統是通過調用鈎子連結清單開始處的第一個鈎子處理函數而進行消息攔截處理的。是以,為了設定鈎子,隻需将回調函數放置于鍊首即可,作業系統會使其首先被調用。在具體實作時由函數SetWindowsHookEx() 負責将回調函數放置于鈎子連結清單的開始位置。SetWindowsHookEx() 函數原型聲明如下:

HHOOK SetWindowsHookEx(

int idHook;

HOOKPROC lpfn;

HINSTANCE hMod;

DWORD dwThreadId);

其中:參數idHook 指定了鈎子的類型,總共有如下13 種:

  WH_CALLWNDPROC 系統将消息發送到指定視窗之前的" 鈎子"

  WH_CALLWNDPROCRET 消息已經在視窗中處理的" 鈎子"

  WH_CBT 基于計算機教育訓練的" 鈎子"

  WH_DEBUG 差錯" 鈎子"

  WH_FOREGROUNDIDLE 前台空閑視窗" 鈎子"

  WH_GETMESSAGE 接收消息投遞的" 鈎子"

  WH_JOURNALPLAYBACK 回放以前通過WH_JOURNALRECORD" 鈎子" 記錄的輸入消息

  WH_JOURNALRECORD 輸入消息記錄" 鈎子"

  WH_KEYBOARD 鍵盤消息" 鈎子"

  WH_MOUSE 滑鼠消息" 鈎子"

  WH_MSGFILTER 對話框、消息框、菜單或滾動條輸入消息" 鈎子"

  WH_SHELL 外殼" 鈎子"

  WH_SYSMSGFILTER 系統消息" 鈎子"

參數lpfn 為指向鈎子處理函數的指針,即回調函數的首位址;

參數hMod 則辨別了鈎子處理函數所處子產品的句柄 HINSTANCE ;

參數dwThreadId 指定被監視的線程,如果明确指定了某個線程的ID 就隻監視該線程,此時的鈎子即為線程鈎子;如果該參數被設定為0 ,則表示此鈎子為監視系統所有線程的全局鈎子。此函數在執行完後将傳回一個鈎子句柄 HHOOK , 失敗傳回 NULL 。

雖然對于線程鈎子并不要求其像全局鈎子一樣必須放置于動态連結庫中,但是推薦其也在動态連結庫中實作。因為這樣的處理不僅可使鈎子可為系統内的多個程序通路,也可以在系統中被直接調用,而且對于一個隻供單程序通路的鈎子,還可以将其鈎子處理過程放在安裝鈎子的同一個線程内,此時SetWindowsHookEx() 函數的第三個參數也就是該線程的執行個體句柄。

在SetWindowsHookEx() 函數完成對鈎子的安裝後,如果被監視的事件發生,系統馬上會調用位于相應鈎子連結清單開始處的鈎子處理函數進行處理,每一個鈎子處理函數在進行相應的處理時都要考慮是否需要把事件傳遞給下一個鈎子處理函數。如果要傳遞,就通過函數 CallNestHookEx() 來解決。盡管如此,在實際使用時還是強烈推薦無論是否需要事件傳遞而都在過程的最後調用一次 CallNextHookEx( ) 函數,否則将會引起一些無法預知的系統行為或是系統鎖定。該函數将傳回位于鈎子連結清單中的下一個鈎子處理過程的位址,至于具體的傳回值類型則要視所設定的鈎子類型而定。該函數的原型聲明如下:

LRESULT CallNextHookEx(HHOOK hhk; int nCode; WPARAM wParam; LPARAM lParam);   其中,參數hhk 為由SetWindowsHookEx() 函數傳回的目前鈎子句柄;參數nCode 為傳給鈎子過程的事件代碼;參數 wParam 和lParam 則為傳給鈎子處理函數的參數值,其具體含義同設定的鈎子類型有關。

鈎子函數也可以通過直接傳回TRUE 來丢棄該消息,并阻止該消息的傳遞。否則的話,其他安裝了鈎子的應用程式将不會接收到鈎子的通知而且還有可能産生不正确的結果。

最後,由于安裝鈎子對系統的性能有一定的影響,是以在鈎子使用完畢後應及時将其解除安裝以釋放其所占資源。釋放鈎子的函數為 UnhookWindowsHookEx() ,該函數比較簡單隻有一個參數用于指定此前由SetWindowsHookEx() 函數所傳回的鈎子句柄,原型聲明如下:

  BOOL UnhookWindowsHookEx(HHOOK hhk);

函數成功傳回TRUE ,否則傳回FALSE 。

3 系統鈎子與線程鈎子

SetWindowsHookEx() 函數的最後一個參數決定了此鈎子是系統鈎子還是線程鈎子。線程 HOOK 用于監視指定線程的事件消息。線程 HOOK 一般在目前線程或者目前線程派生的線程内。系統 HOOK 監視系統中的所有線程的事件消息。因為系統 HOOK 會影響系統中所有的應用程式,是以 HOOK 函數必須放在獨立的動态連結庫 (DLL) 中。系統自動将包含 " 鈎子回調函數 " 的 DLL 映射到受鈎子函數影響的所有程序的位址空間中,即将這個 DLL 注入了那些程序。

幾點說明:

( 1 ) 如果對于同一事件(如滑鼠消息)既安裝了線程 HOOK 又安裝了系統 HOOK ,那麼系統會自動先調用線程 HOOK ,然後調用系統HOOK 。

( 2 )對同一事件消息可安裝多個 HOOK 處理過程,這些 HOOK 處理過程形成了 HOOK 鍊。目前 HOOK 處理結束後應把 HOOK 資訊傳遞給下一個 HOOK 函數。

( 3 ) HOOK 特别是系統 HOOK 會消耗消息處理時間,降低系統性能。隻有在必要的時候才安裝 HOOK ,在使用完畢後要及時解除安裝。

4 一些運作機制

在 Win16 環境中, DLL 的全局資料對每個載入它的程序來說都是相同的;而在 Win32 環境中,情況卻發生了變化, DLL 函數中的代碼所建立的任何對象(包括變量)都歸調用它的線程或程序所有。當程序在載入 DLL 時,作業系統自動把 DLL 位址映射到該程序的私有空 間,也就是程序的虛拟位址空間,而且也複制該 DLL 的全局資料的一份拷貝到該程序空間。也就是說每個程序所擁有的相同的 DLL 的全局資料,它們的名稱相同,但其值卻并不一定是相同的,而且是互不幹涉的。是以,在 Win32 環境下要想在多個程序中共享資料,就必須進行必要的設定。在通路同一個 Dll 的各程序之間共享存儲器是通過存儲器映射檔案技術實作的。也可以把這些需要共享的資料分離出來,放置在一個獨立的資料段裡,并把該段的屬性設定為共享。必須給這些變量賦初值,否則編譯器會把沒有賦初始值的變量放在一個叫未被初始化的資料段中。

#pragma data_seg 預處理指令用于設定共享資料段。例如:

#pragma data_seg("SharedDataName")

HHOOK hHook=NULL;

#pragma data_seg()

在 #pragma data_seg("SharedDataName") 和 #pragma data_seg() 之間的所有變量将被通路該 Dll 的所有程序看到和共享。再加上一條指令 #pragma comment(linker,"/section:.SharedDataName,rws"), 那麼這個資料節中的資料可以在所有 DLL 的執行個體之間共享。所有對這些資料的操作都針對同一個執行個體的,而不是在每個程序的位址空間中都有一份。當程序隐式或顯式調用一個動态庫裡的函數時,系統都要把這個動态庫映射到這個程序的虛拟位址空間裡。這使得 DLL 成為程序的一部分,以這個程序的身份執行,使用這個程序的堆棧。

5 鈎子類型

1 、 WH_CALLWNDPROC 和WH_CALLWNDPROCRET Hooks

WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks 使你可以監視發送到視窗過程的消息。系統在消息發送到接收視窗過程之前調用 WH_CALLWNDPROC Hook 子程,并且在視窗過程處理完消息之後調用WH_CALLWNDPROCRET Hook 子程。

WH_CALLWNDPROCRET Hook 傳遞指針到CWPRETSTRUCT 結構,再傳遞到Hook 子程。

CWPRETSTRUCT 結構包含了來自處理消息的視窗過程的傳回 值,同樣也包括了與這個消息關聯的消息參數。

2 、WH_CBT Hook

在以下事件之前,系統都會調用WH_CBT Hook 子程,這些事件包括:

1. 激活,建立,銷毀,最小化,最大化,移動,改變尺寸等視窗事件;

2. 完成系統指令;

3. 來自系統消息隊列中的移動滑鼠,鍵盤事件;

4. 設定輸入焦點事件;

5. 同步系統消息隊列事件。

Hook 子程的傳回值确定系統是否允許或者防止這些操作中的一個。

3 、WH_DEBUG Hook

在系統調用系統中與其他Hook 關聯的Hook 子程之前,系統會調用 WH_DEBUG Hook 子程。你可以使用這個Hook 來決

定是否允許系統調用與其他Hook 關聯的Hook 子程。

4 、WH_FOREGROUNDIDLE Hook

當應用程式的前台線程處于空閑狀态時,可以使用 WH_FOREGROUNDIDLE Hook 執行低優先級的任務。當應用程式的

前台線程大概要變成空閑狀态時,系統就會調用 WH_FOREGROUNDIDLE Hook 子程。

5 、 WH_GETMESSAGE Hook

應用程式使用 WH_GETMESSAGE Hook 來監視從 GetMessage or PeekMessage 函數傳回的消息。你可以使用 WH_GETMESSAGE Hook 去監視滑鼠和鍵盤輸入,以及其他發送到消息隊列中的消息。

6 、 WH_JOURNALPLAYBACK Hook

WH_JOURNALPLAYBACK Hook 使應用程式可以插入消息到系統消息隊列。可以使用這個 Hook 回放通過使用 WH_JOURNALRECORD Hook 記錄下來的連續的滑鼠和鍵盤事件。隻要 WH_JOURNALPLAYBACK Hook 已經安裝,正常的滑鼠和鍵盤事件就是無效的。

WH_JOURNALPLAYBACK Hook 是全局 Hook ,它不能象線程特定 Hook 一樣使用。

WH_JOURNALPLAYBACK Hook 傳回逾時值,這個值告訴系統在處理來自回放 Hook 目前消息之前需要等待多長時間(毫秒)。這就使Hook 可以控制實時事件的回放。

WH_JOURNALPLAYBACK 是 system-wide local hooks ,它們不會被注射到任何行程位址空間。

7 、 WH_JOURNALRECORD Hook

WH_JOURNALRECORD Hook 用來監視和記錄輸入事件。典型的,可以使用這個 Hook 記錄連續的滑鼠和鍵盤事件,然後通過使用WH_JOURNALPLAYBACK Hook 來回放。

WH_JOURNALRECORD Hook 是全局 Hook ,它不能象線程特定 Hook 一樣使用。

WH_JOURNALRECORD 是 system-wide local hooks ,它們不會被注射到任何行程位址空間。

8 、 WH_KEYBOARD Hook

在應用程式中, WH_KEYBOARD Hook 用來監視 WM_KEYDOWN and WM_KEYUP 消息,這些消息通過 GetMessage or PeekMessage function 傳回。可以使用這個 Hook 來監視輸入到消息隊列中的鍵盤消息。

9 、 WH_KEYBOARD_LL Hook

WH_KEYBOARD_LL Hook 監視輸入到線程消息隊列中的鍵盤消息。

10 、 WH_MOUSE Hook

WH_MOUSE Hook 監視從 GetMessage 或者 PeekMessage 函數傳回的滑鼠消息。使用這個 Hook 監視輸入

到消息隊列中的滑鼠消息。

11 、 WH_MOUSE_LL Hook

WH_MOUSE_LL Hook 監視輸入到線程消息隊列中的滑鼠消息。

12 、 WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks

WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我們可以監視菜單,滾動條,消息框,對話框消息并且發現使用者使用 ALT+TAB or ALT+ESC 組合鍵切換視窗。 WH_MSGFILTER Hook 隻能監視傳遞到菜單,滾動條,消息框的消息,以及傳遞到通過安裝了 Hook 子程的應用程式建立的對話框的消息。 WH_SYSMSGFILTER Hook 監視所有應用程式消息。

WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks 使我們可以在模式循環期間過濾消息,這等價于在主消息循環中過濾消息。通過調用 CallMsgFilter function 可以直接的調用 WH_MSGFILTER Hook 。通過使用這個函數,應用程式能夠在模式循環期 間使用相同的代碼去過濾消息,如同在主消息循環裡一樣。

13 、 WH_SHELL Hook

外殼應用程式可以使用 WH_SHELL Hook 去接收重要的通知。當外殼應用程式是激活的并且當頂層視窗建立或者銷毀時,系統調用WH_SHELL Hook 子程。

WH_SHELL 共有5鐘情況:

1. 隻要有個 top-level 、 unowned 視窗被産生、起作用、或是被摧毀;

2. 當 Taskbar 需要重畫某個按鈕;

3. 當系統需要顯示關于 Taskbar 的一個程式的最小化形式;

4. 當目前的鍵盤布局狀态改變;

5. 當使用者按 Ctrl+Esc 去執行 Task Manager (或相同級别的程式)。

按照慣例,外殼應用程式都不接收 WH_SHELL 消息。是以,在應用程式能夠接收 WH_SHELL 消息之前,應用程式必須調用SystemParametersInfo function 注冊它自己。