您當前的位置:首頁 > 收藏

為何鉤子程式設計具有如此大的殺傷力,讓我們又愛又恨

作者:由 小羊程式設計 發表于 收藏時間:2022-08-16

一、鉤子介紹鉤子的實現機制

鉤子英文名叫Hook,是一種截獲windows系統中某應用程式或者所有程序的訊息的一種技術。下圖是windows應用程式傳遞訊息的過程:

為何鉤子程式設計具有如此大的殺傷力,讓我們又愛又恨

如在鍵盤中按下一鍵,作業系統將收到鍵按下訊息,把訊息放入訊息佇列,然後訊息佇列對訊息進行派發,發給相應的應用程式,經過應用程式處理後發給作業系統,作業系統再呼叫相應的應用程式的建立的視窗過程。

我們可能透過鉤子截獲這些訊息,讓訊息不再往下傳遞,或者說截獲到感興趣的訊息後做點什麼。

1。2鉤子分類與實現

鉤子分程序內鉤子與全域性鉤子,程序內鉤子是擷取某一指定的程序的訊息,直接在程序內建立與消除鉤子即可。全域性鉤子是擷取所有程序的訊息,得以動態庫的方式實現。

實現一個鉤子一般有三個步驟,首先建立鉤子,有專門的API:SetWindowsHookEx,建立成功後,訊息將會傳給SetWindowsHookEx的形參指定的處理函式。然後在訊息處理函式中分析收到的訊息,做相應的處理。最後,鉤子用完後,用API(UnhookWindowsHookEx)消毀鉤子。

二、建立鉤子

2。1鉤子的建立

SetWindowsHookEx安裝一個應用程式定義的鉤子過程,並把建立的鉤子過程放在鉤子鏈中,可以安裝多個鉤子,多個鉤子就形成了鉤子鏈,最後安裝的鉤子總是在最前面。建立鉤子的函式如下:

為何鉤子程式設計具有如此大的殺傷力,讓我們又愛又恨

建立鉤子,返回鉤子控制代碼,否則返回NULL。形參定義如下:

idHook:

鉤子過程型別,如:滑鼠訊息鉤子、鍵盤訊息鉤子、訊息佇列監控鉤子等等。具體取值如下:

為何鉤子程式設計具有如此大的殺傷力,讓我們又愛又恨

lpfn:

相應的鉤子過程,也就是一個處理訊息的回撥函式名而已,如果引數dwThreadId為0,或者dwThreadId指向的是其他程序建立的執行緒標誌符,那麼lpfn必須指向一個位於某一動態庫中的鉤子過程。其他情況下,lpfn可以指向本程序內的某一鉤子過程。

hMod:

指向鉤子過程所在的應用程式例項控制代碼,如:鉤子過程所在的DLL的控制代碼。如果dwThreadId指定的執行緒是由當前程序建立,並且鉤子過程在當時程序中,那麼hMod必須設定為NULL。

dwThreadId:

指定與鉤子相關的執行緒標誌。如果為0,那麼鉤子將與桌面上執行的所有執行緒相關。

鉤子過程可以與特定執行緒相關也可以與所有執行緒相關,取決於dwThreadId的取值。

2。2鉤子過程分析

鉤子過程,是處理鉤子擷取的訊息的一個回撥函式,即SetWindowsHookEx函式中的第二個形參。格式如下:

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam);形參含義並不是都一樣的,不同鉤子過程形參表示的意義不一樣,我們以滑鼠鉤子為例說明,引數含義如下:

nCode:

指示鉤子過程如何處理當前的訊息,如果鉤子是滑鼠訊息鉤子的話,有兩種含義:

為何鉤子程式設計具有如此大的殺傷力,讓我們又愛又恨

wParam:

指示滑鼠訊息標誌。

lParam:

指向MOUSEHOOKSTRUCT結構體指標。以下是MOUSEHOOKSTRUCT結構體,包含著滑鼠訊息。

typedef struct {

POINT pt;//游標包含x,y,是螢幕座標。

HWND hwnd;//目標視窗控制代碼

UINT wHitTestCode;

ULONG_PTR dwExtraInfo;

} MOUSEHOOKSTRUCT, *PMOUSEHOOKSTRUCT;

透過這三個引數,可對訊息進行分析處理。

其他鉤子過程引數的含義可以透過MSDN文件檢視詳細說明,如KeyboardProc、MessageProc等。

2。3消毀鉤子

BOOL UnhookWindowsHookEx(HHOOK hhk);此API的功能是把SetWindowsHookEx建立的鉤子從鉤子鏈中移除。形參是SetWindowsHookEx返回的鉤子控制代碼。

三、程序內鉤子

程序內鉤子一般是為了截獲當前應用程式的訊息,一般會可以在應用程式初始化的時候建立,當然也可以在自己想建立的時候建立,只是建立之前的訊息無法截獲。

3。1滑鼠鉤子過程函式

以下為滑鼠鉤子過程程式碼:

HWND g_DestWndH = NULL;

HHOOK g_hHookDm = NULL;

LRESULT CALLBACK MouseProc(int nCode, WPARAM wParam, LPARAM lParam)

{ //此區間內的訊息都是滑鼠訊息

if (WM_MOUSEMOVE <= wParam && wParam <= WM_MOUSEWHEEL) {

if(g_DestWndH != NULL) {

::SendMessage(g_DestWndH, wParam, wParam, lParam);

//滑鼠鉤子,lParam是MOUSEHOOKSTRUCT結構指標

PMOUSEHOOKSTRUCT lpMsg = (PMOUSEHOOKSTRUCT)lParam;

lpMsg->hwnd = NULL; //把目的視窗置NULL

lpMsg->dwExtraInfo = 0L;

lpMsg->pt = CPoint(0, 0);

lpMsg->wHitTestCode = 0L;

}

return 1;//如果想讓此訊息不再往下傳,返回非0

}

else//不感興趣的訊息往下傳

return ::CallNextHookEx(g_hHookDm, nCode, wParam, lParam);

}

3。2建立鉤子過程

以下為建立鉤子過程的程式碼:

if(g_hHookDm == NULL) {

//如果dwThreadId指定的執行緒是由當前程序建立,並且鉤子過程在當時程序中,那麼hMod必須設定為NULL。

g_hHookDm = ::SetWindowsHookEx(WH_MOUSE,MouseProc, NULL, GetCurrentThreadId());

if (NULL != g_hHookDm)//建立成功

g_DestWndH = m_hWnd;//儲存目的視窗控制代碼

}

3。3消毀程序內鉤子

把鉤子過程從鉤子鏈中拿下來,呼叫一個API即可:

if (NULL != g_hHookDm) {

UnhookWindowsHookEx(g_hHookDm);

g_DestWndH = NULL;

}

到此程序內鉤子的建立與執行到消毀整個過程都完成了。

四、全域性鉤子

4。1鍵盤鉤子過程函式

以上程序內鉤子只是截獲本程序的訊息,如果要截獲桌面全部執行緒的訊息則要透過全域性鉤子。全域性鉤子必須在DLL上實現,鉤子過程不能在本程序程式碼中實現,所以先得寫一個DLL,程式碼見”HookDll”,以鍵盤鉤子為例:

為何鉤子程式設計具有如此大的殺傷力,讓我們又愛又恨

nCode:

與滑鼠鉤子是一樣的含義,有HC_ACTION與HC_NOREMOVE兩個值。

Param:

代表具體的虛擬鍵,如:VK_TAB、VK_RETURN分別代表Tab鍵、Enter鍵按下,其他虛擬鍵訊息可檢視MSDN的”virtual-key code”。

lParam:

存放一些擴充套件資訊,如:按鍵重複資料、29位表示ALT鍵的按下情況。

引數的具體含義可查MSDN。關於組合鍵的問題,參看以下鍵盤鉤子函式的示例程式碼:

HHOOK g_HookKeyBoard = NULL;

LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)

{

//wParam表示虛擬鍵,lparam第29位表示ALT鍵按下狀態(按下為1,否則為0),

//GetKeyState可以獲得對應鍵的狀態,所以收下表示Control + ALT + Z組合鍵按下

if (‘Z’ == wParam && GetKeyState(VK_CONTROL) < 0 && (lParam >> 29 & 1)) {

::SendMessage(g_DestWnd, WM_KEYDOWN, wParam, lParam);

return 1;

}

else

return CallNextHookEx(g_HookKeyBoard, nCode, wParam, lParam);

}

注:全域性鉤子在某版本的系統中,除錯狀態下,如果除錯程式視窗沒有獲得焦點,在設定斷點的方不會停留,如果獲得焦點才會停留,也就是說,在除錯狀態下,不屬於本程序的訊息斷點處不會停留。

4。2建立全域性鉤子介面

建立全域性鉤子的方法在DLL中,所以得引出一個介面,程式碼如下:

BOOL SetHook(HWND hWnd){

if (NULL == hWnd)

return FALSE;

g_DestWnd = hWnd;

//第三個引數可透過些方法獲得:GetModuleHandle(“MouseHook。dll”)

return (NULL != (g_HookKeyBoard = ::SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInst, 0)));

}

其形參是一視窗控制代碼,指示截獲的興趣訊息需傳向的視窗的控制代碼,其中MetWindowsHookE的第三個引數是DLL的應用程式例項控制代碼,可透過DllMain的傳入引數得到,亦可透過GetModuleHandle方法得到,第四個引數必須為0,才能獲得全部桌面執行緒的訊息。成功將返回TRUE否則FALSE。

4。3消毀全域性鉤子介面

在DLL中也得引出一介面消毀全域性鉤子,程式碼如下:

void DestroyHook(){

if (NULL != g_HookKeyBoard) {

UnhookWindowsHookEx(g_HookKeyBoard);

g_DestWnd = NULL;

}

}

4。4使用DLL建立全域性鉤子

以下為建立與消毀全域性鉤子的實現,程式碼如下:

HINSTANCE hIst = NULL;

void CHookTestDlg::OnCreateDllHook()

{

hIst = ::LoadLibrary(“。。\\HookDll\\Debug\\HookDll。dll”);

if (NULL != hIst) {

typedef BOOL (*pFunSetHook)(HWND);

pFunSetHook pSetHook = (pFunSetHook)GetProcAddress(hIst, “SetHook”);

if (NULL != pSetHook) {

if(pSetHook(m_hWnd))

AfxMessageBox(“建立全域性鉤子成功。。。”);

}

}

}

void CHookTestDlg::OnDestroyDllHook()

{

if (NULL != hIst) {

typedef void (*pFunDestroyHook)();

pFunDestroyHook pDestryHook = (pFunDestroyHook)GetProcAddress(hIst, “DestroyHook”);

if (NULL != pDestryHook) {

pDestryHook();

::FreeLibrary(hIst);

hIst = NULL;

}

}

}

鉤子的型別

(1) 鍵盤鉤子和低階鍵盤鉤子可以監視各種鍵盤訊息。

(2) 滑鼠鉤子和低階滑鼠鉤子可以監視各種滑鼠訊息。

(3) 外殼鉤子可以監視各種Shell事件訊息。比如啟動和關閉應用程式。

(4) 日誌鉤子可以記錄從系統訊息佇列中取出的各種事件訊息。

(5) 視窗過程鉤子監視所有從系統訊息佇列發往目標視窗的訊息。

13種常用的Hook型別:

WH_CALLWNDPROC 和 WH_CALLWNDPROCRET Hooks:

監視傳送到視窗過程的訊息。

WH_CBT Hook:

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

1。 啟用,建立,銷燬,最小化,最大化,移動,改變尺寸等視窗事件;

2。 完成系統指令;

3。 來自系統訊息佇列中的移動滑鼠,鍵盤事件;

4。 設定輸入焦點事件;

5。 同步系統訊息佇列事件。

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

WH_DEBUG Hook

在系統呼叫系統中與其他Hook關聯的Hook子程之前,系統會呼叫WH_DEBUG Hook子程。你可以使用這個Hook來決定是否允許系統呼叫與其他Hook關聯的Hook子程。

WH_FOREGROUNDIDLE Hook

當應用程式的前臺執行緒處於空閒狀態時,可以使用WH_FOREGROUNDIDLE Hook執行低優先順序的任務。當應用程式的前臺執行緒大概要變成空閒狀態時,系統就會呼叫WH_FOREGROUNDIDLE Hook子程。

WH_GETMESSAGE Hook

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

WH_JOURNALPLAYBACK Hook:

使應用程式可以插入訊息到系統訊息佇列。

WH_JOURNALRECORD Hook:

監視和記錄輸入事件,記錄連續的滑鼠和鍵盤事件並回放。

WH_KEYBOARD Hook:

在應用程式中監視WM_KEYDOWN 和 WM_KEYUP訊息;

WH_KEYBOARD_LL Hook:

監視輸入到執行緒訊息佇列中的鍵盤訊息。

WH_MOUSE Hook:

監視從GetMessage 或者 PeekMessage 函式返回的滑鼠訊息。

WH_MOUSE_LL Hook:

監視輸入到執行緒訊息佇列中的滑鼠訊息。

WH_MSGFILTER 和 WH_SYSMSGFILTER Hooks:

監視選單,捲軸,訊息框,對話方塊訊息並且發現使用者使用ALT+TAB or ALT+ESC 組合鍵切換視窗。

WH_SHELL Hook:

外殼應用程式可以使用WH_SHELL Hook去接收重要的通知。當外殼應用程式是啟用的並且當頂層視窗建立或者銷燬時,系統呼叫WH_SHELL Hook子程。

WH_SHELL 共有5種情況:

只要有個top-level、unowned 視窗被產生、起作用、或是被摧毀;

當Taskbar需要重畫某個按鈕;

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

當目前的鍵盤佈局狀態改變;

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

按照慣例,外殼應用程式都不接收WH_SHELL訊息。

所以,在應用程式能夠接收WH_SHELL訊息之前,應用程式必須呼叫SystemParametersInfo function進行註冊。

鉤子很厲害,用時需謹慎~想試試鉤子的厲害的夥伴可以私信我噢

標簽: 鉤子  Wh  hook  訊息  null