SEH 入门

SEH 机制

Structedexception handling 结构化异常处理的简称。

这里介绍的异常处理并不是我们通常看到的__try__catch 语句,而是操作系统提供的一种异常机制。通过这种称为SHE 的机制,用户可以通过定义注册自己的异常处理函数以在异常出现的时候有机会执行自己的代码,决定程序流程。我们平时直观看到的异常处理机制都是编译器建立在windows SHE 基础之上的一种扩充和使用。在了解windows SHE 的基础之后我们再来分析编译器提供的SHE 的实现过程。

首先分析Win X86 SHE 的原理。

上面提到用户必须定义注册自己的异常处理函数。

异常处理函数的函数定义是什么?

函数定义

EXCEPTION_DISPOSITION __cdecl _except_handler(
    _In_ struct _EXCEPTION_RECORD *_ExceptionRecord,
    _In_ void * _EstablisherFrame,
    _Inout_ struct_CONTEXT *_ContextRecord,
    _Inout_ void *_DispatcherContext
    );


我们看到,该函数类型为,三个结构体指针,返回值为一个我们暂时不知道的类型。下面我们根据需要介绍需要了解的结构体。

typedef struct _EXCEPTION_RECORD {
        DWORD ExceptionCode;
        DWORD ExceptionFlags;
struct_EXCEPTION_RECORD *ExceptionRecord;
PVOIDExceptionAddress;
        DWORD NumberParameters;
DWORDExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS];
 } EXCEPTION_RECORD;


 

ExceptionCode 异常编号,比如STATUS_ACCESS_VIOLATION。

ExceptionAddress 发生异常的地址。

第三个参数为指向CONTEXT结构体的指针。

typedef struct _CONTEXT
 {
    DWORD ContextFlags;
    DWORD   Dr0;
    DWORD   Dr1;
    DWORD   Dr2;
    DWORD   Dr3;
    DWORD   Dr6;
    DWORD   Dr7;
    FLOATING_SAVE_AREA FloatSave;
    DWORD   SegGs;
    DWORD   SegFs;
    DWORD   SegEs;
    DWORD   SegDs;
    DWORD   Edi;
    DWORD   Esi;
    DWORD   Ebx;
    DWORD   Edx;
    DWORD   Ecx;
    DWORD   Eax;
    DWORD   Ebp;
    DWORD   Eip;
    DWORD   SegCs;
    DWORD   EFlags;
    DWORD   Esp;
    DWORD   SegSs;
 }CONTEXT;


表示特定线程的寄存器值。当用于SHE ,CONTEXT 结构表示异常发生时的寄存器值。

因此现在我们可以看到,当异常发生的时候,系统会保存大量程序的运行信息,比如异常发生的地址,发生异常时寄存器的状态,异常的类型,然后调用一个拥有上述签名的函数,来处理异常。

那么,系统通过什么样的结构来找到相应的异常处理函数呢?

答案是下述结构体:

_EXCEPTION_REGISTRATIONstruc
     prev   dd      ?
     handler dd      ?
 _EXCEPTION_REGISTRATION ends


 

系统将异常处理函数组织成一个链表,这个链表的最后一个处理函数的地址为0xFFFFFFFF,第一个该结构的地址保存在FS:[0] 中(FS寄存器指向TEB 基地址,TEB 第一个结构为TIB,TIB 第一个结构为ExceptionList即EXCEPTION_REGISTRATION_RECORD指针,该结构与上述结构相同),即异常处理函数为线程相关的。等到登记完成以后,线程就可以抛出或处理异常。“系统为异常的出现以及异常的处理提供了一种管理方法,或者一组数据结构用户自己来提供不同异常对应的解决方案

登记操作:

push    handler         // Addressof handler function
push    FS:[0]         // Address of previous handler
mov     FS:[0],ESP     // Install new EXECEPTION_REGISTRATION


上述三行汇编就是登记操作的最简单形式。

到这里,我们已经知道如何去定义并登记自己的异常处理函数到线程的异常处理链表中。那么,我们来看这个函数的返回值类型

typedef enum _EXCEPTION_DISPOSITION{
    ExceptionContinueExecution,
    ExceptionContinueSearch,
    ExceptionNestedException,
    ExceptionCollidedUnwind
} EXCEPTION_DISPOSITION;


 

ExceptionContinueExecution      程序从异常发生的地址继续执行

ExceptionContinueSearch     无法处理该异常,继续沿着异常链表搜索并调用其异常处理函数

剩下的两个枚举先不介绍。

我们知道,程序的调用都有对应的调用堆栈,那么当异常发生的时候,异常函数的调用堆栈是怎样的?是谁调用了我们的异常处理函数?

要搞明白这个问题,首先应该清楚异常的处理过程。


KiDispatchException的处理过程


通过上图我们可以看到,如果是内核态代码且其没有附加的调试器,设计的异常处理函数为:RtlDispatchException 函数。

如果是用户态的异常,且没有调试器附载,KiDispatchException先确认用户态栈有足够的空间容纳CONTEXT 和 EXCEPTION_RECORD 结构,将其复制到用户态栈中,将TrapFram 调整为在用户态执行所需的合适的值,然后将KeUerExceptionDispatcher 指针赋给EIP,该函数内部依然调用的是RtlDispatchException函数。RtlDispatchException(用户态)函数寻找异常处理器,如果返回TRUE,已经有异常处理器处理了该异常,KiUserExceptionDispatcher 调用ZwContinue 系统服务继续执行原来发生异常的代码

用户态异常处理器链表(从FS:[0]开始) 尾部为Kernel32.dll中的 UnhanldedExceptionFilter---通常所见到的一些程序崩溃信息。

RtlDispatchException

接下来介绍RtlDispatchException 函数,该函数虽然有用户态和内核态,但其实是同一份代码的两份拷贝而已。

 
BOOLEAN
RtlDispatchException (
   IN PEXCEPTION_RECORD ExceptionRecord,
   IN PCONTEXT ContextRecord
    )


Wrk 中有对应的源码,有兴趣的可以查看。

该函数内部依次遍历上面描述的异常处理函数链表,然后通过调用RtlpExecuteHandlerForException来执行其异常处理函数,并根据其返回值决定接下来的动作。

另外,RtlpExecuteHandlerForException函数内部会构建一个异常,对应的异常处理函数与嵌套异常的判断有关,这里我们暂时不深入介绍,后面我们将看到这一点。

 

ExceptionContinueExecution,结束遍历,返回。如果记为‘EXCEPTION_NONCONTINUABLE’的异常,调用RtlRaiseException。

ExceptionContinueSearch,继续遍历下一个结点。

ExceptionNestedException,嵌套异常,我们暂时不介绍这种情况。

 

只有正确处理 ExceptionContinueExecution 才会返回 TRUE,其他情况都返回FALSE。

 

到这里,操作系统提供的SHE 机制已经介绍完毕,其功能仅仅是遍历异常链表,挨个调用注册的异常处理函数,如果其中有某个处理函数处理了该异常,从异常触发点继续执行,否则不管是整个链表中没有找到合适的处理函数,还是遍历过程出现异常,都会导致系统崩溃。那么,应用层的程序如果没有注册自己的异常处理函数,简单的访问错误岂不是会导致系统崩溃?答案是不会,因为我们在运行自己的应用程序代码之前,提前执行了系统的函数,其中包含了一个默认的异常处理函数,该异常处理函数即我们常见的崩溃报告窗口。

应用层默认的异常处理函数

应用层线程的起始地址为:RtlUserThreadStart:

我们查看其代码并观察其运行堆栈如下:

 

 

.text:4B2E0F89 ;__stdcall_RtlUserThreadStart(x, x)
.text:4B2E0F89__RtlUserThreadStart@8:                
.text:4B2E0F89                 mov    edi, edi
.text:4B2E0F8B                 push    ebp
.text:4B2E0F8C                 mov     ebp, esp
.text:4B2E0F8E                 push    ecx
.text:4B2E0F8F                 push    ecx
.text:4B2E0F90                 lea     eax, [ebp-8]
.text:4B2E0F93                 push    eax
.text:4B2E0F94                 call    _RtlInitializeExceptionChain@4 ;RtlInitializeExceptionChain(x)
.text:4B2E0F99                 mov     edx, [ebp+0Ch]
.text:4B2E0F9C                 mov     ecx, [ebp+8]
.text:4B2E0F9F                 call    ___RtlUserThreadStart@8 ;__RtlUserThreadStart(x,x)

 

.text:4B2E0FAA ;__stdcall __RtlUserThreadStart(x, x)
.text:4B2E0FAA___RtlUserThreadStart@8 proc near       ;CODE XREF: .text:4B2E0F9Fp
.text:4B2E0FAA
.text:4B2E0FAAExitStatus      = dword ptr -2Ch
.text:4B2E0FAAvar_28          = dword ptr -28h
.text:4B2E0FAAvar_24          = dword ptr -24h
.text:4B2E0FAAvar_20          = dword ptr -20h
.text:4B2E0FAAms_exc          = CPPEH_RECORD ptr -18h
.text:4B2E0FAA
.text:4B2E0FAA ;FUNCTION CHUNK AT .text:4B31E07C SIZE 00000071 BYTES
.text:4B2E0FAA
.text:4B2E0FAA                 push    1Ch
.text:4B2E0FAC                 push    offset stru_4B378710


 

.text:4B2E0FB1                 call    __SEH_prolog4_GS
.text:4B2E0FB6                 mov     edi, ecx
.text:4B2E0FB8                 and     [ebp+ms_exc.registration.TryLevel], 0
.text:4B2E0FBC                 mov     esi, _Kernel32ThreadInitThunkFunction
.text:4B2E0FC2                 push    edx
.text:4B2E0FC3                 test    esi, esi
.text:4B2E0FC5                 jz      loc_4B31E07C
.text:4B2E0FCB                 mov     ecx, esi
.text:4B2E0FCD                 call    ds:___guard_check_icall_fptr ;RtlpHpAppCompatDontChangePolicy()
.text:4B2E0FD3                 mov     edx, edi
.text:4B2E0FD5                 xor     ecx, ecx
.text:4B2E0FD7                 call    esi ; _Kernel32ThreadInitThunkFunction
.text:4B2E0FD9                 jmp     loc_4B31E0E0



__SEH_prolog4_GS
proc near              .text:4B3020FC                                        
.text:4B3020FC
.text:4B3020FCarg_4           = dword ptr  8
.text:4B3020FC
.text:4B3020FC                 push    offset __except_handler4
.text:4B302101                 push    large dword ptr fs:0



 

.text:4B302108                 mov     eax, [esp+8+arg_4]
Eax = 1CH
.text:4B30210C                 mov     [esp+8+arg_4], ebp
.text:4B302110                 lea     ebp, [esp+8+arg_4]
.text:4B302114                 sub     esp, eax

Eax = 1CH

 

 

.text:4B302116                 push    ebx
.text:4B302117                 push    esi
.text:4B302118                 push    edi


 

.text:4B302119                 mov     eax, ds:___security_cookie
.text:4B30211E                 xor     [ebp-4], eax
.text:4B302121                 xor     eax, ebp
.text:4B302123                 mov     [ebp-1Ch], eax
.text:4B302126                 push    eax

Ntdll32!_security_cookie与 ebp 异或后值入栈验证frame 是否被破坏。而offset stru_4B378710 所处的位置为EXCEPTION_REGISTRATION_RECORD 结构的成员FilterFrame,security_cookie 与其异或后在这里放下。

 

.text:4B302127                 mov     [ebp-18h], esp
.text:4B30212A                 push    dword ptr [ebp-8]
.text:4B30212D                 mov     eax, [ebp-4]
Eax= offsetstru_4B378710 xor __security_cookie
 
.text:4B302130                 mov     dword ptr [ebp-4], 0FFFFFFFEh
.text:4B302137                 mov     [ebp-8], eax
.text:4B30213A                 lea     eax, [ebp-10h]
.text:4B30213D                 mov     large fs:0, eax
.text:4B302143                 retn


函数返回后,返回到__RtlUserThreadStart函数处

 

然后函数调用_Kernel32ThreadInitThunkFunction该函数调用线程的启动函数。

.text:4B2E0FB6                 mov     edi, ecx
.text:4B2E0FB8                 and     [ebp+ms_exc.registration.TryLevel], 0
.text:4B2E0FBC                 mov     esi, _Kernel32ThreadInitThunkFunction
.text:4B2E0FC2                 push    edx
.text:4B2E0FC3                 test    esi, esi
.text:4B2E0FC5                 jz      loc_4B31E07C
.text:4B2E0FCB                 mov     ecx, esi
.text:4B2E0FCD                 call    ds:___guard_check_icall_fptr ;RtlpHpAppCompatDontChangePolicy()
.text:4B2E0FD3                 mov     edx, edi
.text:4B2E0FD5                 xor     ecx, ecx
.text:4B2E0FD7                 call    esi ; _Kernel32ThreadInitThunkFunction
.text:4B2E0FD9                 jmp     loc_4B31E0E0


 

 

.text:6B8162A0 ;__fastcall BaseThreadInitThunk(x, x, x)
.text:6B8162A0                 public @BaseThreadInitThunk@12
.text:6B8162A0@BaseThreadInitThunk@12 proc near       ;DATA XREF: .rdata:6B881DECo
.text:6B8162A0                                         ;.rdata:off_6B890348o
.text:6B8162A0
.text:6B8162A0var_4           = dword ptr -4
.text:6B8162A0 arg_0           = dwordptr  8
.text:6B8162A0
.text:6B8162A0                 mov     edi, edi
.text:6B8162A2                 push    ebp
.text:6B8162A3                 mov     ebp, esp
.text:6B8162A5                 push    ecx
.text:6B8162A6                 mov     eax, ___security_cookie
.text:6B8162AB                 xor     eax, ebp
.text:6B8162AD                 mov     [ebp+var_4], eax
.text:6B8162B0                 push    esi
.text:6B8162B1                 mov     esi, edx
.text:6B8162B3                 test    ecx, ecx
.text:6B8162B5                 jnz     short loc_6B8162CB
.text:6B8162B7                 push   [ebp+arg_0]
.text:6B8162BA                 mov     ecx, esi
.text:6B8162BC         call   ds:___guard_check_icall_fptr ; _guard_check_icall_nop(x)
.text:6B8162C2                 call    esi

 

我们看到,用户态每个线程的刚开始就被包含在一个异常里面,下一节我们来研究编译器提供的增强版本的异常处理机制。为了分析的简单,下一篇我们分析X86 Ring0 的SEH,其实和Ring3 做的工作差不多,Ring0 看着更清晰。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值