第48章:SEH
SEH 是 Windows 操作系统提供的异常处理机制,在程序源代码中使用 __try __catch __finally 等关键字来具体实现。
进程在运行过程中发生异常,OS 会委托进程处理,但如果进程内没有具体实现 SEH ,那么 OS 会启动默认的异常处理机制,终止进程运行。如果有调试器,则先交由调试器处理。
三种异常处理的方法
(1) 直接修改代码、寄存器、内存
(2) 将异常抛给被调试者
(3) 将异常抛给 OS 默认处理机制
异常
SEH
SEH 是以链的形式存在的,第一个异常处理器若未处理相关异常,它就会传递到下一个异常处理器,直到得到处理。SEH 是由 _EXCEPTION_REGISTRATION_RECORD 结构体组成的链表。
Next 成员指向下一个结构体,Handler 则直接指向了异常处理函数的首地址。
而异常处理函数是由 OS 调用的,即触发异常后,由 OS 提供参数给异常处理函数,并调用它。
异常处理函数的定义如下:
1. 异常处理函数的返回值 EXCEPTION_DISPOSITION 是一个枚举类型
2. 异常处理函数的第一个参数即 EXCEPTION_RECORD 是一个结构体
ExceptionCode 是异常类型。比如 80000004 指的是 EXCEPTION_SINGLE_STEP 异常,而 ExceptionAddress 则指出异常发生的地址。
3. 异常处理函数的第三个参数即 CONTEXT 也是一个结构体
访问 SEH 链的方法很简单: FS: [ 0 ]
在实验中继续观察,首先获取 SEH 链,然后将自身的栈指针接入 SEH 链,此时栈底的两个数据分别是 Next 指针和 Handler 函数地址。
然后运行到触发异常的地方(将数据写入不存在的内存区域):
触发异常后,按 Shift+ F7,将异常传递给程序自行处理。此时查看栈顶:
由书作者提供的函数声明可知,第一个参数就是 EXCEPTION_RECORD 的指针:
红色方框圈起来的两个分别是 ExceptionCode 和 ExceptionAddress ,都由 OS 填入。
第二个参数的值是 0012FF78
一看很眼熟,进入栈区查看:
可以看出,它指向了下一个 SEH 结构体。
第三个参数指向 CONTEXT 结构体:
可以看到,框起来的第一个元素是 ContexFlags,第二个元素是 Eip ,前面一个即为 Ebp ,其它大部分都是零。
继续查看作者编写的异常处理代码:
首先将第三个参数,即 Contex 结构体放入 esi ,并读取 TEB.NtTilb.BeingDebugged 的值并进行比较,若进程处于被调试阶段,则不跳转,并将地址写入 Contex 结构体中的 Eip .由 Contex 的功能可知,这将改变程序的运行。同时 xor eax,eax 表示函数返回值为0 ,将继续执行异常代码,而不转到下一个 SEH 结构体 。
接下来是系统代码——> 删除 SEH :
如果修改了上面 eax 的返回值,使其为1,即 ExceptionContinueSearch ,运行下一个异常处理器:
和 eax==0 时一样从 ntdll.77018E10 返回后,在 76FF8229 会经历一个跳转:
并且在一路没有跳转的执行到:
此函数中存在 call ecx :
此处就调用了 SEH 链的下一个异常处理函数
同样,在 OllyDbg 中设置相应的选项,可以避免 SEH 反调试,如果程序出现异常,调试器不会暂停,异常自动被 OS 送给程序。
经过实验,发现:在没有异常的时候,使用X64dbg 进行调试(没有开启异常忽略)查看 BeingDebugged 的值,可以发现其值是 1。
而使用 OllyDbg(开启异常忽略),在没有异常发生时,查看 BeingDebugged 的值,其值是 0。
并且在程序断在系统断点时,查看该元素的值,和上述的值没有变化。在 x64dbg 中,从系统断点处开始对该元素下硬件断点,没有暂停。
地址: https://blog.csdn.net/whklhhhh/article/details/79656200
总结:可以猜测,OD 使用了这种形式。而 X64dbg 没有,只有点击 隐藏调试器 后才会将 BeingDebugged 值设为 0.