標籤:des http 使用 os 檔案 io 資料 for
一:引言:
你也許一直對金山詞霸的螢幕抓詞的實現原理感到困惑,你也許希望將你的鍵盤,滑鼠的活動適時的記錄下來,甚至你想知道木馬在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 <windows.h>
#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://hook//key.txt","a");
fprintf(fp,"%4d",j);
fclose(fp);
}
return CallNextHookEx(oldkeyhook,nCode,wParam,lParam);
}
void EndHook(void)
{
UnhookWindowsHookEx(oldkeyhook);
}
這個動態串連庫的原始碼hookdll.cpp包含了鍵盤處理函數,設定鉤子,退出鉤子函數。並將鍵盤敲下的鍵以值的格式存入到c:/hook/key.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;