4.2 Inline Hook 挂钩技术
InlineHook 是一种计算机安全编程技术,其原理是在计算机程序执行期间进行拦截、修改、增强现有函数功能。它使用钩子函数(也可以称为回调函数)来截获程序执行的各种事件,并在事件发生前或后进行自定义处理,从而控制或增强程序行为。Hook技术常被用于系统加速、功能增强、外挂开发等领域。本章将重点讲解Hook是如何实现的,并手动封装实现自己的Hook挂钩模板。
x32 Inline Hook
对于4.1中所提到的Hook方法还是过于复杂,我们可以将上述代码定义为MyHook
类,构造函数用来初始化,析构函数用来恢复钩子,在Hook()
成员函数中完成了3项工作,首先是获得了被HOOK函数的函数地址,接下来是保存了被HOOK函数的前5字节,最后是用构造好的跳转指令来修改被HOOK函数的前5字节的内容。
如下封装中实现了三个类内函数,其中Hook()
用于开始Hook函数,此函数接收三个参数,参数1为需要Hook的动态链接库名,参数2为需要挂钩的函数名,参数3为自定以中转函数地址,其中UnHook()
用于恢复函数挂钩,最后的ReHook()
用于重新挂钩,以下是该类提供的功能的简要摘要:
- m_pfnRig:成员变量,在挂接之前存储原始函数地址。
- m_bOldBytes:成员变量,用于存储函数入口代码的原始5个字节。
- m_bNewBytes:成员变量,用于存储将替换原始函数代码的内联钩子代码。
- Hook():成员函数,通过将函数入口代码的前5个字节替换为JMP指令,将控制流重定向到指定的钩子函数,从而在指定的模块中钩子指定的函数。此函数返回一个BOOL,指示挂钩是否成功。
- UnHook():成员函数,用于删除钩子并恢复原始函数代码。此函数返回一个BOOL,指示解除挂钩是否成功。
- ReHook():成员函数,它使用之前存储的钩子代码重新钩子之前未钩子的函数。此函数返回一个BOOL,指示重新挂钩是否成功。
// 自己封装的Hook组件 |
同样我们以替换自身弹窗为例子具体讲解一下该库如何使用,如下代码中首先我们自定义一个MyMessageBoxA
函数,该函数的原型读者可通过微软官方文档获取,以下是MessageBoxA
函数的原型(函数声明):
int MessageBoxA( |
其中,参数的含义如下:
- hWnd: 指向包含消息框的窗口的句柄,通常为父窗口的句柄。可以设为NULL,表示没有父窗口。
- lpText: 指向要显示的消息文本的字符串指针。
- lpCaption: 指向要显示在消息框标题栏上的字符串指针,通常用于指定消息框的标题。
- uType: 用于指定消息框的按钮和图标样式,可以使用预定义的常量值进行设置,如MB_OK、MB_YESNO等。
有了函数原型声明部分读者则可以自己实现一个MyMessageBoxA
函数,需注意参数传递必须与原函数保持一致,在自定以函数内部我们首先通过MsgHook.UnHook();
恢复之前的钩子,并调用原函数实现功能替换,当调用结束后记得使用MsgHook.ReHook();
重新挂钩恢复钩子。
// 定义全局类 |
在主函数中我们通过调用MsgHook.Hook()
函数,挂钩住user32.dll
模块内的MessageBoxA
函数,并将该函数请求转发到MyMessageBoxA
上面做处理,当此时调用MessageBoxA
时读者可观察弹出提示是否为我们所期望的,最后通过MsgHook.UnHook();
用于解除钩子;
// 调用Hook组件 |
读者可自行运行上述代码,当尝试调用MessageBox
函数并传入hello lyshark
参数时,输出的结果却变成了hi hook api
如下图所示,则说明内联挂钩生效了。
x64 Inline Hook
32位钩子的封装实现详细读者已经能够理解了,接着我们来实现64位钩子的封装,64位与32位系统之间无论从寻址方式,还是语法规则都与x32架构有着本质的不同,由于64位编译器无法直接内嵌汇编代码,导致我们只能调用C库函数内嵌机器码来实现Hook的中转。
首先实现去MessageBox
弹窗,由于64位编译器无法直接内嵌汇编代码,所以在我们需要Hook时只能将跳转机器码以二进制字节方式写死在程序里,如下代码是一段去弹窗演示案例。
|
接着我们在上面代码的基础上继续进行完善,添加恢复钩子的功能,该功能时必须要有的,因为我们还是需要调用原始的弹窗代码,所以要在调用时进行暂时恢复,调用结束后再继续Hook挂钩。
|
将上面所写的代码进行函数化封装,实现一个完整的钩子处理程序。代码如下所示。
|
上方的代码还是基于过程化的案例,为了能更加通用,我们将其封装成MyHook
类,这样后期可以直接引入项目调用了。
|
读者可自行运行上述代码,当尝试调用MessageBox
函数并传入hello lyshark
参数时,输出的结果却变成了hi hook api
如下图所示,则说明内联挂钩生效了。