一、原理部分
1钩子的作用
监控其他程序,劫持其他程序。是黑客技术在本地的基础。
2什么是钩子
钩子是Windows的消息处理机制中的一个监视点,应用程序可以在这里安装一个监视子程序,这样就可以在系统的消息流到达目的窗口的过程前监控它们。
3钩子程序组成部分
1主程序——用来实现界面或其他功能
2钩子回调函数——用来接收系统发过来的消息
3钩子的安装和卸载模块
这是一个钩子程序的例子,包括三个部分中的后两个部分。请结合后面的讲解,学习这段代码。
二、钩子操作流程
所有关于钩子的操作方法都可以从一个函数出发,推导出来。
因此我们在记忆时只需记住这个函数,就能记住整个操作流程。操作时只需以这个主干为核心,就能添加出全部代码。
这个函数就是:
HHOOKWINAPISetWindowsHookEx(
int idHook,\钩子类型
HOOK PROClpfn,\回调函数地址
HINSTANCE hMod,\实例句柄
DWORD dwThreadId);\线程ID
这是安装钩子的函数。
首先,idHook指明了要安装钩子的类型。如下图,大家只用扫一眼就好:
其中比较重要的如WH_KEYBORAD,WH_MOUSE,就是监控其他程序如果有键盘或鼠标行为就对这个信息流进行监控,可以截取修改键盘鼠标信息。
如果要对每个程序都监控,可以使用WH_GETMESSAGE。
第二,回调函数地址,一般只需写函数名。
在截取到了相应类型的消息后,要对这一消息进行处理,这个回调函数就是用来处理消息的。
如果要写一个全局钩子,lpfn参数指向的钩子函数必须位于一个DLL中。这是因为进程的地址空间是相互隔离的,发生事件的进程不能调用其他进程地址空间的钩子函数。如果钩子函数的实现代码在DLL中,在相关事件发生时,系统会把这个DLL插入到发生事件的进程的地址空间,使它能够调用钩子函数。
第三,是钩子回调函数所在的dll的实例句柄。这个我们可以在dll入口函数获得,并保存在一个全局变量中。
关于dll的操作流程,请参考我的上一篇文章。dll编写与使用操作手册
第四,是线程id,用来指定要监控的线程id,全局钩子置0。
回调函数
在上述的第二中,我们提到了回调函数,因此我们需要在dll里写一个回调函数。回调函数形如:
其中,nCode参数是Hook代码,钩子函数使用这个参数来确定任务,它的值依赖Hook的类型。我们在使用到某一钩子时,具体问题具体分析。
wParam和lParam参数的值依赖于Hook代码,但是它们典型的值是一些关于发送或者接收消息的信息。
因为系统中可能会有多个钩子存在,所以要调用CallNextHookEx函数把消息传到链中下一个钩子函数。hHook参数是安装钩子时得到的钩子句柄,也就是SetWindowsHookEx的返回值。我们应该把这个返回值保存在一个全局变量比如g_hook中,供这里使用。
在处理消息的代码中,我们可能会向安装钩子dll的主程序或窗口发送消息。与主窗口通信需要一个messageid,也就是类似于WM_COMMAND的东西。这需要我们自定义一个。我们在.h文件中添加HM_KEY的宏定义。
同时,我们会用到主窗口的句柄。我们同样要将其保存在全局变量中。
到目前为止,我们在全局变量中需要保存三个值,主程序窗口句柄,dll模块句柄,钩子句柄。
由于此DLL将被映射到不同进程的地址空间,而在每个进程中,钩子函数都要使用钩子句柄和主窗口句柄,以便调用CallNextHookEx函数和向主窗口发送消息。
但是钩子成功安装后,Windows将此DLL加载到所有接受某一类型消息的其他进程的地址空间,但是在这些进程中变量钩子句柄和主窗口句柄的值却没有正确设置,因为并没有线程为它们赋值。
这时候,我们使用共享数据段来解决这一问题。共享数据段中的数据在所有进程中共享一块内存,这意味着在一个进程中设置了共享数据段的数据,其他进程中同一数据段的数据也会随之改变。
要使用共享数据段,我们需要添加两处代码。
1在dll程序中,增加#pragma data_seg()段
2在def文件中添加SECTION属性
最后,在不使用钩子的时候,我们使用UnhookWindowsHookEx函数卸载钩子。
至此,钩子dll文件全部编写完毕。我们从函数SetWindowsHookEx出发,推导出了整个程序编写的方法和代码框架。这种方式便于我们理解和记忆。
重新梳理如下:全局钩子需要写在dll中,核心函数是SetWindowsHookEx,回调函数是主体。数据段需要共享。
在启动这个钩子的主程序中,我们按照调用dll的方法,调用安装钩子的程序即可。附主程序简单源代码。其中的注意事项于dll使用方法无异。