第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.

原文地址:https://www.cnblogs.com/Rev-omi/p/13624497.html