出处: CSDN 作者: xstring
不知道大家是通过什么开始对钩子(Hook)有了解的,我是看过Jeffrey Richter的《WINDOWS 高级编程指南》(新版的中文译名为《Windows核心编程》)。在这本书里作者介绍了三种将代码注入其他进程的方法,其中一种就是使用的全局消息钩子。我就是从这本书里对全局钩子有了最初的认识。 大家应该都知道,全局消息钩子要依赖于一个DLL才能够正常工作。于是呢,我也就理所当在地认为全局钩子都要依赖于一个DLL才能正常工作的,我想大部分人肯定和我一样也这么认为的。 但实际上不是这样的。有某些全局钩子可以不依赖于任何DLL而正常工作的。这些钩子包括,WH_JOURNALPLAYBACK,WH_JOURNALRECORD,WH_KEYBOARD_LL,WH_MOUSE_LL。为什么这些钩子可以不依赖于DLL而正常工作呢?我们可以从MSDN中得到答案,MSDN中对于这四种钩子都这样的描述“This hook is called in the context of the thread that installed it.”,翻译成中文意思是钩子函数的调用是在安装钩子的线程上下文中进行的,说得更明白些,意思就是这些钩子是在哪个线程当中安装的,其钩子函数就在哪个线程中执行。所以使用这四种钩子是达不到代码注入的效果的,当然也就可以不依赖于任何DLL了。MSDN中只对个别钩子指出了必须还是没有必要使用DLL。 下面是我给出的一个底层键盘钩子的代码示例,当然是不需要DLL的。 /* kbhook.cpp */ #define _WIN32_WINNT 0400 #define STRICT #define WIN32_LEAN_AND_MEAN #include <stdio.h> #include <stdlib.h> #include <windows.h> DWORD g_main_tid = 0; HHOOK g_kb_hook = 0; BOOL CALLBACK con_handler (DWORD) { PostThreadMessage (g_main_tid, WM_QUIT, 0, 0); return TRUE; }; LRESULT CALLBACK kb_proc (int code, WPARAM w, LPARAM l) { PKBDLLHOOKSTRUCT p = (PKBDLLHOOKSTRUCT)l; const char *info = NULL; if (w == WM_KEYDOWN) info = "key dn"; else if (w == WM_KEYUP) info = "key up"; else if (w == WM_SYSKEYDOWN) info = "sys key dn"; else if (w == WM_SYSKEYUP) info = "sys key up"; printf ("%s - vkCode [%04x], scanCode [%04x]\n", info, p->vkCode, p->scanCode); // always call next hook return CallNextHookEx (g_kb_hook, code, w, l); }; int main (void) { g_main_tid = GetCurrentThreadId (); SetConsoleCtrlHandler (&con_handler, TRUE); g_kb_hook = SetWindowsHookEx ( WH_KEYBOARD_LL, &kb_proc, GetModuleHandle (NULL), // 不能为NULL,否则失败 0); if (g_kb_hook == NULL) { fprintf (stderr, "SetWindowsHookEx failed with error %d\n", ::GetLastError ()); return 0; }; // 消息循环是必须的,想知道原因可以查msdn MSG msg; while (GetMessage (&msg, NULL, 0, 0)) { TranslateMessage (&msg); DispatchMessage (&msg); }; UnhookWindowsHookEx (g_kb_hook); return 0; };
----------------------------------- 作者:未知 来源:从互联网收集整理并转载 发布时间:2005-3-6 0:49:30
下面我就把以前做过的一个简单的全程键盘钩子分析一下。 钩子[以下简称Hook]是应用程序在Microsoft Windows 消息处理过程中设置的用来监控消息流并且处理系统中尚未到达目的窗口的某一类型消息过程的机制。如果Hook过程在应用程序中实现,若应用程序不是当前窗口时,该Hook就不起作用;如果Hook在DLL中实现,程序在运行中动态调用它,它能实时对系统进行监控。根据需要,我们采用的是在DLL中实现Hook的方式[关于HOOK更详细的资料请查阅资料]。 在VC中新建一Win32 Dynamic-Link Library 工程,工程名为KBLock。AppWizard会生成相关文件,编译生成的KBLock.cpp: #include "stdafx.h" #include "KBLock.h" HHOOK hhkHook=NULL; //定义钩子句柄 HINSTANCE hInstance=NULL; //程序实例 //下面的DLLMain相当于Win32程序中的WinMain函数,是入口点 BOOL APIENTRY DllMain( HANDLE hModule, DWORD ul_reason_for_call, LPVOID lpReserved ) { switch (ul_reason_for_call) { case DLL_PROCESS_ATTACH: case DLL_THREAD_ATTACH: case DLL_THREAD_DETACH: case DLL_PROCESS_DETACH: break; } hInstance=(HINSTANCE)hModule; //得到DLL实例 return TRUE; } //这是处理键盘消息的主要函数,在其中进行禁止操作 LRESULT CALLBACK HookProc(int nCode,WPARAM wParam,LPARAM lParam) { if (nCode < 0) { return CallNextHookEx(hhkHook,nCode,wParam,lParam); } if (nCode != HC_ACTION) { return CallNextHookEx(hhkHook,nCode,wParam,lParam); } //给出提示:键盘已经被锁定,要进行判断,看是否已有提示窗口,否则会弹个没完 if (!::FindWindow(0, "KeyBoard Locked")) { ::MessageBox(0,"键盘已经锁定!!!","KeyBoard Locked",MB_OK); } return 1; //没有return CallNextHookEx(hhkHook,nCode,wParam,lParam)则不会把消息//传递下去,所以我们的键盘就不起作用了 } // This is an example of an exported variable //导出函数:启动键盘锁定 BOOL EnableKeyboardCapture() { if(!(hhkHook=SetWindowsHookEx(WH_KEYBOARD,(HOOKPROC)HookProc,hInstance,0))) return FALSE; return TRUE; } //导出函数:解除键盘锁定 BOOL DisableKeyboardCapture() { return UnhookWindowsHookEx(hhkHook); } 上面就是DLL中最重要的代码,当然要使DLL能正常工作还要编辑KBLock.h文件: __declspec(dllexport) BOOL EnableKeyboardCapture(); //加载钩子 __declspec(dllexport) BOOL DisableKeyboardCapture(); //卸载钩子 再编辑KBLock.def ; KBLock.def : Declares the module parameters for the DLL. LIBRARY "KBLock" DESCRIPTION 'KBLock Windows Dynamic Link Library' EXPORTS ; Explicit exports can go here EnableKeyboardCapture @1 DisableKeyboardCapture @2 这样我们用Depends.exe查看这个DLL时,就会发现这两个导出函数了。 DLL方面的工作已经完成,这样我们就可以在程序中调用它了。 虽然DLL是由VC开发的,但调用它的前台程序可以用任何其它支持DLL调用的语言如:VB、VC、DELPHI、Win32asm实现,下面还是以VC为例,实现DLL的调用。 建一基于Dialog的工程,在其中加入两个按钮:“Lock KeyBoard”“UnLock” 在CexeDlg类中加入一个成员函数: /*sign = TRUE 锁定 sign = FALSE解锁*/ BOOL CExeDlg::KBLock(BOOL sign) { hDLL=::LoadLibrary(_T("KBLock.dll")); //加载DLL if (hDLL!=NULL) {loadhook=(LOADHOOK)::GetProcAddress (hDLL,"EnableKeyboardCapture"); unloadhook=(UNLOADHOOK)::GetProcAddress (hDLL,"DisableKeyboardCapture");
if(loadhook==NULL||unloadhook==NULL) {::MessageBox(0,"对不起,本功能不能使用!!!","Somthing Wrong",MB_OK); return 0; } if(sign) loadhook(); else { unloadhook(); ::FreeLibrary(hDLL); } return 1; } ::MessageBox(0,"动态库加载失败!!!","Somthing Wrong",MB_OK); return 0; } 其中用到了事先定义好的全局变量: typedef BOOL (CALLBACK *LOADHOOK)(); typedef BOOL (CALLBACK *UNLOADHOOK)(); HINSTANCE hDLL=NULL; LOADHOOK loadhook; UNLOADHOOK unloadhook;
当然这里需要把dll文件放到相应的目录才行的。。
全局钩子的一定要用DLL吗? 这要从头说起,在98下,内存划分为高2G和低2G,这两部分的作用就不用我罗嗦了吧,基本上和2k是差不多的。但是,有一个区别,对于dll,9x可能出于效率的考虑,把它装在高2G,这样可以在多个进程之间共享代码,所以像那3个著名的dll,都是在高2G里,所有进程使用同一份拷贝。这就使得全局api hook比较方便,只要把dll加载到高2G,用一个简单的jmp在api入口的地方跳到我们的hook dll里面,处理完了再跳回去即可。之所以做成dll,是因为dll可以方便地加载于高2G的地址空间,现在你明白了?只要能把一个模块加载到高2G的空间去,不一定要做成dll。 |