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,相当于下面的组合:
在 不改变 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 结构,它象下面:
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 是不是同一个意思?
上一页 目录 下一页
版权 mik 所有,转载请注明出处!