我刚接触HOOK这个概念时,被各种名词搞得头疼:inline hook、IAT hook、detour、热补丁……真正让我豁然开朗的,是有一次有人问我:你能不能不让某个程序弹错误框?我不改它的代码,只是想知道它到底在什么时候弹。那时候我悟了一件事——HOOK的本质,就是在程序自己的代码流里,悄悄安插一个“临时出口”。下文我用一段极简的inline hook代码,配合我这些年实际踩过的坑,把什么是HOOK、HOOK能干什么、怎么干才稳,一次讲透。
如果你正在学逆向、做安全研究、搞游戏辅助分析,或者在给老系统加可观测性能力,这篇文章都适用。零基础也能看懂前两章,后面几章偏工程经验,建议收藏后慢慢看。
1. 什么是HOOK:给程序装一个“中间人”
1.1 没有源码也想“插一脚”
假设你有一个别人编译好的软件,它每个操作完成都会弹一个提示,而你想在弹提示前自动截取一些信息。你不可能拿到它的源码重新编译,但程序在运行起来以后,所有代码都会躺在内存里,CPU也是一条一条按顺序执行指令。只要你能在它执行“弹提示”这条指令之前,把流程引向你的代码,你就能实现“让它多做一个动作、或者改掉一个参数”。这个“引导”的动作,就是HOOK。
专业一点说:HOOK是一种在运行期改变程序执行流向的技术,它可以在不修改源程序和可执行文件的前提下,通过对内存中的代码或数据结构打补丁,让程序执行到你自己的代码里,再决定要不要回到原来的函数继续执行。之所以很多前辈把这个过程叫“挂钩”,是因为你的代码像挂了一个钩子,钩住了程序原有流程。
1.2 HOOK的三个核心动作
任何HOOK都逃不过三个问题。
- 钩在哪里:你要在哪个函数、哪个系统调用、哪个指令处做拦截。
- 怎么钩:用什么方式改变执行流(改函数入口、改跳转表、改消息回调等)。
- 钩完怎么办:拦截之后要继续执行原函数,还是彻底替换,还是先收数据再放行。
这三个问题贯穿全文。搞懂了这三点,再去读任何HOOK框架的源码都不会迷路。很多新手一上来就研究“怎么写跳转指令”,反而忽略了更关键的“钩哪里”和“钩完怎么办”这两个决策层问题。
1.3 一句话理解HOOK
HOOK不神秘,它和你平时在工厂流水线旁边加一台检测仪没有本质区别。产品(数据/指令)经过检测仪时被扫描一遍,检测仪点头它继续走,检测仪摇头它就去别的工位。真正的技术难点从来不是“加检测仪”这个动作,而是“怎么让产品一定从检测仪前面走”。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 动手写代码:一个最小可用的inline hook
2.1 为什么第一段代码选inline hook
HOOK分很多种,IAT hook、虚表hook、消息钩子、内核回调,我后面会讲。但最直观、最能让你理解“执行流被改掉”这件事的,一定是inline hook。因为它就是在机器指令层面改你的函数入口,你看得到指令是怎么被替换的。
我选了Windows用户态编程里最基础的MessageBoxW作为目标。理由很简单:它在user32.dll里,导出表公开,地址好拿,参数也很好确认,而且你调它的时候能看见窗口,能直观感受到hook前后差异。注意,这个例子只在x86进程里可以跑,x64下需要把地址宽度改成64位,代码会稍微长一点。我特意选x86是为了教学直观,原理则完全通用。
2.2 最核心的原理:五字节跳转
x86下,一条无条件跳转指令jmp由5个字节构成:0xE9开头,后面跟4字节的“相对偏移”。CPU执行到这条jmp时,会跳到“目标地址等于下一条指令地址加上这4字节偏移”的位置。这个“下一条指令地址”就是我们当前指令的地址加5,这经常被新手忽略。
假设原函数地址是0x10000000,你的hook函数地址是0x20000000,那么你在原函数里应该写入的偏移是:
偏移 = 0x20000000 - (0x10000000 + 5)
这种偏移计算方式在x86的call指令里也一样,很多人在手写汇编跳转时算错,基本都是
