HOOK鈎子教程


[轉載]HOOK鈎子教程

 

http://blog.sina.com.cn/s/blog_675049f701019ka9.html(原貼)

先留着,好好學一學!
原文地址: HOOK鈎子教程 作者: X_TK

[轉載]HOOK鈎子教程

    在你讀到這篇文章之前,也許你還已經讀過不少關於HOOK鈎子的教程,如果你已經成功HOOK上了,那么請閱讀本博客更高級別的文章。如果你還沒HOOK成功,相信本文能給你很大的幫助。如果閱讀完本教程依然有疑問,請在評論中留言。本教程是基礎教程,作者也是剛剛學會HOOK,文章中難免有錯漏之處,敬請讀者斧正。

    至於作者寫本文的原因,是在作者沒有HOOK成功之前讀過很多教程,卻感覺這些教程都沒寫到點子上。沒有把一些初學者常常遇到的問題說清楚,而在本文,作者會詳細講述這些問題(作者僅對X_TK博客中的評論進行回復,轉載至其他論壇及博客的,本作者概不負責)。

    另外,本教程所用語言為C/C++。

    鈎子(Hook),是Windows消息處理機制的一個平台,應用程序可以在上面設置子程以監視窗口的某種消息,而且所監視的窗口可以是其他進程所創建的。當消息到達后,在目標窗口處理函數之前處理它。鈎子機制允許應用程序截獲window消息或特定事件。鈎子實際上是一個處理消息的程序段,通過系統調用,把它掛入系統。每當特定的消息發出,在沒有到達目的窗口前,鈎子程序就先捕獲該消息,亦即鈎子函數先得到控制權。其中最后裝載的鈎子最先獲得消息。

    以上便是對鈎子的定義。

    鈎子能對系統中其他窗口的消息提前截取,相信很多人都對這項技術充滿了向往,甚至覺得其深不可測。其實HOOK非常簡單。

    先來看一下設置鈎子的API:SetWindowsHookEx

The SetWindowsHookEx function installs an application-defined hook procedure into a hook chain. An application installs a hook procedure to monitor the system for certain types of events. A hook procedure can monitor events associated either with a specific thread or with all threads in the system. This function supersedes the SetWindowsHook function.

    這段話的意思大致是這個API函數會向鈎子鏈(即一連串鈎子)中安裝一個鈎子並處理指定的消息,可以安裝在指定的進程或系統中的所有進程(全局鈎子)。

    再來看看函數的原型:

HHOOK SetWindowsHookEx(
int idHook, // type of hook to install 要安裝的鈎子的類型
HOOKPROC lpfn, // address of hook procedure 鈎子函數的地址
HINSTANCE hMod, // handle of application instance 包含鈎子函數模塊的句柄
DWORD dwThreadId // identity of thread to install hook for 要安裝鈎子的線程的PID
);

    其中,第一個參數idHook可以有以下取值:

WH_CALLWNDPROC//監視到達窗口前的消息
WH_CALLWNDPROCRET//監視窗口處理后的消息
WH_DEBUG//監視系統調用其他HOOK關聯的HOOK子程
WH_GETMESSAGE//監視發送到窗體消息隊列里的消息
WH_JOURNALPLAYBACK//全局HOOK,可以插入消息到消息隊列
WH_JOURNALRECORD//全局HOOK,監視輸入事件(鍵盤、鼠標等)
WH_KEYBOARD//鍵盤鈎子
WH_MOUSE//鼠標鈎子
WH_MSGFILTER//監視菜單、滾動條、消息框、對話框消息和切換窗口的組合鍵(Alt+Tab等)
WH_SHELL//接收系統中重要的通知(如窗口被產生、摧毀等)

    由於作者能力有限,這里只選擇一個取值進行舉例:WH_GETMESSAGE,這種HOOK能監視到窗體的所有消息。

    第二個參數lpfn是回調函數的地址,將在后文中詳細解說其獲取方法。

    第三個參數hMod是包含第二個參數指向的函數的模塊的句柄,也將在后文中詳細解說。

    最后一個參數dwThreadId是HOOK的目標進程的PID。

    函數的返回值:若返回NULL,則鈎子安裝失敗,可以通過GetLastError查詢錯誤;若返回的不是NULL,那么就是安裝的鈎子的句柄。

    下面就取WH_GETMESSAGE作為例子,對Windows自帶的記事本程序安裝鈎子。

    記事本進程名為notepad.exe,而安裝鈎子需要其PID,而每次啟動記事本,其PID都是不一樣的,那么下面介紹獲取其PID的方法。

    獲取進程PID用的是GetWindowThreadProcessId這個API:

DWORD GetWindowThreadProcessId(

HWND hWnd, // handle of window 要獲取的窗口句柄
LPDWORD lpdwProcessId // address of variable for process identifier 若此值不為NULL,API會將PID復制到這個參數所指向的DWORD型變量
);

返回值:線程的PID。

    對於第二個參數,我們設為NULL即可,我們只需要取返回值就行了。

    至於第一個參數,要求窗口句柄,而我們只知道進程名稱。這時候就應該用FindWindow這個API了:

HWND FindWindow(

LPCTSTR lpClassName, // pointer to class name 窗體Class Name指針
LPCTSTR lpWindowName // pointer to window name 窗體標題指針
);

返回值:窗體句柄。

    根據窗體標題來判讀是很不可靠的,因此對於第二個參數,作者設為了NULL。

    第一個參數要求窗體的Class Name,類型是LPCTSTR,只需輸入字符串即可。那么怎么獲取Notepad的Class Name呢?

    現在要用到VC自帶的工具spy++(沒有的請自行下載spy++),打開spy++,按下Ctrl+M,彈出了如圖的窗口:[轉載]HOOK鈎子教程
    Finder Tool是一個箭靶的圖案,拖動箭靶到記事本標題欄上,松開鼠標,窗體變成了如下的樣子:[轉載]HOOK鈎子教程

    右側顯示了Class:Notepad,於是我們可以知道Class Name就是”Notepad”。

    回到FindWindow API中,我們就可以這么寫:

FindWindow(“Notepad”,NULL);

    定義一個變量進行接收:

HWND notepadhandle;

notepadhandle=FindWindow(“Notepad”,NULL);

    句柄到手了,PID自然可以拿到:

GetWindowThreadProcessId(notepadhandle,NULL);

    返回的就是notepad.exe的PID了。

    有了PID,SetWindowsHookEx的第四個參數就解決了。還有第二個和第三個呢?

    由於目標進程不是本線程,所以HOOK的回調函數要封裝在DLL中,定義如下:

extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam);

    在本例程中,我們的SetWindowsHookEx操作由動態鏈接庫DLL完成,所以HOOK回調函數地址(第二個參數)直接寫函數名即可。如果SetWindowsHookEx操作是由外部模塊執行的,就應該先LoadLibrary獲取Dll的Handle,再用GetProcAddress獲取函數地址(注意函數名修飾問題),本文中這個不是重點,如果有這方面需要的,可以在評論里留言,作者將作詳細答復(作者僅對X_TK博客中的評論進行回復,轉載至其他論壇及博客的,本作者概不負責)。

    所以第二個參數,回調函數的地址,我們直接寫:HookProc。

    第三個參數,就是包含HookProc的模塊句柄了,本例程中執行操作的DLL就是包含HookProc的DLL,而在入口函數DllMain中:

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved);

    hModule就是Dll的句柄,而參數中給出的hModule是HANDLE類型的,我們只需強制轉換成HINSTANCE即可。

    因此第三個參數即為:(HINSTANCE)hModule。

    於是整個SetWindowsHookEx就寫成:

SetWindowsHookEx(WH_GETMESSAGE,HookProc,(HINSTANCE)hModule,GetWindowThreadProcessId(notepadhandle,NULL));

    該API函數返回值是HHOOK類型的,表示安裝的鈎子的句柄,定義一個全局變量接收即可:

HHOOK hooker;

hooker=SetWindowsHookEx(WH_GETMESSAGE,HookProc,(HINSTANCE)hModule,GetWindowThreadProcessId(notepadhandle,NULL));

    上面的篇幅解釋了怎么安裝鈎子。對各個參數進行了詳細的介紹。安裝鈎子是HOOK的整個過程中相對重要的一步,如果在這步上面失敗了,初學者便會失去信心。上文對記事本進行了鈎子安裝,如果對上文仍有疑問,或者按照上文的方法仍然安裝失敗,請在評論中留言,作者將給予幫助(作者僅對X_TK博客中的評論進行回復,轉載至其他論壇及博客的,本作者概不負責)。

    我們知道,鈎子實際上是一個程序段,於是這個程序段就必不可少了,這個程序段是一個回調函數,是上一個鈎子調用CallNextHookEx API之后系統調用的,因此傳來的參數有可能是被上一個鈎子處理過的,在處理消息過后,也應該CallNextHookEx以將消息傳給下一個鈎子(如果你想阻止該消息的傳遞,可以不調用該API)。下面介紹一下CallNextHookEx:

LRESULT CallNextHookEx(

HHOOK hhk, // handle to current hook 鈎子句柄
int nCode, // hook code passed to hook procedure nCode參數,回調函數參數中有
WPARAM wParam, // value passed to hook procedure wParam參數,回調函數參數中有
LPARAM lParam // value passed to hook procedure lParam參數,回調函數參數中有
);

返回值:如果執行成功,返回的值是下一個鈎子函數返回的值;如果執行失敗,返回NULL。

    nCode、wParam、lParam都來自於HookProc的參數。

    返回值倒是值得一提,這個做個抽象的描述,先安裝了Hook1,然后是Hook2,一直到Hookn(都是同一類型的鈎子):

Windows—-Hookn—-……—-Hook2—-Hook1

—-Application

    消息從Windows發出,從左到右經過多個鈎子,最后一個鈎子Hook1返回一個值,而這個值又被Hook2返回,一直返回到Hookn,再返回到Windows,然后消息傳到Application。也就是說,回調函數要返回由CallNextHookEx返回的值。

    綜上,一個什么都不做的HookProc就可以寫成這樣:
extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam){
return CallNextHookEx(hooker,nCode,wParam,lParam);
}

    CallNextHookEx的解釋就到這里,令很多人頭疼的是回調函數中的三個參數,這些會在后面作詳細介紹。現在先來看看另一個API:

BOOL UnhookWindowsHookEx(

HHOOK hhk // handle of hook procedure to remove 要卸載的HOOK的句柄
);

    由於上文中我們用的是hooker來接收句柄的,卸載就可以寫作:

UnhookWindowsHookEx(hooker);

    卸載不是必要的,因為線程結束后系統會自動卸載,但是在不想處理消息之后,卸載鈎子就顯得重要了,卸載后,仍需返回CallNextHookEx的返回值。

    接下來就是重點了,HookProc里面的三個參數。

    首先是第一個,nCode,這個值是由你設定的鈎子類型決定的。它用來告訴你該怎么處理這個發來的消息。

    在上面的例程中,設定了WH_GETMESSAGE鈎子,這個鈎子中,nCode的取值有以下兩種:

    第一種,是HC_ACTION,HC_ACTION實際上就是0;

    第二種,是小於0的取值,這個時候,鈎子必須毫無修改地把消息傳給下一個鈎子。

    至於第二個參數,wParam,這個值也是由你設定的鈎子類型決定的,各種鈎子各不相同。如WH_KEYBOARD鈎子中wParam就是按鍵的virtual-key code,即虛擬鍵值。而在WH_GETMESSAGE鈎子中,該參數指示傳來的消息是否已經從消息隊列中移除,有以下兩個取值:

    第一種,是PM_NOREMOVE,表示還沒有從消息隊列中移除;

    第二種,是PM_REMOVE,表示已經從消息隊列中移除。

    至於第三個參數,lParam,和前兩個參數一樣,由鈎子類型決定,而這個參數也是初學者眼中最難的一個參數。本鈎子中,lParam的解釋如下:

Points to an MSG structure that contains details about the message.

    就是說,這個lParam實際上就是一個指針,指向包含傳來的消息詳情的結構體變量:MSG。

    先來看看MSG的原型:

typedef struct tagMSG { // msg
HWND hwnd;
UINT message;
WPARAM wParam;
LPARAM lParam;
DWORD time;
POINT pt;
} MSG;

    眾多參數中,我們只關注message、wParam、lParam三個。

    message是UNIT變量,它可以表示各種消息,數不勝數,這里只列出幾個作為示例:

WM_CLOSE
WM_KEYDOWN
WM_PAINT
WM_NULL

    至於最后的WM_NULL,將在用到的時候作出解釋。

    而wParam和lParam又是依賴於message參數的。例如WM_CLOSE消息中,wParam和lParam都是NULL,而WM_KEYDOWN消息中,wParam是虛擬鍵值,lParam是關於按鍵消息的其他信息,如重復次數等。

    所以,我們要定義一個tagMSG指針:

tagMSG* msg;

    給它賦上lparam的值:

msg=(tagMSG*)lParam;

    於是就能通過msg訪問消息內容了,由於msg本身是指針,要訪問其內容如message就要如下:

msg->message

    通過上面的講述,我們大概了解了如何設置鈎子,如何編寫鈎子過程,以及對過程的參數的理解等都有了比較詳細的解釋。但有些地方難免缺漏,有任何疑問或補充敬請在評論中留言(作者僅對X_TK博客中的評論進行回復,轉載至其他論壇及博客的,本作者概不負責)。

    下面我們來編寫一個完整的HookProc,用來阻止一次WM_CLOSE消息之后卸載。

    怎么知道已經已經處理過一次了呢?定義一個BOOL變量,初始值為FALSE,處理過后為TRUE即可:

BOOL handled;

handled=FALSE;

    也許你會有疑問:既然用一次就卸載為什么還要設置這么一個變量呢?原因是可能同時又多個消息傳來,那么這段消息會被執行多次。

    那么怎么阻止消息傳遞呢?上文提到可以不CallNextHookEx,這里介紹另一種方法,將消息設為WM_NULL繼續傳遞。

    對於WM_NULL,微軟是這樣解釋的:

For example, if an application has installed a WH_GETMESSAGE hook and wants to prevent a message from being processed, the GetMsgProc callback function can change the message number to WM_NULL so the recipient will ignore it.

    這段話是說,HookProc可以通過將消息設為WM_NULL阻止該消息傳遞。

    於是我們就有思路了:消息傳來,判斷是不是WM_CLOSE,如果是,而且handled為FALSE,那么就將handled設為TRUE,然后將消息改為WM_NULL,然后卸載鈎子,然后調用CallNextHookEx繼續傳遞,並返回它的返回值。

我們的HookProc應該這樣寫:

extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam){
if(nCode<0)
return CallNextHookEx(hooker,nCode,wParam,lParam);
tagMSG* msg;
msg=(tagMSG*)lParam;
if(nCode==HC_ACTION && (msg->message==WM_CLOSE) && handled==FALSE){
if(handled==FALSE)
handled=TRUE;
UnhookWindowsHookEx(hooker);
msg->message=WM_NULL;
return CallNextHookEx(hooker,nCode,wParam,(LPARAM)msg);
}
return CallNextHookEx(hooker,nCode,wParam,lParam);
}

    這樣,就能阻止一次WM_CLOSE消息了。為了測試,我們需要在DllMain函數中添加一個PostMessage的調用,對其發送WM_CLOSE消息。PostMessage的原型如下:

BOOL PostMessage(

HWND hWnd, // handle of destination window 接收消息的窗口句柄
UINT Msg, // message to post 發送消息類型
WPARAM wParam, // first message parameter wParam參數
LPARAM lParam // second message parameter lParam參數
);

    這樣,我們就能發送一個WM_CLOSE消息了:

PostMessage(notepadhandle,WM_CLOSE,NULL,NULL);

    在VC6種新建一個DLL工程,命名為notepadhook:

[轉載]HOOK鈎子教程

    找到源文件:

    完整代碼如下:

// notepadhook.cpp : Defines the entry point for the DLL application.
//

#include “stdafx.h”
#include <stdio.h>
#include <stdlib.h>
HHOOK hooker;
HWND notepadhandle;
BOOL handled;extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam);
char* ConvertInttoChar(int i);

BOOL APIENTRY DllMain(HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved){
if(ul_reason_for_call==DLL_PROCESS_ATTACH){
handled=FALSE;
notepadhandle=FindWindow(“Notepad”,NULL);
if(notepadhandle==NULL){
printf(“Notepad Not Found.n”);
return TRUE;
}
hooker=SetWindowsHookEx(WH_GETMESSAGE,HookProc,(HINSTANCE)hModule,GetWindowThreadProcessId(notepadhandle,NULL));
if(hooker){
printf(“Hook Successfully.nHookID:%dn”,hooker);
}
else{
printf(“Hook Failed.nError:%dn”,GetLastError());
return TRUE;
}
PostMessage(notepadhandle,WM_CLOSE,0,0);
}
return TRUE;
}

extern “C” __declspec(dllexport) LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam){
if(nCode<0)
return CallNextHookEx(hooker,nCode,wParam,lParam);
tagMSG* msg;
msg=(tagMSG*)lParam;
if(nCode==HC_ACTION && (msg->message==WM_CLOSE)){
if(handled==FALSE)
handled=TRUE;
UnhookWindowsHookEx(hooker);
msg->message=WM_NULL;
return CallNextHookEx(hooker,nCode,wParam,(LPARAM)msg);
}
return CallNextHookEx(hooker,nCode,wParam,lParam);
}

    以上是DLL的源碼,另外新建一個C++ Source File,命名為hook:

[轉載]HOOK鈎子教程

    鍵入如下代碼:

#include <stdio.h>
#include <windows.h>
int main(){
LoadLibrary(“notepadhook.dll”);
getchar();//這里getchar是為了防止程序退出,若程序過快退出,鈎子可能沒有效果
return 1;
}
    編譯連接得到hook.exe,將notepadhook.dll復制到hook.exe同一目錄,打開記事本,運行hook.exe,可以看到Hook Successfully的提示,而且記事本並沒有被關閉,這說明成功攔截了WM_CLOSE消息。

    以上是《HOOK鈎子教程》的全部內容。內容較為基礎,要學習HOOK的更高級別的應用,請繼續關注X.TK博客。可能最后的部分顯得有點倉促,因為現在已經快凌晨了,作者快撐不住了。如果對本文由任何疑問,請在評論里留言(作者僅對X_TK博客中的評論進行回復,轉載至其他論壇及博客的,本作者概不負責)。感謝您閱讀本文。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM