關於Hook技術
Hook技術被廣泛應用於安全的多個領域,我們常說的Hook一般指微軟提供的基於訊息機制的Api, 除此之外Hook還有多種實現,hook的根本用途即資訊的攔截,包括訊息和API引數等的攔截。借鑑網上有個比較全面的分類如下:
- 按應用層分
Hook分為應用層(Ring3)Hook和核心層(Ring0)Hook,應用層Hook適用於x86和x64,而核心層Hook一般僅在x86平臺適用,因為從Windows Vista的64版本開始引入的Patch Guard技術極大地限制了Windows x64核心掛鉤的使用。
紅圈部分基於訊息機制的的由SetWindowsHook介面設定提供Hook功能的鉤子只是其中一種,本文中我們暫稱為“訊息鉤子”
訊息鉤子的原理:(關於windows訊息機制可以參照前面一篇文章)
首先以鍵盤訊息為例說明:
- 發生鍵盤輸入事件時,WM_KEYDOWN訊息被新增到[OS message queue]。
- OS判斷哪個應用程式中發生了事件,然後從[OS message queue]取出訊息,新增到相應應用程式的[application message queue]中。
- 應用程式(如記事本)監視自身的[application message queue],發現新新增的WM_KEYDOWN訊息後,呼叫相應的事件處理程式處理。
所以,我們只需在[OS message queue]和[application message queue]之間安裝鉤子即可竊取鍵盤訊息,並實現惡意操作。
關於訊息鉤子的具體說明和使用可以在MSDN查閱SetWindowsHookEx:https://docs.microsoft.com/en-us/windows/desktop/api/winuser/nf-winuser-setwindowshookexa
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
該函式的返回值是一個鉤子控制代碼。引數介紹如下:
lpfn:指定Hook函式的地址。如果dwThreadId引數被賦值為0,或者被設定為一個其他程序中的執行緒ID
hMod:
Type: HINSTANCE
A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.
該引數指定鉤子函式所在DLL的DLL模組控制代碼。即lpfn回撥函式所在的模組控制代碼。如果dwThreadId為當前程序中的執行緒ID且lpfn所指函式在當前程序中,則該引數必須被設定為NULL。
dwThreadId:
Type: DWORD
The identifier of the thread with which the hook procedure is to be associated. For desktop apps, if this parameter is zero, the hook procedure is associated with all existing threads running in the same desktop as the calling thread. For Windows Store apps, see the Remarks section.
指定需要被掛鉤的執行緒ID號。如果設定為0,表示在和被呼叫執行緒位於同一桌面環境中所有的執行緒中掛鉤;如果設定了具體的執行緒ID,表示在指定執行緒中掛鉤。該引數影響上面兩個引數的取值,同時也決定了該鉤子是全域性鉤子還是區域性鉤子。注意是否全域性鉤子還是區域性鉤子與下面具體的鉤子種類無關僅由本引數控制。
idHook:該引數表示鉤子的型別。
區域性鉤子:有的資料又叫執行緒鉤子,用來監視指定執行緒的事件訊息。
全域性鉤子:有的資料又叫系統鉤子,監視系統中的所有執行緒的事件訊息。因為系統鉤子會影響系統中所有的應用程式,所以鉤子函式必須放在獨立的動態連結庫(DLL)中。這是系統鉤子和執行緒鉤子很大的不同之處。
為什麼全域性鉤子必須在Dll中實現:
因為它需要插入到其它程序的地址空間,要做到這一點要求鉤子函式必須在一個動態連結庫中,所以如果您想要使用系統鉤子,就必須把該鉤子函式放到動態連結庫中去。在作業系統中安裝全域性鉤子後,只要程序接收到可以發出鉤子的訊息,全域性鉤子的DLL檔案就會被作業系統自動或強行地載入到該程序中。因此,設定全域性訊息鉤子,也可以達到DLL注入的目的。
幾點需要說明的地方:
(1) 如果對於同一事件(如滑鼠訊息)既安裝了執行緒鉤子又安裝了系統鉤子,那麼系統會自動先呼叫執行緒鉤子,然後呼叫系統鉤子。
(2) 對同一事件訊息可安裝多個鉤子處理過程,這些鉤子處理過程形成了鉤子鏈。當前鉤子處理結束後應把鉤子資訊傳遞給下一個鉤子函式。而且最近安裝的鉤子放在鏈的開始,而最早安裝的鉤子放在最後,也就是後加入的先獲得控制權。
(3) 鉤子特別是系統鉤子會消耗訊息處理時間,降低系統性能。只有在必要的時候才安裝鉤子,在使用完畢後要及時解除安裝。
移除先前用SetWindowsHookEx安裝的鉤子:
BOOL UnhookWindowsHookEx(
HHOOK hhk
);
唯一的引數是待移除的鉤子控制代碼。
在實際應用中,可以多次呼叫SetWindowsHookEx函式來安裝鉤子,而且可以安裝多個同樣型別的鉤子。這樣,鉤子就會形成一條鉤子鏈,最後安裝的鉤子會首先截獲到訊息。當該鉤子對訊息處理完畢後,可以選擇返回或者把訊息繼續傳遞下去。如果是為了遮蔽某訊息,可以在安裝的鉤子函式中直接返回非零值。如果希望我們的鉤子函式處理完訊息後可以繼續傳遞給目標視窗,則必須選擇將訊息繼續傳遞。繼續傳遞訊息的函式定義如下:
LRESULT CallNextHookEx(
HHOOK hhk, //handle to current hook
int nCode, //hook code passed to hook procedure
WPARAM wParam, //value passed to hook procedure
LPARAM lParam //value passed to hook procedure
);
寫一個訊息鉤子程式的步驟總結:
1、在MSDN中查閱SetWindowsHookExA function的說明頁面
2、在idHook引數說明表格中找到自己想要設定的鉤子型別及回撥函式型別(表格中後面一列)
3、點選查看回調函式說明比如CBTProc,
LRESULT CALLBACK CBTProc( _In_ int nCode, _In_ WPARAM wParam, _In_ LPARAM wParam);
4、在頁面的Remarks部分檢視nCode、wParam、wParam轉換成對應的結構體,獲取到要攔截的訊息內容。
下面我實現一個鍵盤鉤子及一個獲取視窗改變大小時當前視窗尺寸的訊息鉤子:
從這個練習中發現有些全域性鉤子在傳入模組控制代碼時可以傳入GetMoudleHandle(Null)比如全域性鍵盤鉤子,有些必須要傳一個非NULL的控制代碼比如CBT型別的,否則會產生錯誤捕獲不到訊息。 具體不清楚為啥,後面再研究看看。
具體連結如下: