SEH stack 结构探索(1)--- 从 SEH 链的最底层(线程第1个SEH结构)说起

个人博客
线程的第 1 个 SEH 结构是什么时候构建的,我在线程启动例程找到答案:ntdll32!_RtlUserThreadStart() 里。

0:000:x86> uf ntdll32!_RtlUserThreadStart
ntdll32!_RtlUserThreadStart:
772e9cfa 8bff            mov     edi,edi
772e9cfc 55              push    ebp
772e9cfd 8bec            mov     ebp,esp
772e9cff 51              push    ecx
772e9d00 51              push    ecx
772e9d01 8d45f8          lea     eax,[ebp-8]
772e9d04 50              push    eax
772e9d05 e8d5ffffff      call    ntdll32!RtlInitializeExceptionChain (772e9cdf)
772e9d0a ff750c          push    dword ptr [ebp+0Ch]
772e9d0d ff7508          push    dword ptr [ebp+8]
772e9d10 e806000000      call    ntdll32!__RtlUserThreadStart (772e9d1b)
772e9d15 cc              int     3
772e9d16 90              nop
772e9d17 90              nop
772e9d18 90              nop
772e9d19 90              nop
772e9d1a 90              nop
772e9d1b 6a14            push    14h
772e9d1d 6890c32d77      push    offset ntdll32! ?? ::FNODOBFM::`string'+0xb5e (772dc390)
772e9d22 e8fd3fffff      call    ntdll32!_SEH_prolog4 (772ddd24)
772e9d27 8365fc00        and     dword ptr [ebp-4],0
772e9d2b a124423b77      mov     eax,dword ptr [ntdll32!Kernel32ThreadInitThunkFunction (773b4224)]
772e9d30 ff750c          push    dword ptr [ebp+0Ch]
772e9d33 85c0            test    eax,eax
772e9d35 0f84d2d20400    je      ntdll32!__RtlUserThreadStart+0x25 (7733700d)

当主线程被创建后,系统会跳转到 ndtll32!_RtlUserThreadStart() 开始执行,它最终会调用用户的入口函数。在 RtlUserThreadStart() 里会调用 SEH_prolog4() 进行构建最初的 SEH 结构。

好了,现在关键是看 ntdll32!_SEH_prolog4() 做了些什么:

ntdll32!_SEH_prolog4:
7774dd24 68dd037977      push    offset ntdll32!_except_handler4 (777903dd)
7774dd29 64ff3500000000  push    dword ptr fs:[0]
7774dd30 8b442410        mov     eax,dword ptr [esp+10h]
7774dd34 896c2410        mov     dword ptr [esp+10h],ebp
7774dd38 8d6c2410        lea     ebp,[esp+10h]
7774dd3c 2be0            sub     esp,eax
7774dd3e 53              push    ebx
7774dd3f 56              push    esi
7774dd40 57              push    edi
7774dd41 a188208277      mov     eax,dword ptr [ntdll32!__security_cookie (77822088)]
7774dd46 3145fc          xor     dword ptr [ebp-4],eax
7774dd49 33c5            xor     eax,ebp
7774dd4b 50              push    eax
7774dd4c 8965e8          mov     dword ptr [ebp-18h],esp
7774dd4f ff75f8          push    dword ptr [ebp-8]
7774dd52 8b45fc          mov     eax,dword ptr [ebp-4]
7774dd55 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh
7774dd5c 8945f8          mov     dword ptr [ebp-8],eax
7774dd5f 8d45f0          lea     eax,[ebp-10h]
7774dd62 64a300000000    mov     dword ptr fs:[00000000h],eax
7774dd68 c3              ret

ntdll32!_SEH_prolog4() 的工作就是构建一个最底层的 _EXCEPTION_REGISTRATION_RECORD,即构造一个最底层的 SEH 结点

我们要看的是它如何构建 _EXCEPTION_REGISTRATION_RECORD 结构,我们最好准备纸笔记录下它的 stack 变化情况

 

1. ntdll32!_SEH_prolog4() 构造的 stack 结构

刚进入 ntdll32!_SEH_prolog4() 时的 stack 是下面的图

上面的两个返回地址一个是返回到 ntdll32!_RtlUserThreadStart() 一个是返回到 ntdll32!__RtlUserThreadStart(),绿色标注的地方实际是由ntdll32!RtlInitializeExceptionChain() 构建一个最终的 _EXCEPTION_REGISTRATION_RECORD

ntdll32!_SEH_prolog4:
7774dd24 68dd037977      push    offset ntdll32!_except_handler4 (777903dd)
7774dd29 64ff3500000000  push    dword ptr fs:[0]
7774dd30 8b442410        mov     eax,dword ptr [esp+10h]
7774dd34 896c2410        mov     dword ptr [esp+10h],ebp    ;前一个 ebp 值
7774dd38 8d6c2410        lea     ebp,[esp+10h]    ;将 ebp 移至 esp+10h 处

底层的 exception handler 是 ntdll32!_except_handler() 接着压入的 FS:[0] 值是 FFFFFFFFh,这个值代表的前一个 EXCEPTION_REGISTRATION_RECORD 结构,它是链的终点标志。

接下来的代码是手工设置 stack frame,相当于下面的组合:

push ebp
mov ebp, esp

 不改变 esp 的情况下,使用人工设置 stack frame 是有目的的,最终这块区域将变成一个 EXCEPTION_REGISTRATION_RECORD 结构,将 ebp 移至 esp + 10 处,也就是ntdll32!_SEH_prolog4() 的第 1 个参数(14h 处

这时的 stack 图如下:

接下着看看下面几行代码:

7774dd41 a188208277      mov     eax,dword ptr [ntdll32!__security_cookie (77822088)]
7774dd46 3145fc          xor     dword ptr [ebp-4],eax
7774dd49 33c5            xor     eax,ebp
7774dd4b 50              push    eax

ntdll32!__security_cookie 值与 ebp 异或后值入栈以备以后用来验证 frame 是否被破坏,而 772dc390 所处的位置是 EXCEPTION_REGISTRATION_RECORD 结构的成员 FilterFrame(参见在except.inc 里定义的 EXCEPTION_REGISTRATION_RECORD 结构),security_cookie 与 772dc390 异或后的值这里暂时放下。

7774dd4c 8965e8          mov     dword ptr [ebp-18h],esp
7774dd4f ff75f8          push    dword ptr [ebp-8]
7774dd52 8b45fc          mov     eax,dword ptr [ebp-4]
7774dd55 c745fcfeffffff  mov     dword ptr [ebp-4],0FFFFFFFEh     ; 设置 FilterFrame 值
7774dd5c 8945f8          mov     dword ptr [ebp-8],eax     ; 设置 ExceptionFilter 值
7774dd5f 8d45f0          lea     eax,[ebp-10h]
7774dd62 64a300000000    mov     dword ptr fs:[00000000h],eax
7774dd68 c3              ret

最后这一段是收尾工作:

  • 将返回到 ntdll32!__RtlUserThreadStart() 的地址移到 stack 顶,修正函数返回值确保 ntdll32!_SEH_prolog4() 能正确返回
  • 设置 esp 值在 EXCEPTION_REGISTRATION_RECORD 结构里
  • 设置 FilterFrame 值和 ExceptionFilter 值
  • 最后设置 FS:[0] 指向

最终的 stack 图如下:

 

2. 扩展的 EXCEPTION_REGISTRATION_RECORD 结构

上图的蓝色部分就是 Matt Pietrek 大侠在《A Crash Course on the Depths of Win32? Structured Exception Handling》 一文中说的:VC++ 生成的扩展 EXCEPTION_REGISTRATION_RECORD 结构</a>,它象下面:

 EBP-00 _ebp
 EBP-04 trylevel
 EBP-08 scopetable pointer
 EBP-0C handler function address
 EBP-10 previous EXCEPTION_REGISTRATION
 EBP-14 GetExceptionPointers
 EBP-18 Standard ESP in frame

VC++ 保留了一个 DWORD 来保存 prolog 代码执行完毕之后的堆栈指针(ESP)的值。在我的 VC++ 10.0 版本的 CRT 头文件 except.inc 里找到的定义是:

; exception registration record structure.

__EXCEPTIONREGISTRATIONRECORD struc
        prev_structure          dd      ?
        ExceptionHandler        dd      ?
        ExceptionFilter         dd      ?
        FilterFrame             dd      ?
        PExceptionInfoPtrs      dd      ?
__EXCEPTIONREGISTRATIONRECORD ends

与 Matt Pietrek 所说版本:

 struct _EXCEPTION_REGISTRATION{
      struct _EXCEPTION_REGISTRATION *prev;
      void (*handler)(PEXCEPTION_RECORD,
                      PEXCEPTION_REGISTRATION,
                      PCONTEXT,
                      PEXCEPTION_RECORD);
      struct scopetable_entry *scopetable;
      int trylevel;
      int _ebp;
      PEXCEPTION_POINTERS xpointers;
 };

有些差异,因此,这里让人迷惑的是:[ebp - 8] 里的值到底是什么? Matt Pietrek 所说的 scopetable 与 ExceptionFilter 是不是同一个意思?

SEH SEH链 stack 结构

分享到:
评论加载中,请稍后...
创APP如搭积木 - 创意无限,梦想即时!
回到顶部