线程的第 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,相当于下面的组合:
在 不改变 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 是不是同一个意思?