windows下钩子的使用
我們知道Windows中的窗口程序是基于消息,由事件驅動的,在某些情況下可能需要捕獲或者修改消息,從而完成一些特殊的功能(MFC框架就利用Windows鉤子對消息進行引導)。對于捕獲消息而言,無法使用IAT或Inline Hook之類的方式去進行捕獲,這就要用到接下來要介紹的Windows提供的專門用于處理消息的鉤子函數。
1.?掛鉤原理
Windows下的應用程序大部分都是基于消息機制的,它們都會有一個消息過程函數,根據不同的消息完成不同的功能。Windows操作系統提供的鉤子機制的作用就是用來截獲和監視這些系統中的消息。Windows鉤子琳瑯滿目,可以用來應對各種不同的消息。
按照鉤子作用的范圍不同,又可以分為局部鉤子和全局鉤子。其中,全局鉤子具有相當大的功能,幾乎可以實現對所有Windows消息的攔截、處理和監控。局部鉤子是針對某個線程的;而全局鉤子則是作用于整個系統中基于消息的應用。全局鉤子需要使用DLL文件,在DLL中實現相應的鉤子函數。系統鉤子,系統就必須把鉤子函數插入到其它進程的地址空間,要做到這一點要求鉤子函數必須在一個動態鏈接庫中,所以如果您想要使用系統鉤子,就必須把該鉤子函數放到動態鏈接庫中去。在操作系統中安裝全局鉤子后,只要進程接收到可以發出鉤子的消息,全局鉤子的DLL文件就會被操作系統自動或強行地加載到該進程中。因此,設置消息鉤子,也可以達到DLL注入的目的。
般來說,HOOK API由兩個組成部分,即實現HOOK API的DLL文件,和啟動注入的主調程序。本文采用HOOK API 技術對剪切板相關的API 函數進行攔截,從而實現對剪切板內容的監控功能,同樣使用該技術實現進程防終止功能。其中DLL文件支持HOOK API的實現,而主調客戶端程序將在初始化時把帶有HOOK API功能的DLL隨著鼠標鉤子的加載注入到目標進程中,這里的鼠標鉤子屬于系統鉤子。
幾點需要說明的地方:
(1) 如果對于同一事件(如鼠標消息)既安裝了線程鉤子又安裝了系統鉤子,那么系統會自動先調用線程鉤子,然后調用系統鉤子。
(2) 對同一事件消息可安裝多個鉤子處理過程,這些鉤子處理過程形成了鉤子鏈。當前鉤子處理結束后應把鉤子信息傳遞給下一個鉤子函數。而且最近安裝的鉤子放在鏈的開始,而最早安裝的鉤子放在最后,也就是后加入的先獲得控制權。
(3) 鉤子特別是系統鉤子會消耗消息處理時間,降低系統性能。只有在必要的時候才安裝鉤子,在使用完畢后要及時卸載。
?
鉤子的類型
1、按事件分類
有如下的幾種常用類型
(1) 鍵盤鉤子和低級鍵盤鉤子可以監視各種鍵盤消息。
(2) 鼠標鉤子和低級鼠標鉤子可以監視各種鼠標消息。
(3) 外殼鉤子可以監視各種Shell事件消息。比如啟動和關閉應用程序。
(4) 日志鉤子可以記錄從系統消息隊列中取出的各種事件消息。
(5) 窗口過程鉤子監視所有從系統消息隊列發往目標窗口的消息。
此外,還有一些特定事件的鉤子提供給我們使用,不一一列舉。
2、按使用范圍分類
主要有線程鉤子和系統鉤子:
(1) 線程鉤子監視指定線程的事件消息。
(2) 系統鉤子監視系統中的所有線程的事件消息。因為系統鉤子會影響系統中所有的應用程序,所以鉤子函數必須放在獨立的動態鏈接庫(DLL)中。這是系統鉤子和線程鉤子很大的不同之處。
2.?鉤子函數
HHOOK SetWindowsHookEx(
int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId
);
?
該函數的返回值是一個鉤子句柄。參數介紹如下:
lpfn:指定Hook函數的地址。如果dwThreadId參數被賦值為0,或者被設置為一個其他進程中的線程ID(遠程鉤子/全局鉤子),那么lpfn屬于DLL中的函數過程。如果dwThreadId為當前進程中的一個線程ID,那么lpfn可以使指向當前進程模塊中的函數,當然,也可以使DLL模塊中的函數(局部鉤子/本地鉤子)。
hMod:該參數指定鉤子函數所在模塊的模塊句柄。即lpfn所在的模塊句柄。如果dwThreadId為當前進程中的線程ID,若lpfn所指函數在當前進程中,則該參數被設置為NULL。
dwThreadId:指定需要被掛鉤的線程ID號。如果設置為0,表示在所有基于所有的線程中掛鉤;如果設置了具體的線程ID,表示在指定線程中掛鉤。該參數影響上面兩個參數的取值,同時也決定了該鉤子是全局鉤子還是局部鉤子。注意是否全局鉤子還是局部鉤子與下面具體的鉤子種類無關僅由本參數控制。
idHook:該參數表示鉤子的類型。常用的幾種如下:
- WH_GETMESSAGE
按照該鉤子的作用是監視被投遞到消息隊列中的消息。也就是當調用GetMessage或PeekMessage函數時,函數從程序的消息隊列中獲取一個消息后調用該鉤子。
WH_GETMESSAGEG的鉤子函數如下:
LRESULT CALLBACK GetMsgProc( ?????
????int code, //hook code
????WPARAM wParam, //removal option
????LPARAM lParam ???????//message
);
?
- WH_MOUSE
該鉤子用于監視鼠標消息。鉤子函數如下:
LRESULT CALLBACK MouseProc( ?????
????int nCode, //hook code
????WPARAM wParam, //message identifier
????LPARAM lParam //mouse coordinates
);
?
- WH_KEYBOARD
該鉤子用于監視鍵盤消息。鉤子函數如下:
LRESULT CALLBACK KeyboardProc( ?????
?
????int code, //hook code
????WPARAM wParam, //virtual-key code
LPARAM lParam //keystroke-message information
}
- WH_DEBUG
用于調試其它鉤子。鉤子函數如下:
LRESULT CALLBACK DebugProc( ?????
?
????int nCode, //hook code
????WPARAM wParam, //hook type
????LPARAM lParam //debugging information
);
對于以上鉤子函數的詳情還請各位看客老爺們自行挪步到MSDN了。
移除先前用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
);
第一個參數是鉤子句柄,就是調用SetWindowsHookEx函數的返回值;后面3個參數是鉤子的參數,直接一次copy即可。例如:
HHOOK g_Hook = SetWindowsHookEx(…);
LRESULT CALLBACK GetMsgProc( ?????
????int code, //hook code
????WPARAM wParam, //removal option
????LPARAM lParam ???????//message
)
{
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
?
3.?鉤子實例
Windows鉤子的使用場景比較廣泛,我們就幾種比較常見的情況做一個應用示例。
參看上一小結可知,編寫鉤子程序的三個步奏是:
定義鉤子函數:
LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam)
安裝鉤子:
HHOOK SetWindowsHookEx( int idHook,HOOKPROC lpfn, INSTANCE hMod,DWORD dwThreadId )
卸載鉤子:
BOOL UnhookWindowsHookEx( HHOOK hhk)
還需要注意一點:系統鉤子必須放在獨立的動態鏈接庫中。由此,程序分為兩個部分:一個是鉤子程序動態鏈接庫,實現了鼠標鉤子程序;另一個是MFC操作窗體,對DLL進行加載和卸載,即對DLL進行測試。//我在win7下測試沒有使用dll也成功了why?不想在DLL實現SetWindowsHookEx時第三個參數傳入GetModuleHandle(NULL)即可?
?
3.1全局鍵盤鉤子
先新建一個DLL程序(這個不會可以看我以前的博客,這里就不重復了),我們在頭文件中增加兩個導出函數和兩個全局。
#define MY_API __declspec(dllexport)
extern "C" MY_API VOID SetHookOn();
extern "C" MY_API VOID SetHookOff();
?
HHOOK g_Hook = NULL; //鉤子句柄
HINSTANCE g_Inst = NULL; //DLL模塊句柄
?
在DllMain中保存該DLL模塊的句柄,以方便安裝全局鉤子。
BOOL APIENTRY DllMain( HANDLE hModule,
???????????????????????DWORD ?ul_reason_for_call,
???????????????????????LPVOID lpReserved
????)
{
//保存DLL模塊句柄
g_Inst = (HINSTANCE)hModule;
????return TRUE;
}
?
安裝與卸載鉤子的函數如下:
VOID SetHookOn()
{
//安裝鉤子
g_Hook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_Inst, 0);
}
?
VOID SetHookOff()
{
//卸載鉤子
UnhookWindowsHookEx(g_Hook);
}
?
鉤子函數的實現如下:
?
//鉤子函數
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
if(code < 0)
{
//如果code小于0,必須調用CallNextHookEx傳遞消息,不處理該消息,并返回CallNextHookEx的返回值。
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
if(code == HC_ACTION && lParam > 0)
{
//code等于HC_ACTION,表示消息中包含按鍵消息
//如果為WM_KEYDOWN,則顯示按鍵對應的文本
char szBuf[MAXBYTE] = {0};
GetKeyNameText(lParam, szBuf, MAXBYTE);
MessageBox(NULL, szBuf, "提示", MB_OK);
}
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
?
編譯鏈接后產生我們需要的.dll和.lib文件,然后新建一個項目來導入動態庫內容調用相關函數。
新建項目如下:
首先導入庫:
?
#pragma comment (lib, "全局鉤子.lib")
聲明將要調用的函數(不聲明鏈接時將報錯):
?
extern "C" VOID SetHookOn();
extern "C" VOID SetHookOff();
在按鈕事件中調用導出函數:
?
void CHookDebugDlg::OnHookon()
{
SetHookOn();
}
?
void CHookDebugDlg::OnHookoff()
{
SetHookOff();
}
?
?
3.2低級鍵盤鉤子
數據防泄漏軟件通常會精致PrintScreen鍵,防止通過截屏將數據保存為圖片而導致數據泄密。下面我們也可以模仿一下,簡單的實現該功能。這里需要注意的是,普通的鍵盤鉤子(WH_KEYBOARD)是無法過濾一些系統按鍵的,得通過安裝低級鍵盤鉤子(WH_KEYBOARD_LL)來達到目的。
在低級鍵盤鉤子的回調函數中,判斷是否為PrintScreen鍵,如果是,則直接返回TRUE;如果不是,則傳遞給下一個鉤子處理。
實現過程與上面相同。
可能在編譯時會報錯,說WH_KEYBOARD_LL和KBDLLHOOKSTRUCT未定義,此時可以在文件開頭加上如下代碼:
?
#define WH_KEYBOARD_LL 13
?typedef struct tagKBDLLHOOKSTRUCT {
?DWORD vkCode;
?DWORD scanCode;
?DWORD flags;
?DWORD time;
?DWORD dwExtraInfo;
?} KBDLLHOOKSTRUCT, FAR *LPKBDLLHOOKSTRUCT, *PKBDLLHOOKSTRUCT;
其實在winuser.h中已有定義,但是可能是兼容的緣故用不了。
3.3鉤子注入DLL
利用WH_GETMESSAGE鉤子,可以方便地將DLL文件注入到所有基于消息機制的程序中。因為有時候可能需要DLL文件完成一些工作,但是工作時需要DLL在目標進程的空間中。這個時候,就可以將DLL注入目標進程來完成相關的功能。
主要的代碼如下:
BOOL APIENTRY DllMain( HANDLE hModule,
???????????????????????DWORD ?ul_reason_for_call,
???????????????????????LPVOID lpReserved
?)
{
//保存DLL模塊句柄
g_Inst = (HINSTANCE)hModule;
?????switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
DoSomething();
break;
}
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
????}
????return TRUE;
}
VOID SetHookOn()
{
g_Hook = SetWindowsHookEx(WH_GETMESSAGE, GetMsgProc, g_Inst, 0);
}
?
VOID SetHookOff()
{
UnhookWindowsHookEx(g_Hook);
}
?
LRESULT CALLBACK GetMsgProc(int code, WPARAM wParam, LPARAM lParam)
{
return CallNextHookEx(g_Hook, code, wParam, lParam);
}
?
VOID DoSomething()
{
MessageBox(NULL, "Hello,我被執行了!", "提示", MB_OK);
}
?
總結
以上是生活随笔為你收集整理的windows下钩子的使用的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: win32创建控件的一些问题
- 下一篇: VS2017控制台工程日志输出到log文