Windows异常世界历险记(二)——Win32用户层下SEH机制之对RtlUnwind的逆向分析

本文详细探讨了Windows用户层的结构化异常处理(SEH)机制,特别是RtlUnwind函数的逆向分析。作者指出,局部展开并非由系统自动完成,而是需要用户手动调用RtlUnwind。文章介绍了RtlUnwind的参数作用,并分享了逆向得到的C语言版本函数代码。同时,文章还提到了在Win8.1 X86平台上寻找RtlUnwind的挑战以及调用的相关子函数分析。

这篇文章写于14年10月,彼时正对Windows的SEH机制感兴趣,且国庆假期时间比较充裕,便记了几篇相关的笔记。后来逐渐忙于课业便没继续分析下去,3年来多次想完成这个系列,但每次刚一开头总会被各种琐事打断,一直未能如愿,希望这一次能如愿以偿地完成分析吧。



2017.12.04补充:
当时分析这个函数的动机有以下几个方面:

  1. Matt Pietrek在他的大作《A Crash Course on the Depths of Win32 Structured Exception Handling》中提到了退出展开,其中有这么一段话:

    When an exception occurs, the system walks the list of EXCEPTION_REGISTRATION structures until it finds a handler for the exception. Once a handler is found, the system walks the list again, up to the node that will handle the exception. During this second traversal, the system calls each handler function a second time. The key distinction is that in the second call, the value 2 is set in the exception flags. This value corresponds to EH_UNWINDING.

    我认为这段话是不准确的,事实上这样的操作被称为局部展开,是用户代码自愿进行的(言下之意就是这部分操作不是由Windows的用户层结构化异常处理机制来完成的)。在Matt Pietrek上面列举的例子中,由于在最外层使用了C/C++结构化异常处理中的异常处理块,因此导致内部原生异常处理程序不处理异常、执行流程转到外部的try-except块后,在其中(准确的说是except_handler4函数)执行了局部展开操作。因此,“局部展开会由系统完成”是一个很普遍的误会,网上很多文章都有类似的问题。就连《Windows核心编程》中的最后一部分也直接取名叫“结构化异常处理”,而事实上,其讲解的仅仅只是Visual Studio中C/C++的结构化异常处理机制而已,而局部展开行为尽管合理,但也需要用户手动进行,在系统的用户层结构化处理中是不会进行局部展开的。

  2. 其实局部展开的秘密,就隐藏在RtlUnwind函数的代码中。长时间以来,这个函数都处于undocument状态,虽然现在(2017年12月)可以在MSDN上查到了,但也是寥寥数语,不如自己分析来的痛快。

  3. 在漏洞利用中,SEH一直是一个热点话题。在经历了几轮魔道斗争后,SEH机制越来越成熟。而关于SEH安全机制的文章,网上一抓一大把,大多拿出的是伪代码和讲解,挖出代码进行分析的很少很少。本着“大海捞针,总不死心”和“眼见为实”的精神,咱们自己来分析一下。)

  4. Visual C++中的结构化异常处理是构建在Windows的用户层异常处理机制之上的。因此有必要先了解后者,以便更好地分析前者。

逆向成果

RtlUnwind函数是一个用户层的Windows API函数,其作用是执行SEH的展开操作(正是由于Windows的SEH机制不自动进行展开操作,才提供了此API,供需要执行该操作的用户调用)。下面介绍其参数:

void WINAPI RtlUnwind(
  _In_opt_ PVOID             TargetFrame,
  _In_opt_ PVOID             TargetIp,
  _In_opt_ PEXCEPTION_RECORD ExceptionRecord,
  _In_     PVOID             ReturnValue
);
  1. TargetFrame :

    • 当进行局部展开时,传入目标EXCEPTION_REGISTRATION_RECORD结构地址,当完成展开操作后,FS:[0]将指向这里。而执行该函数前的fs:[0]指向的节点到目标节点之间的前闭后开的所有合法节点中的处理函数都会被调用,被调用时传入的ExcepttionRecord.ExceptionFlags将包含EXCEPTION_UNWINDING操作。
    • 当进行全局展开时,传入NULL即可。此时SEH链上所有节点的SEH处理函数都会被调用,调用时传入的ExcepttionRecord.ExceptionFlags将包含EXCEPTION_EXIT_UNWIND标志。
    • 其余情况下需要谨慎传入,特别是不能传入比FS:[0]更低的地址,否则将引发异常。
  2. TargetIp:虽然很多地方说通过这个参数可以设定函数执行完成后要执行的指令地址,但经过分析,至少在Win8.1和XP下,这个参数并没有什么用。

  3. ExceptionRecord:在展开过程中,传递给被调用的异常处理函数的EXCEPTION_RECORD结构。如果传入NULL,将由本函数自己构造一个,其构造的那个ExceptionCode为STATUS_UNWIND,ExceptionAddress为调用本函数时压入的返回地址。

  4. ReturnValue:作为传给被调用的异常处理函数的CONTEXT结构中的eax值与恢复线程上下文环境时的eax值。

先贴上逆向的结果——C语言版本的RtlUnwind的代码(在IDA里按了F5然后对着3年前自己的逆向成果得来的):

void __stdcall RtlUnwind(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
{
  PEXCEPTION_RECORD finalExcepttionRecord; // esi
  unsigned int dwCurrentSEHNode; // ebx
  unsigned int pfnHandler; // ecx
  void *pExceptionRecord; // ecx
  int dwRet; // eax
  int *dwPrevSEHNode; // ST10_4
  unsigned int StackLimit; // [esp+Ch] [ebp-384h]
  unsigned int StackBase; // [esp+10h] [ebp-380h]
  unsigned int DispatcherContext; // [esp+14h] [ebp-37Ch]
  EXCEPTION_RECORD tempExceptionRecord; // [esp+18h] [ebp-378h]
  EXCEPTION_RECORD temp2ExceptionRecord; // [esp+68h] [ebp-328h]
  CONTEXT stContext; // [esp+B8h] [ebp-2D8h]
  int savedregs; // [esp+390h] [ebp+0h]
  void *retaddr; // [esp+394h] [ebp+4h]

  finalExcepttionRecord = ExceptionRecord;
  RtlpGetStackLimits((void ***)&StackLimit, (void ***)&StackBase);// 获取线程的栈底和栈顶
  if ( !ExceptionRecord )                       // 如果没有传入ExceptionRecord,则构造一个,作为出现异常时抛出的那个
                                                // 否则就用传入的那个
  {
    finalExcepttionRecord = &temp2ExceptionRecord;
    temp2ExceptionRecord.ExceptionCode = 0xC0000027;// 0xC0000027 = STATUS_UNWIND
    temp2ExceptionRecord.ExceptionFlags = 0;
    temp2ExceptionRecord.ExceptionRecord = 0;
    temp2ExceptionRecord.ExceptionAddress = retaddr;// 这里取出的是调用本函数之前call压入的返回地址
    temp2ExceptionRecord.NumberParameters = 0;
  }
  if ( TargetFrame )
    finalExcepttionRecord->ExceptionFlags |= 2u;// #define EXCEPTION_UNWINDING 0x2
                                                // 如果传入的TargetFrame非空,则进行局部展开
  else
    finalExcepttionRecord->ExceptionFlags |= 6u;// #define EXCEPTION_EXIT_UNWIND 0x4 
                                                // 如果传入的TargetFrame为NULL,则进行退出展开
  RtlpCaptureContext(&savedregs, (int)&stContext);// 填写stContext结构,通用寄存器组清0,
                                                // EIP设置为call本函数时压入的返回地址
                                                // EBP设置为调用本函数时的EBP值(即前一帧的EBP)
                                                // ESP设置为指向TargetFrame的值(因为从本函数ret后这里就应该是ESP该指向的位置)
  stContext.Esp += 16;                          // 上下文结构中的ESP结构加上16,平衡了压入的4个参数
  stContext.Eax = (unsigned int)ReturnValue;    // 将传入的ReturnValue设置给eax作为返回值
  dwCurrentSEHNode = RtlpGetRegistrationHead(); // 从fs:0处获得异常链的首节点
  while ( dwCurrentSEHNode != 0xFFFFFFFF )      // 当前线程注册了自己的SEH处理函数,而非kernel32默认的那个
  {
    if ( (PVOID)dwCurrentSEHNode == TargetFrame )// 如果已到达指定节点,则按照之前设定的Context恢复线程的环境并运行
    {
      ZwContinue(&stContext, 0);
    }
    else if ( TargetFrame && (unsigned int)TargetFrame < dwCurrentSEHNode )// 传入的TargetFrame不为NULL且SEH链首节点高于TargetFrame,抛异常
                                                // (因为合法的SEH节点肯定在栈中,则从SEH首节点开始,应该依次降低)
                                                // 而这里反到首节点在目标节点下方,肯定传入的参数有问题
    {
      tempExceptionRecord.NumberParameters = 0;
      tempExceptionRecord.ExceptionCode = 0xC0000029;
      tempExceptionRecord.ExceptionFlags = 1;
      tempExceptionRecord.ExceptionRecord = finalExcepttionRecord;
      RtlRaiseException(&tempExceptionRecord);
    }
    if ( dwCurrentSEHNode < StackLimit          // 异常情况:SEH首节点比栈顶极限位置还高(这里的高指的是地址更低)
      || dwCurrentSEHNode + 8 > StackBase       // 异常情况:SEH首节点的最末尾成员比栈底还要低(这里的低指的是地址更高)
      || dwCurrentSEHNode & 3                   // 异常情况:SEH首节点地址不能被4整除(没对齐)
      || (pfnHandler = *(_DWORD *)(dwCurrentSEHNode + 4), pfnHandler < StackBase) && pfnHandler >= StackLimit// 异常情况:异常处理节点的函数地址落在栈空间中(说明多半是被溢出了)
      || !RtlIsValidHandler(pfnHandler, 0, (int)&stContext) )// 异常情况:被SafeSEH机制给判定为非法处理函数地址
    {                                           // 以上异常情况,抛出异常,拒绝执行处理函数
      tempExceptionRecord.NumberParameters = 0;
      tempExceptionRecord.ExceptionCode = 0xC0000028;
      tempExceptionRecord.ExceptionFlags = 1;
      tempExceptionRecord.ExceptionRecord = finalExcepttionRecord;
      RtlRaiseException(&tempExceptionRecord);
    }
    else
    {
      dwRet = RtlpExecuteHandlerForUnwind(      // 调用目标SEH节点的处理函数,执行退出前的清理动作
                pExceptionRecord,
                (int)finalExcepttionRecord,
                dwCurrentSEHNode,
                (int)&stContext,
                (int)&DispatcherContext,
                *(_DWORD *)(dwCurrentSEHNode + 4))
            - 1;                                // 这里很顽皮,居然在原SEH函数的结果基础上减了1
                                                // ExceptionContinueExecution = 0n0 //已处理异常,程序可继续执行
                                                // ExceptionContinueSearch = 0n1 //函数不处理该异常,继续搜寻异常处理函数链
                                                // ExceptionNestedException = 0n2 //在处理异常的过程中产生了新的异常
                                                // ExceptionCollidedUnwind = 0n3 //嵌套展开
                                                // 分别减1
      if ( dwRet )                              // 如果不为ExceptionContinueSearch,则表明异常处理函数没按套路出牌
      {
        if ( dwRet == 2 )                       // 如果返回的是CollidedUnwind,则将DispatcherContext作为下一个SEH节点展开
        {
          dwCurrentSEHNode = DispatcherContext;
        }
        else                                    // 否则如果返回ContinueExecution、NestedException都会抛异常
        {
          tempExceptionRecord.NumberParameters = 0;
          tempExceptionRecord.ExceptionCode = 0xC0000026;
          tempExceptionRecord.ExceptionFlags = 1;
          tempExceptionRecord.ExceptionRecord = finalExcepttionRecord;
          RtlRaiseException(&tempExceptionRecord);
        }
      }
      dwPrevSEHNode = (int *)dwCurrentSEHNode;
      dwCurrentSEHNode = *(_DWORD *)dwCurrentSEHNode;// 工作指针后移
      RtlpUnlinkHandler(dwPrevSEHNode);         // 将fs:0指向当前节点的下一个节点,即取消掉本节点
    }
  }
  if ( TargetFrame == (PVOID)0xFFFFFFFF )       // 如果传入的TargetFrame为0xFFFFFFFF,也不可能把这个节点干掉了,表明也到头了,该返回了
    ZwContinue(&stContext, 0);
  else
    ZwRaiseException(finalExcepttionRecord, &stContext, 0);// 如果传入的TargetFrame为其他值,则抛异常(如果匹配到了的话,早在while中就恢复上下文执行了)
}



以下是2014.10.08的笔记:

逆向过程

不知由于何种原因,在Win 8.1 X86平台下,试图用静态方式找出RtlUnwind函数的宿主还费了一番功夫。首先,在kernel32.dll的导出表中发现了该函数,可是立即又发现其仅是一个傀儡:

.text:6B81C83C     ; void __stdcall RtlUnwindStub(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
.text:6B81C83C     public _RtlUnwindStub@16
.text:6B81C83C     _RtlUnwindStub@16 proc near
.text:6B81C83C
.text:6B81C83C     TargetFrame= dword ptr  4
.text:6B81C83C     TargetIp= dword ptr  8
.text:6B81C83C     ExceptionRecord= dword ptr  0Ch
.text:6B81C83C     ReturnValue= dword ptr  10h
.text:6B81C83C
.text:6B81C83C 000 jmp     ds:__imp__RtlUnwind@16 ; RtlUnwind(x,x,x,x)
.text:6B81C83C     _RtlUnwindStub@16 endp

于是跟着JMP跳到了:

.idata:6B880A24                 extrn _api_ms_win_core_registry_l1_1_0_NULL_THUNK_DATA:byte:4
.idata:6B880A28 ;
.idata:6B880A28 ; Imports from api-ms-win-core-rtlsupport-l1-2-0.dll
.idata:6B880A28 ;
.idata:6B880A28 ; SIZE_T __stdcall RtlCompareMemory(const void *Source1, const void *Source2, SIZE_T Length)
.idata:6B880A28                 extrn __imp__RtlCompareMemory@12:dword
.idata:6B880A28                                         ; CODE XREF: FSPErrorMessages::CMessageTagCache::LookupNodeByMessageTag(ulong,FSPErrorMessages::MessageTag *)+29p
.idata:6B880A28                                         ; DATA XREF: FSPErrorMessages::CMessageTagCache::LookupNodeByMessageTag(ulong,FSPErrorMessages::MessageTag *)+29r ...
.idata:6B880A2C ; __declspec(dllimport) __stdcall RtlRaiseException(x)
.idata:6B880A2C                 extrn __imp__RtlRaiseException@4:dword
.idata:6B880A2C                                         ; CODE XREF: PsspValidateWin32WalkMarker+9Ep
.idata:6B880A2C                                         ; DATA XREF: PsspValidateWin32WalkMarker+9Er
.idata:6B880A30 ; WORD __stdcall RtlCaptureStackBackTrace(DWORD FramesToSkip, DWORD FramesToCapture, PVOID *BackTrace, PDWORD BackTraceHash)
.idata:6B880A30                 extrn __imp__RtlCaptureStackBackTrace@16:dword
.idata:6B880A30                                         ; DATA XREF: RtlCaptureStackBackTraceStub(x,x,x,x)+6r
.idata:6B880A34 ; void __stdcall RtlUnwind(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
.idata:6B880A34                 extrn __imp__RtlUnwind@16:dword
.idata:6B880A34                                         ; DATA XREF: RtlUnwindStub(x,x,x,x)r
.idata:6B880A34                                         ; RtlUnwind(x,x,x,x)r
.idata:6B880A38 ; void __stdcall RtlCaptureContext(PCONTEXT ContextRecord)
.idata:6B880A38                 extrn __imp__RtlCaptureContext@4:dword
.idata:6B880A38                                         ; CODE XREF: RaiseFailFastException(x,x,x)+86p
.idata:6B880A38                                         ; DATA XREF: RtlCaptureContext(x)r ...
.idata:6B880A3C                 extrn _api_ms_win_core_rtlsupport_l1_2_0_NULL_THUNK_DATA:byte:4
.idata:6B880A40 ;    

立即找到api-ms-win-core-rtlsupport-l1-2-0.dll,结果大吃一惊,连傀儡都不如:

.text:1000109E ; void __stdcall RtlUnwind(PVOID TargetFrame, PVOID TargetIp, PEXCEPTION_RECORD ExceptionRecord, PVOID ReturnValue)
.text:1000109E                 public _RtlUnwind@16
.text:1000109E _RtlUnwind@16   proc near               ; DATA XREF: .text:off_100010D8o
.text:1000109E
.text:1000109E TargetFrame     = dword ptr  4
.text:1000109E TargetIp        = dword ptr  8
.text:1000109E ExceptionRecord = dword ptr  0Ch
.text:1000109E ReturnValue     = dword ptr  10h
.text:1000109E
.text:1000109E                 retn    10h
.text:1000109E _RtlUnwind@16   endp

这里要吐槽一下IDA Pro 5.5,在设置了PDB服务器和路径后竟然没有解析出pdb文件中的变量名。害得我连蒙带猜,还开着VS2013调试,只为获取call后的函数名。

.text:6B2E755F     ; //=============== S U B R O U T I N E =======================================
.text:6B2E755F
.text:6B2E755F     ; //VOID _stdcall
.text:6B2E755F     ; //RtlUnwind(
.text:6B2E755F     ; //  IN  PVOID TargetFrame OPTIONAL //指向当前异常处理函数的EXCEPTION_REGISTRAION结构,
.text:6B2E755F     ; //  会展开SEH链上之前所有的节点,
.text:6B2E755F     ; //  并发送EXCEPTION_UNWINDING;
.text:6B2E755F     ; //  若留空,则为推出展开,发送EXCEPTION_UNWINDING or EXCEPTION_UNWINDING_FOR_EXIT
.text:6B2E755F     ; //  IN  PVOID TargetIp OPTIONAL //指定函数返回后下一条指令地址,若留空,则默认,经过分析,该参数无效!
.text:6B2E755F     ; //  IN  PEXCEPTION_RECORD ExceptionRecord OPTIONAL
.text:6B2E755F     ; //  指向EXCEPTION_RECORD,建议留空由系统填写
.text:6B2E755F     ; //  IN  PVOID ReturnValue
.text:6B2E755F     ; //  ); //
.text:6B2E755F     ;
.text:6B2E755F     ; //Attributes: bp-based frame
.text:6B2E755F
.text:6B2E755F                     public RtlUnwind
.text:6B2E755F     RtlUnwind       proc near               ; //CODE XREF: .text:6B2BC649p
.text:6B2E755F                                             ; //.text:6B2BCB42p ...
.text:6B2E755F
.text:6B2E755F     StackLimit      = dword ptr -384h
.text:6B2E755F     StackBase       = dword ptr -380h
.text:6B2E755F     DispatcherContext= dword ptr -37Ch
.text:6B2E755F     tempExceptionRecord= EXCEPTION_RECORD ptr -378h
.text:6B2E755F     stExceptionRecord= EXCEPTION_RECORD ptr -328h
.text:6B2E755F     stContext       = CONTEXT ptr -2D8h
.text:6B2E755F     CookiePointer   = dword ptr -4
.text:6B2E755F     TargetFrame     = dword ptr  8
.text:6B2E755F     ExceptionRecord = dword ptr  10h
.text:6B2E755F     dwRet           = dword ptr  14h
.text:6B2E755F
.text:6B2E755F     ; //FUNCTION CHUNK AT .text:6B2F597C SIZE 0000000F BYTES
.text:6B2E755F     ; //FUNCTION CHUNK AT .text:6B31CD49 SIZE 000000B8 BYTES
.text:6B2E755F
.text:6B2E755F 000                 mov     edi, edi
.text:6B2E7561 000                 push    ebp
.text:6B2E7562 004                 mov     ebp, esp
.text:6B2E7564 004                 and     esp, 0FFFFFFF8h ; //栈内8Byte对齐
.text:6B2E7567 004                 sub     esp, 384h
.text:6B2E756D 388                 mov     eax, ds:Security_Cookie
.text:6B2E7572 388                 xor     eax, esp
.text:6B2E7574 388                 mov     [esp+384h+CookiePointer], eax
.text:6B2E757B 388                 push    ebx
.text:6B2E757C 38C                 push    esi
.text:6B2E757D 390                 mov     esi, [ebp+ExceptionRecord]
.text:6B2E7580 390                 lea     edx, [esp+38Ch+StackBase]
.text:6B2E7584 390                 push    edi
.text:6B2E7585 394                 lea     ecx, [esp+390h+StackLimit]
.text:6B2E7589 394                 call    _RtlpGetStackLimits@8 ; //获取当前线程栈的栈顶和栈底
.text:6B2E758E 394                 xor     ecx, ecx
.text:6B2E7590 394                 test    esi, esi
.text:6B2E7592 394                 jnz     short ValidExceptionRecord ; //若用户指定了ExceptionRecord,跳走
.text:6B2E7594 394                 mov     eax, [ebp+4]    ; //函数返回地址
.text:6B2E7597 394                 lea     esi, [esp+390h+stExceptionRecord]
.text:6B2E759B 394                 mov     [esp+390h+stExceptionRecord.ExceptionCode], STATUS_UNWIND
.text:6B2E75A3 394                 mov     [esp+390h+stExceptionRecord.ExceptionFlags], ecx
.text:6B2E75A7 394                 mov     [esp+390h+stExceptionRecord.ExceptionRecord], ecx
.text:6B2E75AB 394                 mov     [esp+390h+stExceptionRecord.ExceptionAddress], eax
.text:6B2E75AF 394                 mov     [esp+390h+stExceptionRecord.NumberParameters], ecx
.text:6B2E75B3
.text:6B2E75B3     ValidExceptionRecord:                   ; //CODE XREF: RtlUnwind+33j
.text:6B2E75B3 394                 mov     edi, [ebp+TargetFrame] ; //若为空,则为退出展开,否则为局部展开
.text:6B2E75B6 394                 test    edi, edi
.text:6B2E75B8 394                 jz      NullTargetFrame
.text:6B2E75BE 394                 or      [esi+EXCEPTION_RECORD.ExceptionFlags], 2 ; //#define EXCEPTION_UNWINDING 0x2         // Unwind is in progress
.text:6B2E75C2
.text:6B2E75C2     loc_6B2E75C2:                           ; //CODE XREF: RtlUnwind+126j
.text:6B2E75C2 394                 lea     eax, [esp+390h+stContext]
.text:6B2E75C9 394                 push    eax
.text:6B2E75CA 398                 call    _RtlpCaptureContext@4 ;
.text:6B2E75CA                                             ; //CONTEXT.EBP = 调用RtlUnwind前的ebp
.text:6B2E75CA                                             ; //CONTEXT.ESP = 调用RtlUnwind传参后的ESP
.text:6B2E75CA                                             ; //CONTEXT.EIP = RtlUnwind执行完后下一条语句的地址
.text:6B2E75CF 394                 mov     eax, [ebp+dwRet]
.text:6B2E75D2 394                 add     [esp+390h+stContext._Esp], 10h ; //ESP = 还没压参前的ESP
.text:6B2E75DA 394                 mov     [esp+390h+stContext._Eax], eax ; //EAX 被改为传入的返回值
.text:6B2E75E1 394                 call    _RtlpGetRegistrationHead@0 ; //取得首个SEH链节点地址
.text:6B2E75E6 394                 mov     ebx, eax
.text:6B2E75E8
.text:6B2E75E8     ProcessSEH:                             ; //CODE XREF: RtlUnwind+10Cj
.text:6B2E75E8                                             ; //RtlUnwind+3586Aj
.text:6B2E75E8 394                 cmp     ebx, 0FFFFFFFFh ; //SEH链上没有函数
.text:6B2E75EB 394                 jz      NullSEHChain
.text:6B2E75F1 394                 cmp     ebx, edi        ; //到达局部展开的节点
.text:6B2E75F3 394                 jz      short IsDestSEHNode
.text:6B2E75F5 394                 test    edi, edi        ; //是局部展开还是退出展开
.text:6B2E75F7 394                 jz      short CheckOutOfBound
.text:6B2E75F9 394                 cmp     edi, ebx        ;
.text:6B2E75F9                                             ; //由于SEH链按栈生长,后加入的节点在SEH链的前端,
.text:6B2E75F9                                             ; //为小地址。此时竟然出现比SEH链首地址还低的节点,
.text:6B2E75F9                                             ; //一定出现了错误
.text:6B2E75F9                                             ;
.text:6B2E75FB 394                 jb      NotValidExceptionChain ;
.text:6B2E75FB                                             ; //引发一个异常,异常号为STATUS_INVALID_UNWIND_TARGET
.text:6B2E75FB                                             ; //异常标志为EXCEPTION_NONCONTINUABLE,并将传入RtlUnwind
.text:6B2E75FB                                             ; //的PEXCEPTION_RECORD作为关联EXCEPTION_RECORD填入主
.text:6B2E75FB                                             ; //EXCEPTION_RECORD
.text:6B2E7601
.text:6B2E7601     CheckOutOfBound:                        ; //CODE XREF: RtlUnwind+98j
.text:6B2E7601                                             ; //RtlUnwind+120j ...
.text:6B2E7601 394                 cmp     ebx, [esp+390h+StackLimit]
.text:6B2E7605 394                 jb      ErrorOutOfStack
.text:6B2E760B 394                 lea     eax, [ebx+8]    ;
.text:6B2E760B                                             ; //判断SEH链最底端((ebx+8)刚好覆盖SEH结构)
.text:6B2E760B                                             ; //是否越过线程栈底(高地址)
.text:6B2E760E 394                 cmp     eax, [esp+390h+StackBase]
.text:6B2E7612 394                 ja      ErrorOutOfStack
.text:6B2E7618 394                 test    bl, 3           ; //SEH链地址不能被4整除
.text:6B2E761B 394                 jnz     ErrorOutOfStack
.text:6B2E7621 394                 mov     ecx, [ebx+4]    ; //ECX = SEH_Handler
.text:6B2E7624 394                 cmp     ecx, [esp+390h+StackBase] ;
.text:6B2E7624                                             ; //函数绝不可能在栈上,函数要么位于系统Dll领空,
.text:6B2E7624                                             ; //要么位于程序代码段,二者地址都大于堆栈段
.text:6B2E7628 394                 jb      ErrorSEHFuncAddr
.text:6B2E762E
.text:6B2E762E     Unwinding:                              ; //CODE XREF: RtlUnwind+E427j
.text:6B2E762E 394                 lea     eax, [esp+390h+stContext]
.text:6B2E7635 394                 xor     edx, edx
.text:6B2E7637 394                 push    eax
.text:6B2E7638 398                 call    RtlIsValidHandler
.text:6B2E763D 394                 test    al, al
.text:6B2E763F 394                 jz      ErrorOutOfStack
.text:6B2E7645 394                 push    dword ptr [ebx+4] ; //SEH_Handler入栈
.text:6B2E7648 398                 lea     eax, [esp+394h+DispatcherContext]
.text:6B2E764C 398                 push    eax
.text:6B2E764D 39C                 lea     eax, [esp+398h+stContext]
.text:6B2E7654 39C                 push    eax
.text:6B2E7655 3A0                 push    ebx
.text:6B2E7656 3A4                 push    esi
.text:6B2E7657 3A8                 call    _RtlpExecuteHandlerForUnwind@20 ;
.text:6B2E7657                                             ; //(PEXCEPTION_RECORD,SEH链,PCONTEXT,&dwDispatcherContext,SEH_Handler)
.text:6B2E765C 394                 dec     eax
.text:6B2E765D 394                 jnz     loc_6B31CD71
.text:6B2E7663
.text:6B2E7663     UninstMoveForward:                      ; //CODE XREF: RtlUnwind+35839j
.text:6B2E7663                                             ; //RtlUnwind+35842j
.text:6B2E7663 394                 push    ebx
.text:6B2E7664 398                 mov     ebx, [ebx]      ; //ebx = NextSeh
.text:6B2E7666 398                 call    _RtlpUnlinkHandler@4 ;
.text:6B2E7666                                             ; //设置FS:[0]指向下一SEH节点
.text:6B2E766B 394                 jmp     ProcessSEH
.text:6B2E7670     ; //---------------------------------------------------------------------------
.text:6B2E7670
.text:6B2E7670     IsDestSEHNode:                          ; //CODE XREF: RtlUnwind+94j
.text:6B2E7670 394                 push    0
.text:6B2E7672 398                 lea     eax, [esp+394h+stContext]
.text:6B2E7679 398                 push    eax
.text:6B2E767A 39C                 call    ZwContinue      ; //实际到了这句就不会往下走了吧?
.text:6B2E767F 394                 jmp     short CheckOutOfBound
.text:6B2E7681     ; //---------------------------------------------------------------------------
.text:6B2E7681
.text:6B2E7681     NullTargetFrame:                        ; //CODE XREF: RtlUnwind+59j
.text:6B2E7681 394                 or      [esi+EXCEPTION_RECORD.ExceptionFlags], 6 ; //#define EXCEPTION_EXIT_UNWIND 0x4       // Exit unwind is in progress
.text:6B2E7685 394                 jmp     loc_6B2E75C2
.text:6B2E7685     RtlUnwind       endp ; //sp-analysis failed

.text:6B31CD49     ; //START OF FUNCTION CHUNK FOR RtlUnwind
.text:6B31CD49
.text:6B31CD49     NotValidExceptionChain:                 ; //CODE XREF: RtlUnwind+9Cj
.text:6B31CD49 394                 and     [esp+390h+tempExceptionRecord.NumberParameters], 0
.text:6B31CD4E 394                 lea     eax, [esp+390h+tempExceptionRecord]
.text:6B31CD52 394                 push    eax
.text:6B31CD53 398                 mov     [esp+394h+tempExceptionRecord.ExceptionCode], STATUS_INVALID_UNWIND_TARGET
.text:6B31CD5B 398                 mov     [esp+394h+tempExceptionRecord.ExceptionFlags], 1 ; //EXCEPTION_NONCONTINUABLE
.text:6B31CD63 398                 mov     [esp+394h+tempExceptionRecord.ExceptionRecord], esi ; //传入RtlUnwind的PEXCEPTION_RECORD
.text:6B31CD67 398                 call    RtlRaiseException
.text:6B31CD6C 394                 jmp     CheckOutOfBound
.text:6B31CD71     ; //---------------------------------------------------------------------------
.text:6B31CD71
.text:6B31CD71     loc_6B31CD71:                           ; //CODE XREF: RtlUnwind+FEj
.text:6B31CD71 394                 dec     eax
.text:6B31CD72 394                 dec     eax
.text:6B31CD73 394                 jz      short CollidedUnwind
.text:6B31CD75 394                 and     [esp+390h+tempExceptionRecord.NumberParameters], 0
.text:6B31CD7A 394                 lea     eax, [esp+390h+tempExceptionRecord]
.text:6B31CD7E 394                 push    eax
.text:6B31CD7F 398                 mov     [esp+394h+tempExceptionRecord.ExceptionCode], 0C0000026h
.text:6B31CD87 398                 mov     [esp+394h+tempExceptionRecord.ExceptionFlags], 1
.text:6B31CD8F 398                 mov     [esp+394h+tempExceptionRecord.ExceptionRecord], esi
.text:6B31CD93 398                 call    RtlRaiseException
.text:6B31CD98 394                 jmp     UninstMoveForward
.text:6B31CD9D     ; //---------------------------------------------------------------------------
.text:6B31CD9D
.text:6B31CD9D     CollidedUnwind:                         ; //CODE XREF: RtlUnwind+35814j
.text:6B31CD9D 394                 mov     ebx, [esp+390h+DispatcherContext]
.text:6B31CDA1 394                 jmp     UninstMoveForward
.text:6B31CDA6     ; //---------------------------------------------------------------------------
.text:6B31CDA6
.text:6B31CDA6     ErrorOutOfStack:                        ; //CODE XREF: RtlUnwind+A6j
.text:6B31CDA6                                             ; //RtlUnwind+B3j ...
.text:6B31CDA6 394                 and     [esp+390h+tempExceptionRecord.NumberParameters], 0
.text:6B31CDAB 394                 lea     eax, [esp+390h+tempExceptionRecord]
.text:6B31CDAF 394                 push    eax
.text:6B31CDB0 398                 mov     [esp+394h+tempExceptionRecord.ExceptionCode], STATUS_BAD_STACK
.text:6B31CDB8 398                 mov     [esp+394h+tempExceptionRecord.ExceptionFlags], 1
.text:6B31CDC0 398                 mov     [esp+394h+tempExceptionRecord.ExceptionRecord], esi
.text:6B31CDC4 398                 call    RtlRaiseException
.text:6B31CDC9 394                 jmp     ProcessSEH
.text:6B31CDCE     ; //---------------------------------------------------------------------------
.text:6B31CDCE
.text:6B31CDCE     NullSEHChain:                           ; //CODE XREF: RtlUnwind+8Cj
.text:6B31CDCE 394                 lea     eax, [esp+390h+stContext]
.text:6B31CDD5 394                 push    0
.text:6B31CDD7 398                 push    eax
.text:6B31CDD8 39C                 cmp     edi, 0FFFFFFFFh
.text:6B31CDDB 39C                 jnz     short RaiseException
.text:6B31CDDD 39C                 call    ZwContinue
.text:6B31CDE2 394                 jmp     short ExitFunc
.text:6B31CDE4     ; //---------------------------------------------------------------------------
.text:6B31CDE4
.text:6B31CDE4     RaiseException:                         ; //CODE XREF: RtlUnwind+3587Cj
.text:6B31CDE4 39C                 push    esi
.text:6B31CDE5 3A0                 call    ZwRaiseException
.text:6B31CDEA
.text:6B31CDEA     ExitFunc:                               ; //CODE XREF: RtlUnwind+35883j
.text:6B31CDEA 394                 mov     ecx, [esp+390h+CookiePointer]
.text:6B31CDF1 394                 pop     edi
.text:6B31CDF2 390                 pop     esi
.text:6B31CDF3 38C                 pop     ebx
.text:6B31CDF4 388                 xor     ecx, esp
.text:6B31CDF6 388                 call    CheckCookie
.text:6B31CDFB 388                 mov     esp, ebp
.text:6B31CDFD 004                 pop     ebp
.text:6B31CDFE 000                 retn    10h
.text:6B31CDFE     ; //END OF FUNCTION CHUNK FOR RtlUnwind
.text:6B31CE01     ; //---------------------------------------------------------------------------

调用的子函数分析

下面是其中函数的分析:

_RtlpGetStackLimits@8:

.text:6B2E71C5     ; // =============== S U B R O U T I N E =======================================
.text:6B2E71C5
.text:6B2E71C5     ; // BOOL _fastcall RtlpGetStackLimits(lpdwStackLimit(ecx),lpdwStackBase(edx))
.text:6B2E71C5     ; // 1 = NormalStack
.text:6B2E71C5     ; // 0 = DestroyedStack
.text:6B2E71C5     ; // 将栈底和栈顶地址分别写入edx和ecx指向的变量中,
.text:6B2E71C5     ; // 并判断调用函数之前堆栈是否越界。
.text:6B2E71C5     ; // Attributes: bp-based frame
.text:6B2E71C5
.text:6B2E71C5     _RtlpGetStackLimits@8 proc near         ; // CODE XREF: sub_6B2E724E+64p
.text:6B2E71C5                                             ; // RtlUnwind+2Ap
.text:6B2E71C5 000                 mov     edi, edi
.text:6B2E71C7 000                 push    ebp
.text:6B2E71C8 004                 mov     ebp, esp
.text:6B2E71CA 004                 mov     eax, large fs:18h ; // eax = TEB
.text:6B2E71D0 004                 push    esi
.text:6B2E71D1 008                 push    edi
.text:6B2E71D2 00C                 lea     edi, [ebp+4]
.text:6B2E71D5 00C                 mov     esi, [eax+4]    ; // esi = StackBase
.text:6B2E71D8 00C                 mov     eax, [eax+8]    ; // eax = StackLimit
.text:6B2E71DB 00C                 mov     [edx], esi      ; // [edx] = StackBase
.text:6B2E71DD 00C                 mov     [ecx], eax      ; // [ecx] = StackLimit
.text:6B2E71DF 00C                 cmp     eax, edi        ; // 比较限制栈顶和当前栈顶(指call本函数之前的栈)
.text:6B2E71E1 00C                 ja      short DestoryedStack
.text:6B2E71E3 00C                 cmp     edi, esi        ; // 当前栈顶是否高于限制栈底
.text:6B2E71E5 00C                 jnb     short DestoryedStack
.text:6B2E71E7 00C                 mov     al, 1
.text:6B2E71E9
.text:6B2E71E9     RecvyPointerReg:                        ; // CODE XREF: RtlpGetStackLimits(x,x)+2Aj
.text:6B2E71E9 00C                 pop     edi
.text:6B2E71EA 008                 pop     esi
.text:6B2E71EB 004                 pop     ebp
.text:6B2E71EC 000                 retn
.text:6B2E71ED     ; // ---------------------------------------------------------------------------
.text:6B2E71ED
.text:6B2E71ED     DestoryedStack:                         ; // CODE XREF: RtlpGetStackLimits(x,x)+1Cj
.text:6B2E71ED                                             ; // RtlpGetStackLimits(x,x)+20j
.text:6B2E71ED 00C                 xor     al, al
.text:6B2E71EF 00C                 jmp     short RecvyPointerReg
.text:6B2E71EF     _RtlpGetStackLimits@8 endp
.text:6B2E71EF
.text:6B2E71EF     ; // ---------------------------------------------------------------------------

_RtlpCaptureContext@4

.text:6B2BC923     ; // // __stdcall RtlpCaptureContext(x)
.text:6B2BC923     _RtlpCaptureContext@4:                  ; // // CODE XREF: RtlUnwind+6Bp
.text:6B2BC923                     push    ebx
.text:6B2BC924                     mov     ebx, [esp+8]    ; // // [esp+8] = eax = &stContext
.text:6B2BC928                     mov     [ebx+CONTEXT._Eax], 0
.text:6B2BC932                     mov     [ebx+CONTEXT._Ecx], 0
.text:6B2BC93C                     mov     [ebx+CONTEXT._Edx], 0
.text:6B2BC946                     mov     [ebx+CONTEXT._Ebx], 0
.text:6B2BC950                     mov     [ebx+CONTEXT._Esi], 0
.text:6B2BC95A                     mov     [ebx+CONTEXT._Edi], 0
.text:6B2BC964
.text:6B2BC964     loc_6B2BC964:                           ; // // CODE XREF: RtlCaptureContext+2Cj
.text:6B2BC964                     mov     word ptr [ebx+CONTEXT.SegCs], cs
.text:6B2BC96A                     mov     word ptr [ebx+CONTEXT.SegDs], ds
.text:6B2BC970                     mov     word ptr [ebx+CONTEXT.SegEs], es
.text:6B2BC976                     mov     word ptr [ebx+CONTEXT.SegFs], fs
.text:6B2BC97C                     mov     word ptr [ebx+CONTEXT.SegGs], gs
.text:6B2BC982                     mov     word ptr [ebx+CONTEXT.SegSs], ss
.text:6B2BC988                     pushf
.text:6B2BC989                     pop     [ebx+CONTEXT.EFlags]
.text:6B2BC98F                     mov     eax, [ebp+4]    ; // // stContext.eip = RtlUnwind后的第一条指令地址
.text:6B2BC992                     mov     [ebx+CONTEXT._Eip], eax
.text:6B2BC998                     mov     eax, [ebp+0]    ; // // stContext.ebp = RtlUnwind前的EBP值
.text:6B2BC99B                     mov     [ebx+CONTEXT._Ebp], eax
.text:6B2BC9A1                     lea     eax, [ebp+8]    ; // // stContext.esp = ebp+8 = &TargetFrame(最后一个入栈参数的地址)
.text:6B2BC9A4                     mov     [ebx+CONTEXT._Esp], eax
.text:6B2BC9AA                     mov     [ebx+CONTEXT.ContextFlags], CONTEXT_FULL
.text:6B2BC9B0                     pop     ebx
.text:6B2BC9B1                     retn    4

_RtlpGetRegistrationHead@0:

.text:6B2BC9B7     ; __stdcall RtlpGetRegistrationHead()
.text:6B2BC9B7     _RtlpGetRegistrationHead@0:             ; CODE XREF: sub_6B2E724E+69p
.text:6B2BC9B7                                             ; RtlUnwind+82p ...
.text:6B2BC9B7                     mov     eax, large fs:0
.text:6B2BC9BD                     retn
.text:6B2BC9BE     ; ---------------------------------------------------------------------------

_RtlpExecuteHandlerForUnwind@20

.text:6B2BC828     ; // =============== S U B R O U T I N E =======================================
.text:6B2BC828
.text:6B2BC828
.text:6B2BC828     ; // __stdcall RtlpExecuteHandlerForUnwind(x, x, x, x, x)
.text:6B2BC828     _RtlpExecuteHandlerForUnwind@20 proc near ; // CODE XREF: RtlUnwind+F8p
.text:6B2BC828
.text:6B2BC828     PEXCEPTION_RECORD= dword ptr  4
.text:6B2BC828     SEH             = dword ptr  8
.text:6B2BC828     PCONTEXT        = dword ptr  0Ch
.text:6B2BC828     dwDispatcherContext= dword ptr  10h
.text:6B2BC828     lpSEHHandlerFunc= dword ptr  14h
.text:6B2BC828
.text:6B2BC828 000                 mov     edx, offset TempSEHFunc
.text:6B2BC82D 000                 lea     ecx, [ecx]
.text:6B2BC82F
.text:6B2BC82F     loc_6B2BC82F:                           ; // CODE XREF: sub_6B2BC820+5j
.text:6B2BC82F 000                 push    ebx
.text:6B2BC830 004                 push    esi
.text:6B2BC831 008                 push    edi
.text:6B2BC832 00C                 xor     eax, eax
.text:6B2BC834 00C                 xor     ebx, ebx
.text:6B2BC836 00C                 xor     esi, esi
.text:6B2BC838 00C                 xor     edi, edi
.text:6B2BC83A 00C                 push    [esp+0Ch+lpSEHHandlerFunc]
.text:6B2BC83E 010                 push    [esp+10h+dwDispatcherContext]
.text:6B2BC842 014                 push    [esp+14h+PCONTEXT]
.text:6B2BC846 018                 push    [esp+18h+SEH]
.text:6B2BC84A 01C                 push    [esp+1Ch+PEXCEPTION_RECORD]
.text:6B2BC84E 020                 call    CallNodeSEHFunc
.text:6B2BC853 00C                 pop     edi
.text:6B2BC854 008                 pop     esi
.text:6B2BC855 004                 pop     ebx
.text:6B2BC856 000                 retn    14h
.text:6B2BC856     _RtlpExecuteHandlerForUnwind@20 endp
.text:6B2BC856
.text:6B2BC859     ; // ---------------------------------------------------------------------------
.text:6B2BC85B     ; // =============== S U B R O U T I N E =======================================
.text:6B2BC85B
.text:6B2BC85B     ; // Attributes: bp-based frame
.text:6B2BC85B
.text:6B2BC85B     CallNodeSEHFunc proc near               ; // CODE XREF: RtlpExecuteHandlerForUnwind(x,x,x,x,x)+26p
.text:6B2BC85B
.text:6B2BC85B     PEXCEPTION_RECORD= dword ptr  8
.text:6B2BC85B     SEH             = dword ptr  0Ch
.text:6B2BC85B     PCONTEXT        = dword ptr  10h
.text:6B2BC85B     dwDispatcherContext= dword ptr  14h
.text:6B2BC85B     lpfnSEHHandler  = dword ptr  18h
.text:6B2BC85B
.text:6B2BC85B 000                 push    ebp
.text:6B2BC85C 004                 mov     ebp, esp
.text:6B2BC85E 004                 push    [ebp+SEH]
.text:6B2BC861 008                 push    edx
.text:6B2BC862 00C                 push    large dword ptr fs:0
.text:6B2BC869 010                 mov     large fs:0, esp
.text:6B2BC870 010                 push    [ebp+dwDispatcherContext]
.text:6B2BC873 014                 push    [ebp+PCONTEXT]
.text:6B2BC876 018                 push    [ebp+SEH]
.text:6B2BC879 01C                 push    [ebp+PEXCEPTION_RECORD]
.text:6B2BC87C 020                 mov     ecx, [ebp+lpfnSEHHandler]
.text:6B2BC87F 020                 call    ecx             ; // 调用该节点的SEH处理函数
.text:6B2BC881 008                 mov     esp, large fs:0 ; // 利用FS:[0]维持ESP平衡
.text:6B2BC888 008                 pop     large dword ptr fs:0 ; // 恢复原FS:[0]
.text:6B2BC88F 004                 mov     esp, ebp
.text:6B2BC891 004                 pop     ebp
.text:6B2BC892 000                 retn    14h
.text:6B2BC892     CallNodeSEHFunc endp
.text:6B2BC8BC     ; // =============== S U B R O U T I N E =======================================
.text:6B2BC8BC
.text:6B2BC8BC
.text:6B2BC8BC     TempSEHFunc     proc near               ; // DATA XREF: RtlpExecuteHandlerForUnwind(x,x,x,x,x)o
.text:6B2BC8BC
.text:6B2BC8BC     ExceptionRecord = dword ptr  4
.text:6B2BC8BC     lpSEH           = dword ptr  8
.text:6B2BC8BC     lpDispatcherContext= dword ptr  10h
.text:6B2BC8BC
.text:6B2BC8BC 000                 mov     ecx, [esp+ExceptionRecord]
.text:6B2BC8C0 000                 test    [ecx+EXCEPTION_RECORD.ExceptionFlags], 6 ; // 展开 or 退出展开
.text:6B2BC8C7 000                 mov     eax, ExceptionContinueSearch
.text:6B2BC8CC 000                 jz      short ret
.text:6B2BC8CE 000                 mov     ecx, [esp+lpSEH]
.text:6B2BC8D2 000                 mov     edx, [esp+lpDispatcherContext]
.text:6B2BC8D6 000                 mov     eax, [ecx+EXCEPTION_RECORD.ExceptionRecord]
.text:6B2BC8D9 000                 mov     [edx], eax
.text:6B2BC8DB 000                 mov     eax, ExceptionCollidedUnwind
.text:6B2BC8E0
.text:6B2BC8E0     ret:                                    ; // CODE XREF: TempSEHFunc+10j
.text:6B2BC8E0 000                 retn    10h
.text:6B2BC8E0     TempSEHFunc     endp

_RtlpUnlinkHandler@4:

.text:6B2BC8E3     ; // =============== S U B R O U T I N E =======================================
.text:6B2BC8E3
.text:6B2BC8E3
.text:6B2BC8E3     ; // __stdcall RtlpUnlinkHandler(x)
.text:6B2BC8E3     _RtlpUnlinkHandler@4 proc near          ; // CODE XREF: RtlUnwind+107p
.text:6B2BC8E3                                             ; // sub_6B358216+34p
.text:6B2BC8E3
.text:6B2BC8E3     SEH             = dword ptr  4
.text:6B2BC8E3
.text:6B2BC8E3 000                 mov     ecx, [esp+SEH]
.text:6B2BC8E7 000                 mov     ecx, [ecx]
.text:6B2BC8E9 000                 mov     large fs:0, ecx
.text:6B2BC8F0 000                 retn    4
.text:6B2BC8F0     _RtlpUnlinkHandler@4 endp

ZwContinue:

.text:6B2BA920     ; =============== S U B R O U T I N E =======================================
.text:6B2BA920
.text:6B2BA920
.text:6B2BA920                     public ZwContinue
.text:6B2BA920     ZwContinue      proc near               ; CODE XREF: KiUserApcDispatcher+35p
.text:6B2BA920                                             ; KiUserExceptionDispatcher+18p ...
.text:6B2BA920 000                 mov     eax, 42h        ; NtContinue(模拟中断后返回,可切上下文)
.text:6B2BA925 000                 call    large dword ptr fs:0C0h  ;快速陷入内核的又一种姿势
.text:6B2BA92C 000                 retn    8
.text:6B2BA92C     ZwContinue      endp
.text:6B2BA92C

剩余的函数还有RtlIsValidHandler没有分析,该函数会校验传入的SEH异常处理函数是否有效。由于最初SEH并不进行这种校验,使得SEH容易成为溢出攻击的跳板。因此,从Vista系统开始,微软启用了SafeSEH机制,我们将在后续文章中对该机制加以分析。

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值