SEH中的prolog和epilog

最初发布在QQ空间,见:SEH中的prolog和epilog,内有贴图。

使用SEH的代码都需要构建栈桢,支持exception处理的栈桢,而重复的代码就应当提炼成函数,微软自然不会例外。为此系统提供了prolog和epilog系列,类似的函数有不少版本,但大同小异。这次不用调试器,分析下__SEH_prolog4_GS和__SEH_epilog4_GS 2个函数,看看它们如何工作支持异常的栈桢结构。

先看看prolog,这是内核函数KiDispatchException调用__SEH_prolog4_GS的代码,

000 push    0F8h
004 push    offset off_8488DCE0
008 call    __SEH_prolog4_GS

调用函数前传递2个参数,一个是需要保留的栈的大小,一个是指针。
再看看函数代码本身,详细的注释在代码后面,

   ; two instrucitons before call it.
    ;
    ; take KiDispatchException as example,
    ;
    ; 000 push    0F8h ; it's used as a stack size
    ; 004 push    offset off_8488DCE0 ; it's pointer to scope table entry.
    __SEH_prolog4_GS proc near
    arg_4= dword ptr  8
000 push    offset __except_handler4
004 push    large dword ptr fs:0
008 mov     eax, [esp+8+arg_4] ; so now there are 5 values in stack.
                            ;
                            ; f8
                            ; 8488dce0
                            ; return address of call
                            ; _except_handler4
                            ; old fs:0
                            ; ; follwing is additional information
                            ; to be pointer to EXCEPTION_POINTERS
                            ; to be esp value
                            ; to be cookie value
008 mov     [esp+8+arg_4], ebp ; push current ebp value into place
                            ;  where 0f8 is here.
008 lea     ebp, [esp+8+arg_4] ; update ebp to current frame pointer
008 sub     esp, eax        ; reserve stack
008 push    ebx
00C push    esi
010 push    edi
014 mov     eax, ___security_cookie
014 xor     [ebp-4], eax    ; encrypt pointer to scope table entry
014 xor     eax, ebp
014 mov     [ebp-1Ch], eax  ; store cookie value
014 push    eax
018 mov     [ebp-18h], esp  ; store current esp
; middle part
018 push    dword ptr [ebp-8] ; push "return address" of current call
                            ; for next ret instruction
01C mov     eax, [ebp-4]    ; mov [ebp-4] to [ebp-8]
01C mov     dword ptr [ebp-4], 0FFFFFFFEh ; initialize try level
01C mov     [ebp-8], eax    ; so far, the stack is following:
                            ;
                            ; ebp
                            ; -1
                            ; 8488DCE0 xor ___security_cookie
                            ; _except_handler4
                            ; old fs:[0]
                            ; to be pointer to EXCEPTION_POINTERS
                            ; current esp value
                            ; cookie value (___security_cookie xor ebp)
                            ; ...
                            ; ebx
                            ; esi
                            ; edi
                            ; cookie value
                            ; return address to parent function
01C lea     eax, [ebp-10h]
01C mov     large fs:0, eax ; update fs:[0] to current frame
01C retn                    ; return to parent function.

由于注释比较详细,就不多说什么了,补充几张图。
函数开始执行2条push指令后的栈,

执行到中间处的栈如下,注意数字顺序。
SEH中的prolog和epilog_第1张图片
执行到最后retn之前的栈,此时(ebp-10)处已经构建好了异常栈桢结构,(ebp-18)(ebp-1c)处也赋了相应的值。SEH中的prolog和epilog_第2张图片
接下来分析epilog,先看看KiDispatchException如何调用它的,

            ; why is there 118?
11C lea     esp, [ebp-118h] ; sizeof exception_frame + F8(includes additional information) + sizeof( ebx + esi + edi + cookie )
                            ; ebp - (10+F8+10)
000 call    __SEH_epilog4_GS
000 retn    14h

将栈指针修正到合适的位置,实际上就是__SEH_prolog4_GS函数返回后的栈的位置。然后调用函数__SEH_epilog4_GS,注意call指令会将返回地址压栈。
再看看函数的实现部分,

    __SEH_epilog4_GS proc near
000 mov     ecx, [ebp-1Ch]  ; cookie value (___security_cookie xor ebp)
000 xor     ecx, ebp
000 call    @__security_check_cookie@4 ;
                            ; cmp     ecx, ___security_cookie
                            ; jnz     ___report_gsfailure
                            ; retn
000 jmp     __SEH_epilog4
    __SEH_epilog4_GS endp

检查cookie有没有被破坏,如果没有问题就跳转到__SEH_epilog4。

    __SEH_epilog4 proc near
000 mov     ecx, [ebp-10h]  ; restore old fs:[0]
000 mov     large fs:0, ecx
000 pop     ecx             ; return address of parent function
-04 pop     edi             ; another cookie value besides [ebp-1c]
-08 pop     edi
-0C pop     esi
-10 pop     ebx
-14 mov     esp, ebp        ; restore esp and ebp
000 pop     ebp
-04 push    ecx
000 retn
    __SEH_epilog4 endp

这个函数比prolog简单的多了,恢复fs:[0],恢复寄存器,返回到调用__SEH_epilog4_GS的地方。
对这2个函数的分析最终演变成了对栈中数据结构的理解,IDA中的观察到的数据结构如下,成员后面加了注释:

EXCEPTION_POINTERS struc ; (sizeof=0x8, standard type)
ExceptionRecord dd ? ; pointer to ExceptionRecord
ContextRecord dd ? ; pointer to ContextRecord
EXCEPTION_POINTERS ends
CPPEH_RECORD struc ; 
old_esp dd ? ; esp position
exc_ptr dd ? ; pointer to EXCEPTION_POINTERS
prev_er dd ? ; pointer to old fs:[0]
handler dd ? ; exception handler
msEH_ptr dd ? ; pointer to scope table entry (might be encrytped)
disabled dd ? ; try level
CPPEH_RECORD ends

注意图中栈数据的顺序是从高到低,而上面结构中数据成员是从低到高,正好相反。另外,事实上CPPEH_RECORD还差了2个值,一个在old_esp上面,用来放cookie值。另一个在disabled下面,用来存放前个ebp值(和前面的图对比)。在异常处理函数中,可以访问这个数据结构,得到自己需要的信息。

这也让我想起了<人月神话>中的一句话。"给我看你的流程图,而藏起你的表,我仍然莫名其妙,给我看你的表藏起流程图,它们是如此的明显。 "这里的“表”指的就是数据结构,它最终决定了代码的实现。

你可能感兴趣的:(数据结构,c,exception,Security,table,Pointers)