📄 20.1.3 全局钩子.txt
字号:
20.1.3 全局钩子
到目前为止, InnerHook程序只能屏蔽当前进程的主线程的鼠标消息和键盘消息,如果想要屏蔽当前正在运行的所有进程的鼠标消息和键盘消息,那么安装钩子过程的代码必须放到动态链接库中去实现。根据前面的介绍,如果想让安装的钩子过程与所有进程相关,应该将 SetWindowsHookEx函数的第四个参数设置为 0,并将它的第三个参数指定为安装
钩子过程的代码所在的DLL的句柄。下面首先编写一个安装钩子过程的 DLL。利用 VC++新建一个 Win32 Dynamic Link Library工程,工程取名为: Hook,如图20.2所示。
图 20.2新建一个 Win32 Dynamic Link Libr町工程
然后在创建工程向导的第一步选择建立一个空的DLL工程,如图20.3所示。单击【Finish】按钮, AppWizard就为我们创建了一个空的Win32DLL工程。然后选择【File\New..】菜单项,打开新建对话框,在Files选项卡上选择C++ Source
File,并在File编辑框中输入源文件名:Hook,如图20.4所示。接下来就在Hook.cpp这一源文件中添加安装钩子过程的代码,结果如例20-11所示。
图 20.3新建 Wm32 Dynamic Link Library Stepl 图 20.4新建一个源文件 .tl20伽 11
#include <windows .h>
HHOOK g_hMouse=NULL;
//鼠标钩子过程
LRESULT CALLBACK MouseProc( int nCode , WPARAM wPararn , LPARAM lPararn)
return 1;
//安装鼠标钩子过程的函数
void SetHook ( )
g_hMouse=SetWindowsHookEx(WH_MOUSE, MouseProc, GetModuleHandle("Hook") ,
0) ;
上述如例 20-11所示代码中,首先包含了 Windows头文件,定义了一个全局变量: ιhMouse,用来保存安装的鼠标钩子句柄。接着定义鼠标钩子过程: MouseProc,该过程直接返回一个非 0值,表示对鼠标消息己经处理了,也就是说,该钩子将屏蔽所有的鼠标消息。最后定义函数 : SetHook,在此函数中调用 SetWindowsHookEx函数安装鼠标钩子过程,该函数的第四个参数设置为 0,这样安装的钩子过程就和运行在同一桌面上的所有进程相关了。该函数的第三个参数要求指定安装钩子过程所在的 DLL模块的句柄。有两种方式可以得到这一句柄,一种是为 DLL程序提供一个 DllMain函数,当第一次加载 DLL时,系统会调用这个函数,井传递当前 DLL模块的句柄。因此可以在 Hook这一 DLL中提供一个动态链接库入口函数: DIIMain,并定义一个全局的实例变量:ιhInst,然后在 DlIMaio函数中保存系统传递来的 DLL模块句柄,之后在调用 SetHook函数时就可以使用这个句柄了。具体代码如例 20-12所示。
锣tl 20-12
HINSTANCE g_hlnst;
BOOL WINAPI DllMain( HINSTANCE hinstDLL ,
DWORD fdwReason , LPVOID lpvReserved) g_hlnst=hinstDLL ; void SetHook ( ) g_hMouse=SetWindowsHookEx(WH_MOUSE , MouseProc , g_hlnst , O) ;
另一种方式就是如例 20-11所示代码中使用的方法,调用 GetModuleHandle函数来得到指定的 DLL模块句柄。如果指定的模块己经被映射到当前进程的地址空间中,那么该函数将返回该指定模块的句柄。 GetModuleHandle函数的声明形式如下所示 :
HMODULE GetModuleHandle( LPCTSTR lpModuleName );
该函数有一个参数: lpModuleName,指向一个以 NULL为终止符的字符串,该字符串指定了想要获取其模块句柄的模块名称,该模块可以是一个 .exe文件,也可以是一个 .Dll文件。如果不指定扩展名,系统会自动加上 .dll作为其扩展名。 GetModuleHandle函数将返回指定模块的句柄,返回值是 HMODULE类型。前面已经介绍过, HMODULE和 HINSTANCE类型可以通用。
目标:这里介绍的两种获取模块句柄的实现方式可任选一种。
下面,为 Hook程序定义一个模块定义文件,方法是选择【 File\New..】菜单项,打开新建对话框,在 Files选项卡上选择 Text File,并在 File编辑框中输入文件名 : Hook.def, 如图 20.5所示。
图 20.5添加一个模块定义文件然后编辑该文件,结果如例 20-13所示。例 20-13
上述例 20-13所示代码的第一行语句指定 DLL内部名称,接着用 EXPORTS关键字指定该 DLL导出函数的名称。前面已经提到,从 DLL中导出的函数有一个序号,如果想自己指定序号,而不是使用由系统安排的序号,可以在导出函数符号名后面加上@,接上一个序号数字,这个数字就是函数导出时的序号。
利用 Build命令生成 Hook.DLL程序。但是它是不能够独立运行的,如果想让所定义的钩子过程起作用,必须由某个客户程序调用 Hook.DLL的 SetHook函数,因此必须编写一个客户程序,让它加载这个 DLL,然后去调用该 DLL提供的 SetHook函数,这时就会调用 SetWindowsHookEx函数,从而设置一个与当前所有进程相关的全局钩子。在上述这些操作完成之后,如果一个进程中的某个线程给一个窗口发送消息,操作系统在检查这个线程是否己经安装了钩子过程时,它会发现该线程己经安装了一个全局的钩子过程,于是就要找到包含这个钩子过程的 DLL,将这个 DLL映射到该进程的地址空间中,井调用相应的钩子过程函数,然后将鼠标消息传递给该钩子过程。
下面,我们再新建一个基于对话框的工程,工程取名为: HookTest,用来测试上面所编写的 Hook程序中设置全局钩子过程的代码。为了在 CHookTestDlg类中调用 Hook.DLL中的 SetHook函数,首先应在调用前进行如下声明,表明 SetHook这个符号是从 DLL的.lib文件中输入的:
_declspec(dllimport) void SetHook();
接着,为 HookTest程序添加与 Hook.dll文件的链接,方法是选择【Project\Settings.. .>菜单项,打开 HookTest工程设置对话框,切换到 Link选项卡,在 Objec tJlibrary modules 编辑框中输入 Hook.dll的导入库文件: hook .lib,如图 20.6所示(注意该文件的路径)。
然后就可以在 CHookTestDlg类的初始化函数: OnInitialDialog函数中调用 DLL中的 SetHook函数了,代码如例 20-14所示。
图 20.6为 HookTest工程添加 DLL的导入库
例 20-14
BOOL CHookTestDlg : :On工口 itDialog () CDialog : : On工 nitDialog() ;
第20
HOOK
11 TODO : Add extra initialization here
SetHook();
return TRUE ; 11 return TRUE unless you set the focus to a control
另外,还需要将先前产生的 Hook.dll文件复制到 HookTest这个客户程序目录下,让它在执行时能找到这个库文件。因为刚才在指定导入库文件 (Hook.lib)时,直接指定了该文件的路径,所以不需要复制这个Hook.lib文件了。
Build井运行HookTest程序,这时会发现鼠标消息己经被屏蔽掉了。当切换到其他程序窗口(可以利用Alt+Tab组合键实现),想用鼠标进行一些操作时,也会发现鼠标按键不起作用了。这就说明先前设计的全局钩子起作用了,也就是说,现在所有进程的鼠标消息都被屏蔽掉了。这时可以再利用Alt+Tab组合键切换回HookTest程序窗口,使该窗口成为激活状态,然后按下键盘上的空格或回车键,即可让HookTest程序退出。之后就可以发现鼠标按键又重新起作用了。
这里顺便为读者介绍DLL的调试。如果希望跟踪 SetHook函数的内部执行情况,可以在上述CHookTestDlg类的OnInitiaIDialog函数中对 SetHook函数的调用处 (即例20-14所示代码中加灰显示的那行代码)设置一个断点,然后调试运行HookTest程序,当程序在设置的断点处暂停后(如图20.7所示),选择【Debug\Step into】菜单项,这时程序就会跳转到 SetHook函数的内部,而后者是在 Hook.DLL中实现的,因此这时程序就进入到 Hook.DLL中的代码 (如图 20.8所示)。然后就可以单步执行程序。在平时编程时,如果需要对编写的 DLL及其客户程序进行联调时,就可以使用这种方法。
图 20.7程序暂停于设置的断点处
图 20.8进入到Hook.dll中的SetHook函数定义处
接下来,我们在Hook.DLL中再安装一个键盘钩子。重新在VC++开发环境中打开Hook工程,在Hook.cpp文件中再定义一个HHOOK类型的全局变量:ιhKeyboard,以保存将要安装的键盘钩子句柄,同时对它进行初始化,定义代码如下:
HHOOK g_hKeyboard=NULL;
这里,不能让这个键盘钩子过程简单地返回一个非0值,并不是说这样做是错误的,而是因为现在安装的是一个全局钩子,是与机器上当前运行的所有进程相关的钩子,先前已经把所有鼠标消息都屏蔽了,如果这时再把所有键盘消息也屏蔽了,这个程序就没有办法退出,同时在当前桌面上运行的其他程序也无法退出。因此,这时在键盘钩子过程中应该留下一个"后门",即当我们按下某个按键时,程序能够退出。本例仍利用F2键退出程序,这就需要在键盘钩子过程中进行判断,如果当前按下的是F2键,那么就退出程序:否则屏蔽该按键消息。根据前面的知识,我们知道,为了退出程序,应该向调用进程的主窗口发送一个WM_CLOSE消息,但是在动态链接库中如何能够得到调用进程的主窗口句柄呢?我们发现没有一个合适的现成函数能够提供这一功能,只能另想其他的方法。其实,办法非常简单,可以为Hook.DLL的导出函数: SetHook增加一个窗口句柄类型(HWND)的参数。当调用进程加载Hook.DLL后,调用该DLL提供的SetHook函数时,让该调用进程将它自己的窗口句柄通过这个新增加的参数传递进来,我们只需要在Hook.DLL中定义一个全局的窗口句柄变量,将传递进来的调用进程的主窗口句柄保存起来就可以了。因此,在Hook.cpp文件中增加下述定义:
HWND g_hWnd;
并修改 SetHook函数的定义,为其增加一个 HWND类型的参数,然后在其内部保存该参数值,并添加安装键盘钩子过程的代码,结果如例20-15所示。
.~ 20-15
void SetHook(HWND hwnd)
{
g_hWnd=hwnd ;
g_hMouse=SetWindowsHookEx(WH_MOUSE , MouseProc , GetModuleHandle( "Hook " ) , 0) ; g_hKeyboard=SetWindowsHookEx(WH_KEYBOARD, KeyboardProc , GetModuleHandle( "Hook" ) , 0) ;
然后编写键盘钩子过程代码,结果如例20-16所示。
.lJ 20-16
LRESULT CALLBACK KeyboardProc(int code, WPARAM wParam, LPARAM lParam)
{
if(VK_ F2==wParam)
SendMessage(g_hWnd,WM_CLOSE, 0, 0) ;
UnhookWindowsHookEx(g_hMouse) ;
UnhookWindowsHookEx(g_ hKeyboard);
return 1 ;
在上述如例20-16所示键盘钩子过程中,如果判断用户当前按下的是F2键,那么就调用 SendMessage函数向调用进程的主窗口句柄发送WM_CLOSE消息,让调用进程退出,然后应该移除所安装的钩子过程。
重新生成Hook.DLL.并将新产生的Hook.dll文件复制到HookTest客户应用程序的目录下。然后修改HookTest客户应用程序,首先将SetHook函数的声明修改为如下代码:
_declspec(dllimport} void SetHook(HWND hwnd};
接着修改OnInitDialog函数中对SetHook函数的调用,结果如例20-17所示。
锣IJ 20-17
BOOL CHookTestDlg : :OnlnitDialog() CDialog : :OnlnitDialog(};
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -