0x00 前言
再来系统学习一下之前学的不扎实的HOOK技术:cry:
HOOK技术,用好了能有很奇妙的效果哟~
可以对程序的执行流进行监控、拦截。举个例子:键盘钩子,可以监控用户通过键盘输入了什么:happy:
本文重点去讨论HOOK过程的原子性
0x01 HOOK的几种分类
HOOK技术可以分为Inline Hook
、Address Hook
、基于异常处理的Hook
、不是HOOK的HOOK
等
1. Inline Hook
Inline Hook是指直接修改指令的Hook,其关键是转移程序的执行流程,一般用jmp、call、retn等转移指令
几种常见形式
- jmp xxxxxxxx(5字节)
- push xxxxxxxx / retn(6字节)
- mov eax,xxxxxxxx / jmp eax(7字节)
- call Hook
- HotPatch Hook
2. Address Hook
Address Hook是指通过修改数据来进行Hook,这些数据往往是一些函数的地址或者偏移量,它们通常存放在某类数据结构中或是指定位置处,也可能是存在于寄存器中。它们有一个共同点,就是会在某时刻成为执行过程中的eip,因此将这数据替换成我们想要的函数地址就实现了Hook
几种常见形式
- IAT(Import Address Table,输入表)
- EAT(Export Address Table,输出表)
- user32.dll的回调函数表
- IDT(Interrupt Descriptor Table,系统的中断描述符表)
- SSDT和Shadow SSDT
- C++类的虚函数表
- COM接口的功能函数表
- 特殊寄存器中的地址
- 特定的函数指针
3. 基于异常处理的Hook
在程序中自行安装SEH,向被HOOK的位置写入一条会引发异常的指令,只要程序执行到这里就会触发异常,从而跳转到事先安装的异常处理程序处。
4. 不是HOOK的HOOK
回顾病毒和操作系统的一些行为,都是在某些时候取得程序的控制权,并进行适当的处理。
几种常见的形式
PE被感染,修改EnterPoint
会在执行完病毒代码后跳回正常的程序入口,使其不易被察觉。
系统回调机制
分层服务和过滤驱动模型
0x02 几个细节
1. 裸函数 naked
__declspec(naked) 告诉编译器,以下的汇编不需要编译器再优化什么指令。
2. 系统位数引起的细节问题
指针
指针在x86上4字节,在x64上8字节。
可以使用ULONG_PTR
来自适应于编译器,类似的还有SIZE_T
内存地址对齐
x64最好按照16字节来对齐,尽管x64对应8字节
PE格式
影响依照PE结构的HOOK
调用约定
x64仅有__fastcall
调用方式
用寄存器来传参
第1~4个整型参数依次按照RCX、RDX、R8、R9
来传参
第1~4个浮点参数依次按照XMM0、XMM1、XMM2、XMM3
来传参
跳转指令
对于直接跳转指令E9
,跳转范围以当前位置为界,前后各2GB,在X86平台完全够用,但是在X64平台就不够了。
所以在64位的情况下,jmp必须直接包含目标地址
可以用
mov rax,addr / jmp rax
push ret
jmp [addr]
其中此jmp为FF25
且Address = eip + 偏移量 + 6(指令长度)
PatchGuard
3. 一个经典的问题
我们该如何保证HOOK的原子性?
即,我们如何保证负责HOOK的线程在进行HOOK的时候,其他线程不会访问被HOOK但还没完成HOOK的代码段?
这里提出几种解决方案,这几种解决方案均是受启发于《Windows核心编程》这本经典著作。
方案一
我们可以在执行HOOK的时候,挂起(Suspend)其他一切无关的线程,在HOOK完毕后再恢复(Resume)其他线程。
不过,这个方案相比方案二,耗时会较多一些。
相关代码如下:
1 | BOOL Solution1(BOOL isSuspend) { |
方案二
我们可以借助Windows提供的具有原子性的API,例如
InterlockedExchange
系列与InterlockedCompareExchange
系列
但是值得注意的是,这两个系列均仅支持修改1/2/4/8字节,并不支持修改其他字节,是的,我并没有找到直接修改HOOK所需的5或者7字节的API,但是这并不妨碍我们进行修改,例如我们的Inline Hook往往是需要修改5或者7字节,这都小于8字节,因此我们可以调用InterlockedExchange64
来直接修改8字节!
1 |
|
方案三
借助信号量机制。
首先,我们进行修改内存的时候极有可能使用memcpy
这个函数,而这个函数并不是线程安全函数,引用自StackOverflow
memcpy is typically coded for raw speed. It will not be thread safe. If you require this, you need to perform the memcpy call inside of a critical section or use some other semaphor mechanism.
1
2
3
4 > take_mutex(&mutex);
> memcpy(dst, src, count);
> yield_mutex(&mutex);
>
0x03 HOOK实例
来写一个DLL来稍微练习一下HOOK
同时为了应用上述提到的实现原子性的方案,给出多种代码作为参考。
各方案共同的代码
1 | //InlineHook.h |
1 | // dllmain.cpp : 定义 DLL 应用程序的入口点。 |
方案一
1 | //InlineHook.cpp |
方案二
1 | //InlineHook.cpp |
一个用于测试的demo
1 |
|
0x04 参考
《加密与解密》第四版
《逆向工程核心原理》
inline hook需要注意的问题
这一篇里面关于挂起线程和恢复线程的代码是存在问题的,它挂起的是所有进程的线程而非目标进程的线程,本文已经对其代码进行了修改完善,不过仍要感谢文章作者提供的思路!