SEH stack 结构探索(2)--- 用户程序构造的 SEH stack 结构
经过上一篇对 prolog 的分析,我们对 SEH 的 stack 结构有大概了解,现在我们回过头来看看在第 2 篇《SEH 机制探讨2 --- 构建 SEH 链》的示例,
但是 __except() 部分有了重大的变化:
int _tmain(int argc, _TCHAR* argv[]) { DWORD dwI; EXCEPTION_RECORD ExceptionRecord; CONTEXT Context; __try { dwI = 0; } __except(ExceptionRecord = *(GetExceptionInformation())->ExceptionRecord, Context = *(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER) { return 0; } return 0; } |
1. SEH stack 的构建
这是用户程序的代码,这个 SEH stack 是在函数头,进入 __try{ } 块之前就已经设好:
int _tmain(int argc, _TCHAR* argv[]) { 011E33D0 55 push ebp 011E33D1 8B EC mov ebp,esp 011E33D3 6A FE push 0FFFFFFFEh 011E33D5 68 C8 6B 1E 01 push offset ___rtc_tzz+1A8h (11E6BC8h) 011E33DA 68 6E 10 1E 01 push offset @ILT+105(__except_handler4) (11E106Eh) 011E33DF 64 A1 00 00 00 00 mov eax,dword ptr fs:[00000000h] 011E33E5 50 push eax 011E33E6 81 C4 F0 FB FF FF add esp,0FFFFFBF0h 011E33EC 53 push ebx 011E33ED 56 push esi 011E33EE 57 push edi 011E33EF 8D BD E0 FB FF FF lea edi,[ebp-420h] 011E33F5 B9 02 01 00 00 mov ecx,102h 011E33FA B8 CC CC CC CC mov eax,0CCCCCCCCh 011E33FF F3 AB rep stos dword ptr es:[edi] 011E3401 A1 14 70 1E 01 mov eax,dword ptr [___security_cookie (11E7014h)] 011E3406 31 45 F8 xor dword ptr [ebp-8],eax 011E3409 33 C5 xor eax,ebp 011E340B 89 45 E4 mov dword ptr [ebp-1Ch],eax 011E340E 50 push eax 011E340F 8D 45 F0 lea eax,[ebp-10h] 011E3412 64 A3 00 00 00 00 mov dword ptr fs:[00000000h],eax 011E3418 89 65 E8 mov dword ptr [ebp-18h],esp DWORD dwI; EXCEPTION_RECORD ExceptionRecord; CONTEXT Context; __try 011E341B C7 45 FC 00 00 00 00 mov dword ptr [ebp-4],0 |
上面的代码是用户程序里的进入 __try{}前的代码,它和 ntdll32!_SEH_prolog4() 构造的 stack 结构是一致的,如下图所示:
这里同样是用 __security_cookie 值与 [ebp-8] 异或,并且与 ebp 异或后保存起来备用。
这是到目前为止,我们对 VC++ 对 SEH4 实现的了解。当我们逐步深入了解 SEH 机制后,这个图或许有进一步的修改。
2. EXCEPTION_POINTERS 指针
到目前为止,我们对 [ebp-14] 里的值还不解。
这里我可以明确地告诉大家:实际上 [ebp-14] 里的值是一个指向 EXCEPTION_POINTERS 结构的指针值。这个指针与 __except() 块是紧密相关的。
现在我不知道该从何说起,是从 __except() 说起呢?,还是先看看 EXCEPTION_POINTERS 结构是什么? 好吧,还是先看看 EXCEPTION_POINTERS 是什么吧。
2.1 EXCEPTION_POINTERS 结构
EXCEPTION_POINTERS 结构只有两个成员,这两个成员是指针:ExceptionRecord 和 ContextRecord
这个结构在 WinNT.h 中定义为:
// // Typedef for pointer returned by exception_info() // typedef struct _EXCEPTION_POINTERS { PEXCEPTION_RECORD ExceptionRecord; PCONTEXT ContextRecord; } EXCEPTION_POINTERS, *PEXCEPTION_POINTERS; |
这两个指针指向 EXCEPTION_RECORD 结构和 CONTEXT 结构
2.2 EXCEPTION_RECORD 结构
EXCEPTION_RECORD 是用来记录线程发生异常时的记录信息,在 WinNT.h 定义为:
typedef struct _EXCEPTION_RECORD { DWORD ExceptionCode; DWORD ExceptionFlags; struct _EXCEPTION_RECORD *ExceptionRecord; PVOID ExceptionAddress; DWORD NumberParameters; ULONG_PTR ExceptionInformation[EXCEPTION_MAXIMUM_PARAMETERS]; } EXCEPTION_RECORD; |
这些信息重要的有:异常码,标志,地址等。异常码在 WinNT.h 中定义了一部分:
#define STATUS_WAIT_0 ((DWORD )0x00000000L) #define STATUS_ABANDONED_WAIT_0 ((DWORD )0x00000080L) #define STATUS_USER_APC ((DWORD )0x000000C0L) #define STATUS_TIMEOUT ((DWORD )0x00000102L) #define STATUS_PENDING ((DWORD )0x00000103L) #define DBG_EXCEPTION_HANDLED ((DWORD )0x00010001L) #define DBG_CONTINUE ((DWORD )0x00010002L) #define STATUS_SEGMENT_NOTIFICATION ((DWORD )0x40000005L) #define DBG_TERMINATE_THREAD ((DWORD )0x40010003L) #define DBG_TERMINATE_PROCESS ((DWORD )0x40010004L) #define DBG_CONTROL_C ((DWORD )0x40010005L) #define DBG_PRINTEXCEPTION_C ((DWORD )0x40010006L) #define DBG_RIPEXCEPTION ((DWORD )0x40010007L) #define DBG_CONTROL_BREAK ((DWORD )0x40010008L) #define DBG_COMMAND_EXCEPTION ((DWORD )0x40010009L) #define STATUS_GUARD_PAGE_VIOLATION ((DWORD )0x80000001L) #define STATUS_DATATYPE_MISALIGNMENT ((DWORD )0x80000002L) #define STATUS_BREAKPOINT ((DWORD )0x80000003L) #define STATUS_SINGLE_STEP ((DWORD )0x80000004L) #define STATUS_LONGJUMP ((DWORD )0x80000026L) #define STATUS_UNWIND_CONSOLIDATE ((DWORD )0x80000029L) #define DBG_EXCEPTION_NOT_HANDLED ((DWORD )0x80010001L) #define STATUS_ACCESS_VIOLATION ((DWORD )0xC0000005L) #define STATUS_IN_PAGE_ERROR ((DWORD )0xC0000006L) #define STATUS_INVALID_HANDLE ((DWORD )0xC0000008L) #define STATUS_INVALID_PARAMETER ((DWORD )0xC000000DL) #define STATUS_NO_MEMORY ((DWORD )0xC0000017L) #define STATUS_ILLEGAL_INSTRUCTION ((DWORD )0xC000001DL) #define STATUS_NONCONTINUABLE_EXCEPTION ((DWORD )0xC0000025L) #define STATUS_INVALID_DISPOSITION ((DWORD )0xC0000026L) #define STATUS_ARRAY_BOUNDS_EXCEEDED ((DWORD )0xC000008CL) #define STATUS_FLOAT_DENORMAL_OPERAND ((DWORD )0xC000008DL) #define STATUS_FLOAT_DIVIDE_BY_ZERO ((DWORD )0xC000008EL) #define STATUS_FLOAT_INEXACT_RESULT ((DWORD )0xC000008FL) #define STATUS_FLOAT_INVALID_OPERATION ((DWORD )0xC0000090L) #define STATUS_FLOAT_OVERFLOW ((DWORD )0xC0000091L) #define STATUS_FLOAT_STACK_CHECK ((DWORD )0xC0000092L) #define STATUS_FLOAT_UNDERFLOW ((DWORD )0xC0000093L) #define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L) #define STATUS_INTEGER_OVERFLOW ((DWORD )0xC0000095L) #define STATUS_PRIVILEGED_INSTRUCTION ((DWORD )0xC0000096L) #define STATUS_STACK_OVERFLOW ((DWORD )0xC00000FDL) #define STATUS_DLL_NOT_FOUND ((DWORD )0xC0000135L) #define STATUS_ORDINAL_NOT_FOUND ((DWORD )0xC0000138L) #define STATUS_ENTRYPOINT_NOT_FOUND ((DWORD )0xC0000139L) #define STATUS_CONTROL_C_EXIT ((DWORD )0xC000013AL) #define STATUS_DLL_INIT_FAILED ((DWORD )0xC0000142L) #define STATUS_FLOAT_MULTIPLE_FAULTS ((DWORD )0xC00002B4L) #define STATUS_FLOAT_MULTIPLE_TRAPS ((DWORD )0xC00002B5L) #define STATUS_REG_NAT_CONSUMPTION ((DWORD )0xC00002C9L) #define STATUS_STACK_BUFFER_OVERRUN ((DWORD )0xC0000409L) #define STATUS_INVALID_CRUNTIME_PARAMETER ((DWORD )0xC0000417L) #define STATUS_ASSERTION_FAILURE ((DWORD )0xC0000420L) #if defined(STATUS_SUCCESS) || (_WIN32_WINNT > 0x0500) || (_WIN32_FUSION >= 0x0100) #define STATUS_SXS_EARLY_DEACTIVATION ((DWORD )0xC015000FL) #define STATUS_SXS_INVALID_DEACTIVATION ((DWORD )0xC0150010L) |
上现在非常常见的:ACCESS_VIOLATION 访问违例异常,它的值是 0xC0000005
2.3 CONTEXT 结构
这个结构很简单但较长,我还是打算在这里贴出来,好有个直观的认识,它在 WinNT.h 定义为:
typedef struct _CONTEXT { // // The flags values within this flag control the contents of // a CONTEXT record. // // If the context record is used as an input parameter, then // for each portion of the context record controlled by a flag // whose value is set, it is assumed that that portion of the // context record contains valid context. If the context record // is being used to modify a threads context, then only that // portion of the threads context will be modified. // // If the context record is used as an IN OUT parameter to capture // the context of a thread, then only those portions of the thread's // context corresponding to set flags will be returned. // // The context record is never used as an OUT only parameter. // DWORD ContextFlags; // // This section is specified/returned if CONTEXT_DEBUG_REGISTERS is // set in ContextFlags. Note that CONTEXT_DEBUG_REGISTERS is NOT // included in CONTEXT_FULL. // DWORD Dr0; DWORD Dr1; DWORD Dr2; DWORD Dr3; DWORD Dr6; DWORD Dr7; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_FLOATING_POINT. // FLOATING_SAVE_AREA FloatSave; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_SEGMENTS. // DWORD SegGs; DWORD SegFs; DWORD SegEs; DWORD SegDs; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_INTEGER. // DWORD Edi; DWORD Esi; DWORD Ebx; DWORD Edx; DWORD Ecx; DWORD Eax; // // This section is specified/returned if the // ContextFlags word contians the flag CONTEXT_CONTROL. // DWORD Ebp; DWORD Eip; DWORD SegCs; // MUST BE SANITIZED DWORD EFlags; // MUST BE SANITIZED DWORD Esp; DWORD SegSs; // // This section is specified/returned if the ContextFlags word // contains the flag CONTEXT_EXTENDED_REGISTERS. // The format and contexts are processor specific // BYTE ExtendedRegisters[MAXIMUM_SUPPORTED_EXTENSION]; } CONTEXT; typedef CONTEXT *PCONTEXT; |
用来保存当线程发生异常时的 CPU 上下文环境。
2.4 GetExceptionInfomation 内置函数
上面介绍的几个结构是为了哄托出这个 GetExceptionInformation() 函数,这个函数是 VC++ 内置的函数,在 MSDN 上说它是一个宏:http://msdn.microsoft.com/en-us/library/ms679357(VS.85).aspx
LPEXCEPTION_POINTERS GetExceptionInformation(void); |
使用它时,编译器会嵌入几行代码而不是生成函数调用语句。 它返回一个 EXCEPTION_POINTERS 结构的指针值,这个 EXCEPTION_POINTERS 结构就是前面所介绍的,它的两个成员分别指向 EXCEPTION_RECORD 结构和 CONTEXT 结构
GetExceptionInformation() 只能用在 __except() 块的 括号 内,否则编译器会产生错误。
下面回到前面的示例代码中的 __except() 块:
__except(ExceptionRecord = *(GetExceptionInformation())->ExceptionRecord, Context = *(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER) { return 0; } |
__except() 括号里使用了 GetExceptionInformation(),可以看出由于 GetExceptionInformation() 返回 EXCEPTION_POINTERS 指针,因此:
- 用这个指针值来获取 ExceptionRecord 和 ContextRecord 这两个指针值
- 然后通过这两个指针值来获取 EXCEPTION_RECORD 结构和 CONTEXT 结构的内容
3. 揭开 [ebp-14] 的面纱
到现在,我们应该要揭开 [ebp-14] 的神秘面纱了,前面说过这个值就是 EXCEPTION_POINTERS 的指针值。
下面看看是不是这样?这种情况下,只有看反汇编结果才能确定:
__except(ExceptionRecord = *(GetExceptionInformation())->ExceptionRecord, Context = *(GetExceptionInformation())->ContextRecord, EXCEPTION_EXECUTE_HANDLER) 011E3432 8B 45 EC mov eax,dword ptr [ebp-14h] // EXCEPTION_POINTERS 指针 011E3435 8B 30 mov esi,dword ptr [eax] // 获取 ExceptionRecord 值 011E3437 B9 14 00 00 00 mov ecx,14h 011E343C 8D 7D 84 lea edi,[ebp-7Ch] 011E343F F3 A5 rep movs dword ptr es:[edi],dword ptr [esi] 011E3441 8B 4D EC mov ecx,dword ptr [ebp-14h] // EXCEPTION_POINTERS 指针 011E3444 8B 71 04 mov esi,dword ptr [ecx+4] // 获取 ContextRecord 值 011E3447 B9 B3 00 00 00 mov ecx,0B3h 011E344C 8D BD B0 FC FF FF lea edi,[ebp-350h] 011E3452 F3 A5 rep movs dword ptr es:[edi],dword ptr [esi] 011E3454 B8 01 00 00 00 mov eax,1 $LN7: 011E3459 C3 ret $LN6: 011E345A 8B 65 E8 mov esp,dword ptr [ebp-18h] { return 0; |
代码中从 [ebp-14] 处得到 EXCEPTION_POINTERS 结构指针值,在第 1 个成员中得到 ExceptionRecord 值,它是指向 EXCEPTION_RECORD 结构的指针。然后从第 2 个成员里得到 ContextRecord 值,它是指向 CONTEXT 结构的指针值。
然后,分别将这些复制到用户的 stack 中,对应 C 语句的赋值语句。对 EXCEPTION_RECORD 结构复制 14h 字节。对 CONTEXT 结构复制 B3h 字节。
于是我们 SEH 的 stack 结构有了进一步的认识,如图
4. GetExceptionCode 内置函数
与 GetExceptionInformation() 一样 GetExceptionCode() 也是内置函数,用来获得线程刚刚发生的异常码。
GetExceptionCode() 生成的代码类似:
mov eax, dword ptr [ebp-14h] // get EXCEPTION_POINTERS mov eax, dword ptr [eax] // get ExceptionRecord mov ecx, dword ptr [eax] // get ExceptionCode |
GetExceptionCode() 只能用在 __except(){} 块的括号或者异常处理程序的代码({} 块内)
上一页 目录 下一页
版权 mik 所有,转载请注明出处!