📄 20.1.3 全局钩子.txt
字号:
…..
11 TODO : Add extra initialization here
SetHook(m_hWnd) ;
return TRUE; 11 return TRUE unless you set the focus to a control
Build并运行 HookTest程序,将会发现鼠标消息被屏蔽了,当按下键盘的空格或回车键时, HookTest程序也没有反应。切换到其他程序的窗口下,发现也是同样的效果。也就是说,这时,当前运行的所有进程的鼠标消息和键盘消息都被屏蔽了。我们可以切换回 HookTest程序窗口,然后按下F2键,该程序就会退出,鼠标和键盘按键又都恢复正常了。
接下来,我们希望在程序切换时不能看到其他程序的窗口,即让用户始终看到的都是 HookTest程序的窗口。为了达到这样的效果,可以把 HookTest程序的窗口设置为最顶层窗口,并将该窗口的大小设置为屏幕的大小。这样,在程序运行时,用户就不能看到其他程序的窗口了。要完成这个功能,可以调用 SetWindowPos函数,相信读者对此函数并不陌生,之前我们已经用过它了。
因此在CHookTestDlg类的OnInitDialog函数中,在调用 SetHook函数之前添加下述如例20-18所示代码中加灰显示的部分。
BOOL CHookTestDlg: :OnlnitDialog() { CDialog::OnlnitDialog();
…...
11 TODO: Add extra initialization here
int cxScreen, cyScreen;
cxScreen=GetSystemMetrics(SM_CXSCREEN);
cyScreen=GetSystemMetrics(SM_CYSCREEN);
SetWindowPos(&wndTopMost, 0, 0, cxScreen, cyScreen, SWP_SHOWWINDOW) ;
SetHook(m_hWnd) ;
return TRUE; // return TRUE unless you set the focus to a control
这里应该将窗口的宽度和高度设置为屏幕的宽度和高度,为了得到屏幕的宽度和高度,需要调用以前用过的 GetSystemMetrics函数。另外, SetWindowPos函数的第一个参数应该设置为 wndTopMost,即把当前窗口设置为最顶层窗口。
Build并运行 HookTest程序,可以发现该程序的窗口占据了整个屏幕,当切换到其他窗口时,发现虽然 HookTest程序处于不激活的状态(它的标题栏变灰了),但是在屏幕上仍然看不到其他程序的窗口。因为我们创建的 HookTest程序窗口是一个顶层窗口,位于所有其他窗口之上。当我们在 HookTest程序窗口处于不激活的状态下按下 F2键时,将会发现该程序没有任何反应。当切换到这个窗口上时,即 HookTest程序窗口处于激活状态下,再按下F2键, HookTest程序将会退出。
前面的内容已经提到,当 DLL被多个进程使用时,这些进程可以共享 DLL的代码和数据。因此,对于我们在 Hook.DLL中定义的全局句柄变量 :ιhWnd,应该是被所有进程所共享的。这样的话,在切换到其他进程窗口后,当调用 SendMessage给 ιhWnd所表示的进程窗口发送一个 WM CLOSE消息时, HookTest程序也应该能够退出,然而,上面我们己经通过实践发现这时 HookTest程序并没有退出。让我们再回想一下在上一章中关于动态链接库被多个进程访问时的情形。图 20.9是上一章中己经见过的图,是两个进程访问同一个 DLL时的情形,它们共享同一份代码和一份数据。既然它们是共享的,那么对所定义的全局变量也应该是共享的。但是实际上通过先前的实验,发现它们并没有共享。因为如果全局变量是共享的,那么当切换到其他进程时,全局变量 :ιhWnd保存的仍应该是 HookTest程序的主窗口句柄,这时一旦按下F2键, HookTest程序还是应该能够退出的。但是情况并非如此。
图 20.9两个进程访问同一个 DLL时的情形
我们可以详细地分析一下这种情况,如果多个进程可以共享同一份可写入的数据的话,这会造成很严重的后果。假设 DLL中数据页面中有一个指针类型的变量,如果第一个进程修改了这个变量的地址值,第二个进程如果能共享这个变量的话,它就很危险了,因为该变量的地址值已经被第一个进程修改了,其修改的地址对第二个进程来说可能是其他数据所占据的内存地址,所以,这就有可能导致潜在的错误。为了解决这一问题,即关于可写入的数据的改变问题, Windows 2000采用了写入时复制机制。例如图 20.9所示的调用情况, DLL有一个数据可被两个进程所共享,如果第 2个进程想修改 DLL数据页面 2上的数据时,操作系统会分配一个新的页面,并将数据页面 2上的数据复制一份到这个新页面中,接下来将断开数据页面 2到第 2个进程数据空间的映射,将新的页面映射到第 2个进程的地址空间,结果如图 20.10所示。
图 20.10 Windows 2000采用的写入时复制机制
这样,对第 2个进程来说,它改变的实际上是新页面上的数据。而对第 1个进程来说,它访问的仍是 DLL中先前的原始数据。这就是 Windows 2000采用的写入时复制机制。在改变数据之前,两个进程仍然是共享同一份数据。井且对-个只读数据来说,也是在多个进程间共享的。但要注意的是, Window 98采用的不是这种机制,在 Windows 98下,对于可写入的数据,每个进程都拥有一个可写入数据的复制。
对 HookTest程序来说,当调用 SetHook函数时,全局变量 :m_hWnd发生了改变,正因为它发生了改变,所以在 Windows 2000系统下,发生了写入时复制,所以为调用进程 C HookTest)重新分配了一个页面,并且随后我们对 g_hWnd这一全局变量设置的值就是在这个新页面中完成的。而切换到其他进程时,它们仍保存的是该全局变量的原始数据。因此,这时按下 F2键,并不能使 HookTest程序退出。
如果用户希望在 HookTest程序运行时,即使切换到其他进程的情况下,按下 F2键也能使 HookTest程序退出,这应该如何实现呢?实现这一功能的方法是:为 Hook.dll创建一个新的节,并将此节设置为一个共享的节,然后将全局变量: g_hWnd放到此节中,让该全局变量在多个进程问共享。
首先,我们可以利用 dumpbin命令的 Headers选项查看一下当前 Hook.DLL中各节的
信息列表,部分信息如图 20.11所示。
在输出的信息中,可以看到 Hook.dll有一个名称为" .text"的节,编译时编译器会把所有代码放置到这个节中。对于每个标准的节,其名称都是以圆点开头的。并且在每节信息的最后都列出了该节的读写权限说明,例如对于如图 20.11所示的结果中, .rdata节是只读的 (Read Only)。
下面,我们为 Hook.DLL创建一个新的节,并将全局变量: g_hWnd放到这个节中。创建节可以用指令 : #pragma data_seg来实现,具体代码如例 20 -18所示。
例 20-18
#pragma data_seg ( "MySec" ) HWND g_hWnd;
#pragma data_seg()
上述例 20-18所示代码的第一行代码中,字符串: "MySec"就是新创建的节的名称。需要注意的是,这个字符串的最大长度限制为 8个,如果超过 8个,系统将自动截断为 8个。一个新节的定义的最后必须以句ragma data_segO这行代码结束,表明新节的结尾。上述例 20-18所示代码只是把 g_hWnd这个变量放置到这个新定义的节中,其他变量仍在原先的节中。
再次生成 Hook.DLL程序,然后再利用 Dumpbin命令查看该 DLL中节的信息,但在输出的节列表信息中并没有看到新创建的节: MySec。这是因为如果要向这个新建的节中放置变量的话,该变量必须是已经经过初始化了的。因此修改如例 20-18所示代码的第二行代码,给这个变量进行初始化,将其初始化为 NULL。结果如例 20-19所示。
例 20-19
#pragma data_seg ("MySec" )
HWND g_hWnd=NULL;
#pragma data_seg()
.
图 20.11初始 Hook. DLL中节的信息列表
再次生成 Hook.DLL程序,并利用 Dumpbin命令查看该 DLL中节的信息,这时在输出信息中就可以看到新建的节: MySec (如图 20.12所示),并且可以发现这个新节的读写权限是可读 (Read)可写 (Write)。
图 20.12添加新节后的 Hook.DLL中节的信息列表
:如果希望新建的节的名称前面也有一个眠,可以直接在程序中在
其名称前加上圆点符号即可。
接下来,我们将 MySec节设置为共享的节,因为只有经过这样的处理之后,多个进程才能共享这个节中的数据。这时需要用到指令: #pragma comment,具体代码如例 20-20所示。
19IJ 20-20
#pragma data_seg ("MySec" )
HWND g_hWnd=NULL;
#pragma data_seg()
#pragma comment(linker, "/section:MySec, RWS"}
这里将指令类型指定为 linker,表明该行代码用来指定链接选工页。而字符串 " /section:MySec, RWS"的含义是表明将 MySec这个节设置为读 (R)写 (W)共享 (S)类型。通过这种方式,程序为链接器传递一个链接选工页。关于这个#pragma指令的详细用法,读者可以查看 MSDN中相关信息。
重新生成 Hook.DLL,并利用 Dumpbin命令查看该 DLL中节的信息,这时在输出信
息中就可以看到这个新节是共享的了,如图20.13所示。
图20.13将新节MySec设置为共享的
除了利用上述方式将新建的节设置为共享的之外,还可以在 DLL的模块定义文件中利用 SEGMENTS关键字来实现,具体方法是在该关键字下写上节的名称,后面指定节的
属性。对本例来说,可以在 Hook.def文件中已有内容之后添加如例 20-21所示代码中加灰
显示的代码。
例20-21
LIBRARY Hook
EXPORTS
SetHook @2
SEGMENTS
MySec READ WRITE SHARED
回注意:这里的读~~~ ~享属性只能用Read、 Write和Shared英文单词来
表示,不区分大小写,但不能是缩写。
当完成为Hook.dll添加一个新的共享节,并把全局变量g_hWnd放置到这个节之后,我们把重新生成的Hook.DLL复制到客户程序HookTest目录下,再次运行HookTest程序,然后切换到其他进程窗口,按下F2键,将会发现此时HookTest程序可以退出了。
这是因为我们己经在Hook.DLL中创建了一个新的共享的节,并将g_hWnd变量放置在这个共享的节中,这样所有进程都可以共享ιhWnd的同一份数据了。当运行HookTest程序之后, ιhWnd这一全局变量的值就是该程序的主窗口句柄。当切换到其他进程窗口时,对于ιhWnd这一全局变量来说,其表示的句柄值是同一个,即 HookTest程序的主窗口旬柄。当按下F2键,发送WM CLOSE消息时,就是给该句柄所表示的窗口发送一个关闭消息,所以HookTest程序就退出了。
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -