消息钩子在Windows编程中有着非常广泛的应用,它可以任意拦截Windows系统,这个以消息为驱动的系统中的绝大多数消息类型。一方面这给编程者带来了巨大的灵活性,另一方面也埋下了巨大隐患,大多数窃密软件都使用这种方法。此篇文章给您提供一种钩子的反拦截方法,希望对您有所帮助。文章中使用了API钩子,您之前必须对此技术有一定了解。
为求完整,文章分为两部分,第一部分为消息钩子的使用,熟悉此技术的读者可以直接跳过此节。第二部分为消息钩子的反拦截。
一、消息钩子的使用
消息钩子分为本地(local)和远程(remote)两种(两个local system-wide hook例外,无关主题,不多说了)。local类型的钩子函数只能拦截本进程的消息。能够拦截本进程以外的消息的钩子,都是remote类型。remote类型的钩子必须放在DLL里面。下面以remote类型为例,通过安装键盘钩子介绍其使用。
1、首先建立DLL,在头文件中添加如下代码。
#ifdef KM_EXPORTS #define KM_API __declspec(dllexport) #else #define KM_API __declspec(dllimport) #endif KM_API BOOL HookStart();//安装钩子 KM_API BOOL HookStop();//卸载钩子
2、在.cpp文件中添加代码
#pragma data_seg("Shared") HHOOK g_hhookKey=NULL; #pragma data_seg() #pragma comment(linker,"/SECTION:Shared,RWS")
g_hhookKey为键盘钩子的句柄,为确保此数值在所有实例中均保持不变,将其存放于此模块所有实例的共享数据区,若在exe程序中按此格式添加一int 变量 appNum,在程序启动时appNum++,则可以通过访问此变量的数值,确定有多少个exe的实例,当然这种方法也可以替代同步对象用于只启动一个实例。
HINSTANCE g_hinstDll=NULL; //添加全局变量用于记录此DLL模块的句柄 BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: g_hinstDll=(HINSTANCE)hModule;//在DLL加载时对全局变量赋值 .................. } } LRESULT KeyHookProc(int nCode,WPARAM wParam,LPARAM lParam)//键盘钩子的过滤函数 { ..................... return::CallNextHookEx(g_hhookKey,nCode,wParam,lParam);//*****请留意此行代码***** } BOOL HookStart()//安装钩子 { g_hhookKey=::SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)KeyHookProc,g_hinstDll, ::GetWindowThreadProcessId(::FindWindow(NULL,"被监视的窗口的标题"),NULL) ); return (g_hhookKey!=NULL); } BOOL HookStop()//卸载钩子 { BOOL ret; if(g_hhookKey!=NULL) ret=::UnhookWindowsHookEx(g_hhookKey); g_hhookKey=NULL; return ret; }
只要在exe程序中调用HookStart函数,就可以监视某一窗口的键盘消息,若此窗口为QQ的密码框,你的密码就泄漏了。
二、消息钩子的反拦截
请留意前面带*号注释的代码,其中传入了钩子的句柄g_hhookKey,只要使用API钩子将CallNextHookEx函数替换,并在替换函数中将其卸载,消息钩子就完蛋了。同时,还要保证本进程安装的钩子不被卸载,其中既可能有local类型的还可能有remote类型的。不要以为自己没有在程序中安装钩子,程序中就一定没有安装钩子,在MFC4版本中,MFC会自己装一个local类型的钩子,MFC7版本中好像没了。好了,下面介绍其实现。
1、建立DLL,在头文件中添加如下代码。
#ifdef HOOKFORBID_EXPORTS #define HOOKFORBID_API __declspec(dllexport) #else #define HOOKFORBID_API __declspec(dllimport) #endif HOOKFORBID_API int fnHookForbid(void);//在exe程序中调用此函数,使DLL加载 HOOKFORBID_API bool AddHhook(HHOOK Hhook);//若exe中安装remote类型消息钩子,将其句柄添加 HOOKFORBID_API bool DelHhook(HHOOK Hhook);//在exe中卸载remote类型消息钩子时,删除其句柄
2、在.cpp文件中添加代码
CArray<HHOOK,HHOOK> array;//用于记录本进程安装的钩子的句柄 ////////////////////////////////////////////////////////////////////////////// int fnHookForbid(void) { return 1; } bool AddHhook(HHOOK Hhook) { array.Add(Hhook); return true; } bool DelHhook(HHOOK Hhook) { bool ret=false; for(int i=0;i<array.GetSize();i++) { if(array.GetAt(i)==Hhook) { array.RemoveAt(i); ret=true; break; } } return ret; } //////////////////////////////////////////////////////////////////////////////
下面的代码用于API替换,其中用到了CAPIHook 类,《Windows 核心编程》(Jeffrey Richter著)一书中有源代码。使用其它开发包也可以实现此功能。
////////////////////////////////////////////////////////////////////////////// typedef HHOOK (WINAPI *PFNSETWINDOWSHOOKEX)( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ); typedef LRESULT (WINAPI *PFNCALLNEXTHOOKEX)( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam ); ////////////////////////////////////////////////////////////////////////////// extern CAPIHook g_SetWindowsHookExA; extern CAPIHook g_SetWindowsHookExW; extern CAPIHook g_CallNextHookEx; ////////////////////////////////////////////////////////////////////////////// //此函数用于替换SetWindowsHookEx函数的ASCII版本SetWindowsHookExA HHOOK WINAPI Hook_SetWindowsHookExA( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ) { HHOOK nResult =0; nResult = ((PFNSETWINDOWSHOOKEX)(PROC) g_SetWindowsHookExA)( idHook, lpfn, hMod, dwThreadId ); //若在本进程中安装了local类型钩子,记录其句柄 if(hMod==NULL) array.Add(nResult); return(nResult); } //此函数用于替换SetWindowsHookEx函数的UNICODE版本SetWindowsHookExW HHOOK WINAPI Hook_SetWindowsHookExW( int idHook, HOOKPROC lpfn, HINSTANCE hMod, DWORD dwThreadId ) { HHOOK nResult =0; nResult = ((PFNSETWINDOWSHOOKEX)(PROC) g_SetWindowsHookExW)( idHook, lpfn, hMod, dwThreadId ); //若在本进程中安装了local类型钩子,记录其句柄 if(hMod==NULL) array.Add(nResult); return(nResult); } //此函数用于替换CallNextHookEx函数,此函数只有一个版本 LRESULT WINAPI Hook_CallNextHookEx( HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam) { LRESULT nResult =0; nResult = ((PFNCALLNEXTHOOKEX)(PROC) g_CallNextHookEx)( hhk, nCode, wParam, lParam ); //在数组中查找句柄,若找不到,将其卸载 bool bfind=false; for(int i=0;i<array.GetSize();i++) { if(array.GetAt(i)==hhk) { bfind=true; break; } } if(!bfind) { UnhookWindowsHookEx( hhk ); } return (nResult); } ////////////////////////////////////////////////////////////////////////////// //使用CAPIHook 类对函数进行替换 CAPIHook g_SetWindowsHookExA("User32.dll", "SetWindowsHookExA", (PROC) Hook_SetWindowsHookExA, true); CAPIHook g_SetWindowsHookExW("User32.dll", "SetWindowsHookExW", (PROC) Hook_SetWindowsHookExW, true); CAPIHook g_CallNextHookEx("User32.dll", "CallNextHookEx", (PROC) Hook_CallNextHookEx, true);
到了这里,所有工作都完成了,只要在exe程序中调用fnHookForbid函数,并在安装remote类型钩子时调用AddHhook函数记录其句柄,卸载时调用DelHhook函数删除句柄就万事ok了。
一点不足:这种方法可以有效屏蔽消息钩子对信息安全的威胁。可以使Spy++失效。然而,由于是在CallNextHookEx函数中卸载钩子,因此,钩子函数总是会被调用一次。还有一件非常费解的事,金山词霸总能够正常取词,不知道词霸是怎么做到的。
本人并非专业程序员, 若此方法存在任何错误或隐患,敬请批评指出,请不要在帖子上损我。
呵呵!假如我的钩子是这么用的:
FUN_SETWINDOWSHOOKA *pFn = (FUN_SETWINDOWSHOOKA *) ::GetProcAddress(::GetModuleHandle("kernel32.dll"), "SetWindowsHookA"); pFn(...);
你的方法还是屏蔽不了哦!不信试验一下!记得给分哦!(该法为:“反反API钩子大法”)
当然!同理也可以绕过API钩子!有同样兴趣的人记得发消息给我哦!
首先声明一下:我拦截的是消息钩子,如果安装钩子时考虑到了反卸载则不在讨论之内。
其次:上述方法不可靠,对CAPIHook类进行更改,可以实时对地址进行替换,就象消息钩子被调用次序的不确定性一样,到时候没法确定那个替换函数被调用了。