利用鉤子函式來捕捉鍵盤響應的windows應用程式

youhello發表於2007-11-20
你也許一直對金山詞霸的螢幕抓詞的實現原理感到困惑,你也許希望將你的鍵盤,滑鼠的活動適時的記錄下來,甚至你想知道木馬在windows作業系統是怎樣進行木馬dll的載入的…..其實這些都是用到了windows的鉤子函式。因此本文將對鉤子函式的相關知識進行闡述。當然,本文的目的並不是想透過此程式讓讀者去竊取別人的密碼,只是由於鉤子函式在windows系統中是一個非常重要的系統介面函式,所以想和大家共同的探討,當然本文也對怎樣建立動態連結庫(DLL)作了一些簡單的描述。(本文的程式為vc6.0的開發環境,語言是:C和win32 api)。[@more@]一:引言:

你也許一直對金山詞霸的螢幕抓詞的實現原理感到困惑,你也許希望將你的鍵盤,滑鼠的活動適時的記錄下來,甚至你想知道木馬在windows作業系統是怎樣進行木馬dll的載入的…..其實這些都是用到了windows的鉤子函式。因此本文將對鉤子函式的相關知識進行闡述。當然,本文的目的並不是想透過此程式讓讀者去竊取別人的密碼,只是由於鉤子函式在windows系統中是一個非常重要的系統介面函式,所以想和大家共同的探討,當然本文也對怎樣建立動態連結庫(DLL)作了一些簡單的描述。(本文的程式為vc6.0的開發環境,語言是:C和win32 api)。
二:鉤子概述:

微軟的windowsX作業系統是建立在事件驅動的機制上的,也就是透過訊息傳遞來實現。而鉤子在windows作業系統中,是一種能在事件(比如:訊息、滑鼠啟用、鍵盤響應)到達應用程式前中途接獲事件的機制。而且,鉤子函式還可以透過修改、丟棄等手段來對事件起作用。
Windows 有兩種鉤子,一種是特定執行緒鉤子(Thread specific hooks),一種是全域性系統鉤子(Systemwide hooks)。特定執行緒鉤子只是監視指定的執行緒,而全域性系統鉤子則可以監視系統中所有的執行緒。無論是特定執行緒鉤子,還是全域性系統鉤子,都是透過SetWindowsHookEx ()來設定鉤子的。對於特定執行緒鉤子,鉤子的函式既可以是包含在一個.exe也可以是一個.dll。但是對於一個全域性系統鉤子,鉤子函式必須包含在獨立的dll中,因此,當我們要捕捉鍵盤響應時,我們必須建立一個動態連結庫。但是當鉤子函式在得到了控制權,並對相關的事件處理完後,如果想要該訊息得以繼續的傳遞,那麼則必須呼叫另一個函式:CallNextHookEx。由於系統必須對每個訊息處理,鉤子程式因此增加了處理的負擔,因此也降低了系統的效能。鑑於這一點,在windows ce中對鉤子程式並不支援。所以當程式完成並退出時,應當釋放鉤子,呼叫函式:UnhookWindowsHookEx。
下面我們將舉一個例子(捕捉鍵盤)來詳細的講解鉤子函式的程式設計。
三:程式的設計:

I:設定鉤子

設定鉤子是透過SetWindowsHookEx ()的API函式.
原形: HHOOK SetWindowsHookEx(int idHook,HOOKPROC lpfn,HINSTANCE hMod,DWORD dwThreadId)
idhook:裝入鉤子的型別.
lpfn: 鉤子程式的入口地址
hMod: 應用程式的事件控制程式碼
dwThreadId: 裝入鉤子的執行緒標示

引數:
idHook:
這個引數可以是以下值:
WH_CALLWNDPROC、WH_CALLWNDPROCRET、WH_CBT、WH_DEBUG、WH_FOREGROUNDIDLE、WH_GETMESSAGE、WH_JOURNALPLAYBACK、WH_JOURNALRECORD、WH_KEYBOARD、WH_KEYBOARD_LL、WH_MOUSE、WH_MOUSE_LL、WH_MSGFILTER、WH_SHELL、WH_SYSMSGFILTER。
對於這些引數,我不想一一加以解釋,因為MSDN中有關於他們的詳細註解。我只挑選其中的幾個加以中文說明。
WH_KEYBOARD:一旦有鍵盤敲打訊息(鍵盤的按下、鍵盤的彈起),在這個訊息被放在應用程式的訊息佇列前,WINDOWS將會呼叫你的鉤子函式。鉤子函式可以改變和丟棄鍵盤敲打訊息。
WH_MOUSE:每個滑鼠訊息在被放在應用程式的訊息佇列前,WINDOWS將會呼叫你的鉤子函式。鉤子函式可以改變和丟棄滑鼠訊息。

WH_GETMESSAGE:每次當你的應用程式呼叫一個GetMessage()或者一個PeekMessage()為了去從應用程式的訊息佇列中要求一個訊息時,WINDOWS都會呼叫你的鉤子函式。而鉤子函式可以改變和丟棄這個訊息。

II:釋放鉤子

鉤子的釋放使用的是UnhookWindowsHookEx()函式
原形:BOOL UnhookWindowsHookEx( HHOOK hhk )
UnhookWindowsHookEx()函式將釋放的是鉤子鏈中函式SetWindowsHookEx所裝入的鉤子程式。
hhk: 將要釋放的鉤子程式的控制程式碼。

III:鉤子程式

鉤子程式使用函式HookProc;其實HookProc僅僅只是應用程式定義的符號。比如你可以寫成KeyBoardHook.但是引數是不變的。Win32 API提供了諸如:CallWndProc、GetMsgProc、DebugProc、CBTProc、MouseProc、KeyboardProc、MessageProc等函式,對於他們的詳細講解,可以看MSDN我在此只講解一下KeyBoardHook的含義。
原形:LRESULT CALLBACK KeyBoardHook (int nCode, WPARAM wParam, LPARAM lParam)
說明:鉤子程式是一些依附在一個鉤子上的一些函式,因此鉤子程式只被WINDOWS呼叫而不被應用程式呼叫,他們有時就需要作為一個回撥函式(CALLBACK)。

引數說明:
nCode:鉤子程式碼,鉤子程式使用鉤子程式碼去決定是否執行。而鉤子程式碼的值是依靠鉤子的種類來定的。每種鉤子種類都有他們自己一系列特性的程式碼。比如對於WH_KEYBOARD,鉤子程式碼的引數有:HC_ACTION,HC_NOREMOVE。HC_ACTION的意義:引數wParam 和lParam 包含了鍵盤敲打訊息的資訊,HC_NOREMOVE的意義:引數wParam 和lParam包含了鍵盤敲打訊息的資訊,並且,鍵盤敲打訊息一直沒有從訊息佇列中刪除。(應用程式呼叫PeekMessage函式,並且設定PM_NOREMOVE標誌)。也就是說當nCode等於HC_ACTION時,鉤子程式必須處理訊息。而為HC_NOREMOVE時,鉤子程式必須傳遞訊息給CallNextHookEx函式,而不能做進一步的處理,而且必須有CallNextHookEx函式的返回值。
wParam:鍵盤敲打所產生的鍵盤訊息,鍵盤按鍵的虛擬程式碼。
lParam:包含了訊息細節。
注意:如果鉤子程式中nCode小於零,鉤子程式必須返回(return) CallNextHookEx(nCode,wParam,lParam);而鉤子程式中的nCode大於零,但是鉤子程式並不處理訊息,作者推薦你呼叫CallNextHookEx並且返回該函式的返回值。否則,如果另一個應用程式也裝入WH_KEYBOARD 鉤子,那麼該鉤子將不接受鉤子通知並且返回一個不正確的值。如果鉤子程式處理了訊息,它可能返回一個非零值去阻止系統傳遞該資訊到其它剩下的鉤子或者windows程式。所以最好在鉤子程式的最後都返回CallNextHookEx的返回值。

IV:呼叫下一個鉤子函式

呼叫下一個鉤子函式時使用CallNexHookEx函式。
原形:LRESULT CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam )
CallNexHookEx()函式用於對當前鉤子鏈中的下一個鉤子程式傳遞鉤子資訊,一個鉤子程式既可以在鉤子資訊處理前,也可以在鉤子資訊處理後呼叫該函式。為什麼使用該函式已在iii鉤子程式中的“注意”中,加以了詳細的說明。
hhk: 當前鉤子的控制程式碼
nCode: 傳送到鉤子程式的鉤子程式碼。
wParam:傳送到鉤子程式的值。
lParam:傳送到鉤子程式的值。
引數:
hhk: 當前鉤子的控制程式碼. 應用程式接受這個控制程式碼,作為先前呼叫SetWindowsHookE函式的結果
nCode: 傳送到鉤子程式的鉤子程式碼,下一個鉤子程式使用這個程式碼以此決定如何處理鉤子資訊
wParam:傳送給鉤子程式的wParam 引數值 ,引數值的具體含義與當前鉤子鏈的掛接的鉤子型別有關
lParam : 傳送給鉤子程式的wParam 引數值 ,引數值的具體含義與當前鉤子鏈的掛接的鉤子型別有關
返回值:返回值是鏈中下一個鉤子程式返回的值,當前鉤子程式必須返回這個值,返回值的具體含義與掛接的鉤子型別有關,詳細資訊請參看具體的鉤子程式描述。

V 建立一個動態連線庫(DLL)

當我們熟悉了以上的各個函式後,現在我們開始編寫一個動態連線庫(DLL)。在這兒我採用的是WIN32 DLL,而不是MFC DLL。而且以下所有的程式也都是採用C語言去編寫。這主要是因為使用WIN32 API能夠更詳細、更全面的控制程式的如何執行,而使用MFC,一些低階的控制是不可能實現的(當然,僅對該程式來說,也是可以使用MFC的)。

1:建立一個動態連線庫的.cpp檔案。比如我們現在建立一個名為hookdll.cpp的檔案。在hookdll.cpp的檔案中加上如下內容:
#include
#include "string.h"
#include "stdio.h"

HINSTANCE hInst;

#pragma data_seg("hookdata")
HHOOK oldkeyhook=0;
#pragma data_seg()

#pragma comment(linker,"/SECTION:hookdata,RWS")

#define DllExport extern "C"__declspec(dllexport)

DllExport LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam, LPARAM lParam );
DllExport void InstallHook(int nCode);
DllExport void EndHook(void);

BOOL WINAPI DllMain(HINSTANCE hInstance,ULONG What,LPVOID NotUsed)
{
switch(What)
{
case DLL_PROCESS_ATTACH:
hInst = hInstance;
break;
case DLL_PROCESS_DETACH:
break;
case DLL_THREAD_ATTACH:
break;
case DLL_THREAD_DETACH:
break;

}
return 1;
}

void InstallHook(int nCode)
{
oldkeyhook = SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyBoardProc,hInst,0);
}

DllExport LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam, LPARAM lParam )
{
WPARAM j;
FILE *fp;
if(lParam&0x80000000)
{
j = wParam;
fp=fopen("c:hookkey.txt","a");
fprintf(fp,"%4d",j);
fclose(fp);
}
return CallNextHookEx(oldkeyhook,nCode,wParam,lParam);
}
void EndHook(void)
{
UnhookWindowsHookEx(oldkeyhook);
}
這個動態連線庫的原始碼hookdll.cpp包含了鍵盤處理函式,設定鉤子,退出鉤子函式。並將鍵盤敲下的鍵以值的格式存入到c:hookkey.txt檔案中。以下是對該檔案的詳細的解釋。
使用包含在DLL的函式,必須將其匯入。匯入操作時透過dllimport來完成的,dllexport和dllimport都是vc(visual C++)和bc(Borland C++)所支援的擴充套件的關鍵字。但是dllexport和dllimport關鍵字不能被自身所使用,因此它的前面必須有另一個擴充套件關鍵字__declspec。通用格式如下:__declspec(specifier)其中specifier是儲存類標示符。對於DLL,specifier將是dllexport和dllimport。而且為了簡化說明匯入和匯出函式的語句,用一個宏名來代替__declspec.在此程式中,使用的是DllExport。如果使用者的DLL被編譯成一個C++程式,而且希望C程式也能使用它,就需要增加“C”的連線說明。#define DllExport extern "C"__declspec(dllexport),這樣就避免了標準C++命名損壞。(當然,如果讀者正在編譯的是C程式,就不要加入extern “C”,因為不需要它,而且編譯器也不接受它)。有了宏定義,現在就可以用一個簡單的語句就可以匯出函式了,比如:DllExport LRESULT CALLBACK KeyBoardProc(int nCode,WPARAM wParam, LPARAM lParam );DllExport void InstallHook(int nCode);DllExport void EndHook(void);
第一個#pragma 語句創造資料段,這裡命名為hookdata。其實也可以命名為您喜歡的任意的一個名稱。#pragma 語句之後的所有初始化的變數都進入hookdata段中。第二個#pragma語句是資料段的結束標誌。對變數進行專門的初始化是很重要的,否則編譯程式將把它們放在普通的未初始化的段中而不是放在hookdata中。
但是連結程式必須直到有一個hookdata段。我們可以在Project Setting(vc6.0) 對話方塊中選擇Link選項,選中HOOKDLL時在Project Options域(在Release 和Debug配置中均可),包含下面的連線語句:/SECTION:hookdata,RWS字母RWS是表明該段具有讀、寫、和共享屬性。當然,您也可以直接用DLL原始碼指定連結程式就像HOOKDLL.c那樣:#pragma comment(linker,"/SECTION:hookdata,RWS")。
由於有些DLL需要特殊的啟動和終止程式碼。為此,所有的DLL都有一個名為DllMain()的函式,當初始化或終止DLL時呼叫該函式。一般在動態連結庫的資原始檔中定義此函式。不過如果沒有定義它,則編譯器會自動提供預設的形式。
原型為:BOOL WINAPI DllMain(HINSTANCE hInstance,ULONG What,LPVOID NotUsed)
引數:
hInstance:DLL例項控制程式碼
What:指定所發生的操作
NotUsed:保留引數
其中What的值可以為以下值:
DLL_PROCESS_ATTACH:程式開始使用DLL
DLL_PROCESS_DETACH:程式正在釋放DLL
DLL_THREAD_ATTACH:程式已建立一個新的執行緒
DLL_THREAD_DETACH:程式已捨棄了一個執行緒
總的來說,無論何時呼叫DllMain()函式,都必須根據What的內容來採取適當的動作。這種適當的動作可以什麼都不做,但不是返回非零值。
DllMain()接下來的便是設定鉤子,鍵盤處理,和釋放鉤子。

2:建立標頭檔案
正如應用程式所使用的其它任何庫函式一樣,程式也必須包含dll內的函式的原型。所有得Windows程式都必須包含windows.h的原因。所以我們現在建立一個標頭檔案hookdll.h如下:
#define DllImport extern"C"__declspec(dllimport)
DllImport void InstallHook(int nCode);
DllImport LRESULT CALLBACK KeyBoardProc (int nCode,WPARAM wParam, LPARAM lParam );
DllImport void EndHook(void);
使用dllimport主要是為了使程式碼更高效,因此推薦使用它。但是在匯入資料時是需要dllimport的。當完成了上面的程式後,建一個專案工程,不妨為hookdll,然後將hookdll.c插入導專案工程中,編譯,則可以生成了hookdll.dll和hookdll.lib。

3:建立程式主檔案
我們在上面作的所有得工作都是為現在的主程式打得基礎。其實當我們完成了Dll檔案後,剩下的就是呼叫設定鉤子函式:InstallHook 。如果你對windows程式設計十分的熟悉,那麼你可以在你任何需要的時候來呼叫InstallHook。但是在你必須記住在你退出程式的時候你需要調EndHook以便釋放你所裝入的鉤子函式。現在我在建立了一個hookspy.cpp,並將生成好的hookdll.dll和hookdll.lib複製到從一個目錄下,並建立一個hookspy的專案工程。將hookspy.cpp,hookdll.dll,hookdll.lib,hookdll.h插入到專案工程中去。然後在建立windows視窗時就將鉤子設定,在退出程式時退出鉤子函式。比如:
case WM_CREATE:
InstallHook(TRUE);
break;
case WM_DESTROY: //terminate the program
EndHook();
PostQuitMessage(0);
break;
當然,如果大家需要我的原始碼,可與我聯絡,但是我認為重要的是技巧和原理,而不是原始碼。就像如果你知道了Windows程式設計原理,至於用什麼編譯工具都是可以的。

來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/79126/viewspace-983067/,如需轉載,請註明出處,否則將追究法律責任。

相關文章