作者 | 榴莲
编辑 | 楌橪
Windows操作系统中存在多种异常处理,我们现在需要的是其中的VEH(VectoredExceptionHandler)异常处理,也就是向量化异常处理。我们之所以可以使用VEH异常来进行HOOK的主要原因,在于两点。其一,VEH异常处理的优先级是高于SEH异常处理的,也就是说可以先手拿到异常,确保不会被其他异常处理流程将异常截获而导致HOOK失败。其二就是在VEH异常处理的回调函数中,可以获取及修改异常发生处的上下文环境,这就意味着我们可以操作的东西会非常多,例如通过上下文环境中的ESP(栈顶指针寄存器)就可以拿到HOOK位置触发异常时的堆栈数据。而我们设置的HOOK位置通常位于函数内部的起始位置,这就意味着我们可以直接通过堆栈里的数据获取到被HOOK函数的参数,并且可以对其进行修改。
在我们之前的文章中,我们已经使用过利用软件断点(int 3 0xCC)触发异常,实现HOOK。
但是这种方法也是有一定缺陷的。例如,如果目标进程具有CRC32一类的完整性检查,int3 软件断点又会修改指令。这样就无法通过完整性检查了。所以,我们这一次,提出一种新的方式。依然是基于VEH异常的,但是可以实现“无痕”的效果。不修改任何一个字节就完成HOOK。那么,这种方式就是基于硬件调试寄存器实现的。也就是硬件断点。因为硬件断点的地址是存储在寄存器里的,所以不会修改内存。
那么在学习具体的HOOK方法之前,我们首先需要了解一下硬件断点的基本知识:
上图就是Intel手册中对于调试断点的说明图,下面我们对其字段进行一定解释:
DR0 - DR3就是用来保存硬件断点的地址的,这个地址是线性地址而不是物理地址,因为CPU是在线性地址被翻译成物理地址之前出处理断点的,也因此,我们在保护模式内不能用调试寄存器对物理内存地址设置断点。
DR4和DR5是保留的,如果调试扩展开启了(CR4的DE位设置成1就是开启了),任何对DR4和DR5的调用都会导致一个非法指令异常#UD,如果调试扩展禁用了,那么DR4和DR5其实就是DR6和DR7的别名寄存器。
DR7寄存器是调试控制寄存器:
R/W0 - R/W3 读写域 四个读写域分别与DR0-DR3寄存器所对应,用来指定被监控地点的访问类型。
占两位,所以有以下四种状态:
00:仅执行对应断点的时候中断(执行断点)
01:仅写数据中断(写入断点)
10:(需要开启CR4的DE【调试扩展】)I/O时中断
11:读写数据都中断,但是读指令除外(访问断点)
LEN0 - LEN3 长度域 四个长度域分别与DR0-DR3寄存器所对应,用来指定监控区域的长度
占两位,所以有以下四种状态:
00:1字节长
01:2字节长
10:8字节长
11:4字节长
如果R/W位是00,那么这里应该设置成0
L0-L3 局部断点启用 分别与DR0-DR3寄存器所对应,对应项为1就是开启断点,为0就是关闭断点,执行后自动清除该位
G0-G3 全部断点启用 分别与DR0-DR3寄存器所对应,对应项为1就是开启断点,为0就是关闭断点,CPU不会主动清除
LE和GE 忽略即可,高版本CPU不用了,486之前才会用
GD启用访问检测,如果GD是1,那么CPU遇到修改DR寄存器的指令,会产生一条异常。
DR6寄存器是调试状态寄存器
B0-B3 分别与DR0-DR3寄存器所对应,如果B0被置1了,那说明R/W0 len0 DR0的条件都被满足了
BD 与DR7的GD位相关联,当CPU发现了需要修改DR寄存器的指令,那么就会停止执行,把BD设置成1,然后交给#DB的处理程序
BS 单步 与EFLAGS里的TF位相关联,如果这一位是1,则表示是单步触发的
BT 任务切换 与任务段相关,TSS的T标志(调试陷阱)相关联,当任务切换,发现下一个TSS的T是1,那么就会中断到调试中断程序里
那么以MessageBoxA为例,我们实际上来体验一下无痕HOOK的实现方式。
首先,我们需要一个目标程序,代码如下:
然后我们来看一下HOOK后的效果:
正常情况下:
HOOK后:
接下来,我们将使用代码实现IAT Hook,我这里采用的操作系统是Windows 10 20H2(19042.1288),集成开发环境采用的是Visual Studio 2017。
阅读全文
公众号:极安御信安全研究院(即可获得文章全部内容)