IAT/EAI hook
属于地址hook,通过修改PE文件中导入地址表(IAT)或导出地址表(EAT)中的函数地址,将对目标API的调用重定向到自定义函数。
实现原理:
- 解析目标模块的PE头,定位到导入表(IMAGE_IMPORT_DESCRIPTOR)
- 遍历导入表,找到目标函数对应的IAT条目
- 修改IAT中该函数地址为自定义函数的地址(需注意内存页保护属性,通常需要VirtualProtect修改为可写)
- 当程序调用该API时,实际跳转到自定义函数执行
适用情况:
- 适用于拦截通过显式导入(静态链接)调用的API
- 常用于监控文件操作(CreateFile、ReadFile)、注册表操作、进程创建等
- 对同一模块内所有调用统一生效,无需逐个修改调用点
注意事项:
- 无法拦截通过LoadLibrary + GetProcAddress动态获取的函数调用
- 修改IAT后,原始函数地址丢失,需保存原始地址以便在自定义函数中调用原函数
- 需考虑DLL加载顺序问题,若目标DLL尚未加载则无法hook
- 某些安全软件会检测IAT是否被篡改
inline hook
通过修改目标函数起始处的几个字节,跳转到自定义函数处。
实现原理:
- 保存目标函数开头的若干字节(通常5-14字节,取决于跳转指令长度)
- 在函数开头写入跳转指令(如x86的
jmp rel32占5字节,x64需考虑地址空间问题) - 自定义函数执行完毕后,执行被覆盖的原始指令,再跳回原函数剩余部分继续执行
- 通常使用
jmp+ 绝对地址或push ret方式实现跳转
适用情况:
- 通用性强,可hook任意函数(无论静态导入还是动态获取)
- 适用于需要深度监控或修改函数行为的场景
- 可hook未导出的内部函数(需知道函数地址)
注意事项:
- 多线程环境下需确保修改指令时的原子性,避免部分修改导致崩溃
- 被hook函数若正在执行,修改可能导致不可预知的行为
- 需处理指令边界对齐问题,避免覆盖不完整的指令
- x64下需注意相对跳转范围(32位偏移仅±2GB),需使用
jmp [rip+offset]或push rax; mov rax, addr; jmp rax等方式 - 某些函数开头较短(如只有2字节的
ret),需特殊处理 - 需绕过Windows的PatchGuard(仅内核态)和用户态的安全软件检测
异常hook
主要利用了Windows用户态异常处理机制,在触发异常时控制权转到我们设置的异常处理器(如调试器、VEH、SEH)中执行。
int3 hook
实现原理:
- 将目标函数开头的第一个字节替换为
0xCC(int3指令) - 当程序执行到该处时触发断点异常(EXCEPTION_BREAKPOINT)
- 通过VEH(Vectored Exception Handler)或调试器捕获该异常
- 在异常处理器中修改上下文(CONTEXT结构),将EIP/RIP指向自定义函数
- 恢复执行后程序跳转到自定义函数
适用情况:
- 适合临时调试、动态分析场景
- 可hook任意函数,包括动态获取的函数
- 对原始函数代码修改极小(仅1字节)
注意事项:
- 性能开销较大(每次调用都会触发异常处理流程)
- 需处理异常处理器的递归调用问题
- 多线程环境下需考虑同步
- 某些反调试技术会检测
0xCC断点 - 需在异常处理器中正确恢复执行上下文
硬件断点hook
实现原理:
- 利用CPU的调试寄存器(DR0-DR3)设置硬件断点
- 将目标函数地址写入DR0-DR3,设置断点类型为执行断点
- 当CPU执行到该地址时触发异常(EXCEPTION_SINGLE_STEP)
- 通过VEH捕获异常,修改上下文实现hook
- 设置DR6标志位清除断点状态,DR7控制断点启用
适用情况:
- 无需修改目标函数代码,隐蔽性较好
- 适合监控关键API调用
- 可设置读写断点监控内存访问
注意事项:
- 最多只能设置4个硬件断点(DR0-DR3)
- 线程相关:调试寄存器是线程上下文的一部分,需为每个线程单独设置
- 性能开销较大(每次命中都会触发异常)
- 需处理异常处理器的递归和嵌套
- 某些虚拟化环境可能不支持硬件断点
内存断点hook
实现原理:
- 通过VirtualProtect修改目标函数所在内存页的保护属性(如改为PAGE_NOACCESS或PAGE_GUARD)
- 当程序访问该内存页时触发访问违例异常(EXCEPTION_ACCESS_VIOLATION)
- 在VEH中捕获异常,判断是否为目标地址
- 修改上下文将执行流转到自定义函数
- 临时恢复内存属性后单步执行,再重新设置断点
适用情况:
- 可监控大范围内存区域的访问
- 适合逆向分析和调试场景
- 可同时监控读、写、执行操作
注意事项:
- 性能开销极大(每次内存访问都触发异常)
- 内存页粒度为4KB,同一页内的其他代码也会受影响
- 需处理递归异常(恢复属性后单步执行再重新设置)
- 多线程环境下需谨慎处理内存属性修改
- 某些安全软件会监控内存属性变化
虚函数表hook
实现原理:
- 获取目标对象的虚函数表指针(vptr),通常位于对象内存布局的前4/8字节
- 定位到目标虚函数在虚函数表中的索引位置
- 修改虚函数表中的函数指针,指向自定义函数
- 当通过基类指针或引用调用虚函数时,实际执行自定义函数
适用情况:
- 适用于C++虚函数调用的拦截
- 常用于COM接口hook、MFC框架hook
- 可hook特定对象实例或全局修改(修改虚函数表本身)
注意事项:
- 需了解目标类的虚函数表布局(虚函数顺序)
- 修改虚函数表会影响该类的所有实例(若修改的是共享虚函数表)
- 若只修改某个对象的vptr,则只影响该对象
- 需考虑多继承、虚继承等复杂场景
- 某些编译器优化(如COMDAT折叠)可能导致虚函数表被合并
- 需注意内存页保护属性,虚函数表通常位于只读段
消息钩子
通过SetWindowsHookEx函数安装系统级或线程级的消息过滤器实现,只能拦截消息事件。
实现原理:
- 调用SetWindowsHookEx注册钩子过程(Hook Procedure)
- 指定钩子类型(如WH_KEYBOARD、WH_MOUSE、WH_CBT等)
- 当指定事件发生时,系统调用钩子链中的钩子过程
- 钩子过程可监控、修改或阻止消息传递
- 全局钩子需将钩子过程放在DLL中,由系统注入到目标进程
适用情况:
- 适合键盘记录、鼠标监控、窗口消息拦截
- 可用于自动化测试、辅助功能开发
- 系统级钩子可监控所有进程的消息
注意事项:
- 全局钩子会降低系统性能(所有消息都需经过钩子链)
- 钩子DLL会被注入到所有满足条件的进程中,需确保DLL兼容性
- 需在DLL中正确处理钩子过程,避免死锁
- 64位系统需注意32位和64位钩子的区别(不能混用)
- UIPI(用户界面特权隔离)会阻止低权限进程钩取高权限进程的消息
- 某些杀毒软件会检测SetWindowsHookEx的调用
- 钩子过程应尽快返回,避免阻塞消息处理