• 标 题:SEH IN ASM 研究(二)---提高篇:关于异常处理的嵌套和堆栈展开 (15千字)
  • 作 者:hume
  • 时 间:2002-1-28 8:26:20
  • 链 接:http://bbs.pediy.com

SEH IN ASM 研究(二)
                                        ---提高篇
                                      By Hume[AfO]/冷雨飘心

                        part 4  关于异常处理的嵌套和堆栈展开

    在实际程序设计过程中,不可能只有一个异常处理例程,这就产生了异常处理程序嵌套的问题,可能很多
处理例程分别监视若干子程序并处理其中某种异常,另外一个监视所有子程序可能产生的共性异常,这作
起来实际很容易,也方便调试.你只要依次建立异常处理框架就可以了.
    关于VC++异常处理可以嵌套很多人可能比较熟悉,用起来更容易不过实现比这里也就复杂得多,在VC++
中一个程序所有异常只指向一个相同的处理句例程,然后在这个处理例程里再实现对各个子异常处理例程的
调用,他的大致方法是建立一个子异常处理例程入口的数组表,然后根据指针来调用子处理例程,过程比较烦
琐,原来打算大致写一点,现在发现自己对C/C++了解实在太少,各位有兴趣还是自己
参考MSDN Matt Pietrek 1996年写的一篇文章<Crash Course on the Depths of Win32? Stru >
ctured Exception Handling>>,里面有非常详细的说明,对于系统的实现细节也有所讨论,不过相信很多
人都没有兴趣.hmmm...:)实际上Kernel的异常处理过程和VC++的很相似.

    有异常嵌套就涉及到异常展开的问题,也许你注意到了如果按照我前面的例子包括final都不处理异常的话,
最后系统在终结程序之前会来一次展开,在试验之后发现,展开不会调用final只是对per_thread例程展开
(right?).什么是堆栈展开?为什么要进行堆栈展开?如何进行堆栈展开?
   
    我曾经为堆栈展开迷惑过,原因是各种资料的描述很不一致,Matt Pietrek说展开后前面的ERR结构被释放,
并且好像链后面如果决定处理必须展开,很多C/C++讲述异常处理的书也如斯说这使人很迷惑,我们再来看看Jeremy
Gordon的描述,堆栈展开是处理异常的例程自愿进行的.呵呵,究竟事实如何?
   
    在迷惑好久之后我终于找到了答案:Matt Pietrek讲的没有错,那是VC++以及系统kernel的处理方法,Jeremy
Gordon说的也是正确的,那是我门asm Fans的自由!

    好了现在来说堆栈展开,堆栈展开是异常处理例程在决定处理某个异常的时候给前面不处理这个异常的处理
例程的一个清洗的机会,前面拒绝处理这个异常的例程可以释放必要的句柄对象或者释放堆栈或者干点别的工作...
那完全是你的自由,叫stack unwind似乎有点牵强.堆栈展开有一个重要的标志就是
EXCEPTION_RECORD.ExceptionFlag为2,表示正在展开,你可以进行相应的处理工作,但实际上经常用的是6这是
因为还有一个UNWIND_EXIT什么的,具体含义我也没有搞明白,不过对我们的工作好像没有什么影响.

  注意在自己的异常处理例程中,unwind不是自动的,必须你自己自觉地引发,如果所有例程都不处理系统最后的
展开是注定的,当然如果没有必要你也可以不展开.
  win32提供了一个api RtlUnwind来引发展开,如果你想展开一下,就调用这个api吧,少候讲述自己代码如何展开

RtlUnwind调用描述如下:
        PUSH Return value        ;返回值,一般不用
        PUSH pExceptionRecord    ;指向EXCEPTION_RECORD的指针
        PUSH OFFSET CodeLabel    ;展开后从哪里执行
        PUSH LastStackFrame      ;展开到哪个处理例程终止返回,通常是处理异常的Err结构
        CALL RtlUnwind
调用这个api之前要注意保护ebx,esi和edi,否则...嘿嘿

MASM格式如下:       
        invoke RtlUnwind,pFrame,OFFSET return_code_Address,pExceptionRecord,Return_value

这样在展开的时候,就以pExceptionRecord.flag=2 依次调用前面的异常处理例程,到决定异常的处理例程停止,
Jeremy Gordon手动展开代码和我下面的例子有所不同.他描述最后决定处理异常的ERR结构的prev成员为-1,好像
我的结果和他的有所差异,因此采用了另外的方法,具体看下面的例子.
   
  最后一点要注意在嵌套异常处理程序的时候要注意保存寄存器,否则你经常会得到系统异常代码为C00000027h
的异常调用,然后就是被终结.

  一下给出一点垃圾代码演示可能有助于理解,注意link的时候要加入 /section:.text,RWE 否则例子里面的
代码段不能写,SMC功能会产生异常以致整个程序不能进行.

注意:2K/XP下非法指令异常的代码不一致,另外用下面的方法SMC代码段也不可以!不知如何解决?
只用于9X,为了在2k/Xp下也能运行我加了点代码,有兴趣看看,另外帮我解决一下2K/Xp下SMC的问题?thx!

    下面例子很烂,不过MASM格式写起来容易一点,也便于理解.
;-----------------------------------------
;Ex4,演示堆栈展开和异常嵌套处理 by Hume,2002
;humewen@21cn.com
;hume.longcity.net
;-----------------------------------------
.586
.model flat, stdcall
option casemap :none  ; case sensitive
include hd.h
include mac.h

;;--------------
per_xHandler1        proto C :DWORD,:DWORD,:DWORD,:DWORD
per_xHandler2        proto C :DWORD,:DWORD,:DWORD,:DWORD
per_xHandler3        proto C :DWORD,:DWORD,:DWORD,:DWORD
;-----------------------------------------

.data
sztit  db "except Mess,by hume[AfO]",0
count  dd 0,0
Expt1_frm  dd 0                    ;ERR结构指针,用于堆栈展开手动代码
Expt2_frm  dd 0
Expt3_frm  dd 0
       
;;-----------------------------------------
    .CODE
_Start:
        assume  fs:nothing
        push    offset per_xHandler3
        push    fs:[0]
        mov    fs:[0],esp
        mov    Expt3_frm,esp

        push    offset per_xHandler2
        push    fs:[0]
        mov    fs:[0],esp
        mov    Expt2_frm,esp

        push    offset per_xHandler1
        push    fs:[0]
        mov    fs:[0],esp
        mov    Expt1_frm,esp
        ;--------------------------
        ;install xhnadler
        ;-----------------------------------------
       
        xor    ebx,ebx
        mov    eax,200
        cdq
        div    ebx                  ;除法错误

        invoke    MessageBox,0,ddd("Good,divide overflow was solved!"),addr sztit,40h

        sub    eax,eax
        mov    [eax],ebx            ;内存写错误

succ:
        invoke    MessageBox,0,ddd("Good,memory write violation solved!"),addr sztit,40h
       
        db      0F0h,0Fh,0C7h,0C8h  ;什么cmpchg8b指令的非法形式?我从来没有成功过!!
                                    ;演示程序中使用seh实现SMC技术,加密??...
        invoke    MessageBox,0,ddd("illeagal instruction was solved!"),addr sztit,20h
        ;--------------------------
        ;uninstall xhnadler
        ;-----------------------------------------

        pop    fs:[0]           
        add    esp,4
        pop    fs:[0]           
        add    esp,4
      ;或者add esp,10h
     
        pop    fs:[0]           
        add    esp,4

    invoke    ExitProcess,0
;-----------------------------------------       
;异常处理句柄1,处理除法异常错误
per_xHandler1 PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
        pushad
        MOV    ESI,pExcept
ASSUME  ESI:PTR EXCEPTION_RECORD
        TEST    [ESI].ExceptionFlags,1
        JNZ    @cantdo1
        TEST    [ESI].ExceptionFlags,6
        JNZ    @unwind1
        CMP    [ESI].ExceptionCode,0C0000094h
        JNZ    @cantdo1
        MOV    EDI,pContext

ASSUME  EDI:PTR CONTEXT
        m2m    [edi].regEbx,20            ;将ebx置20,修复除法错误,继续执行
        popad
    MOV  EAX, ExceptionContinueExecution
        RET

@unwind1:
        invoke    MessageBox,0,CTEXT("state: unwinding in xhandler1..."),addr sztit,0
@cantdo1:
        popad
        MOV    EAX,ExceptionContinueSearch
    RET
per_xHandler1 ENDP
;-----------------------------------------
;异常处理句柄2,处理内存写错误,扩展可以有其他的例子如自动扩充堆栈
per_xHandler2 PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
       
        pushad
    MOV    ESI,pExcept
ASSUME  ESI:PTR EXCEPTION_RECORD
        MOV    EDI,pContext
ASSUME  EDI:PTR CONTEXT

        call    Dispcont                            ;显示一点lame的消息,自己调试用

        TEST    [ESI].ExceptionFlags,1
        JNZ    @cantdo2
        TEST    [ESI].ExceptionFlags,6
        JNZ    @unwind2
        CMP    [ESI].ExceptionCode,0C0000005h
        JNZ    @cantdo2
        .data                                        ;ASM的数据定义灵活性,如果需要这是可以的
        validAddress dd 0
        .code
       
        m2m    [EDI].regEax,<offset  validAddress>    ;置eax为有效地址     
        popad
    MOV  EAX, ExceptionContinueExecution
        RET

@unwind2:
        invoke    MessageBox,0,CTEXT("hmmm... unwinding in xhandler2..."),addr sztit,40h
@cantdo2:
        popad
        MOV    EAX,ExceptionContinueSearch
    RET
per_xHandler2 ENDP
;-----------------------------------------

per_xHandler3 PROC C pExcept:DWORD,pFrame:DWORD,pContext:DWORD,pDispatch:DWORD
    pushad
        MOV    ESI,pExcept
ASSUME  ESI:PTR EXCEPTION_RECORD
        MOV    EDI,pContext
ASSUME  EDI:PTR CONTEXT     

        TEST    [ESI].ExceptionFlags,1
        JNZ    @cantdo3
        TEST    [ESI].ExceptionFlags,6
        JNZ    @unwind3
        ;-----------------------------------------
                                                   
        push    ecx
        mov    ecx,cs
        xor    cl,cl
        jecxz  win2k_Xp       
win9X:
        pop    ecx
        CMP    [ESI].ExceptionCode,0C000001DH      ;非法指令异常,与2K/XP下的不一致
        JNZ    @cantdo3
        jmp    ok_here
win2k_Xp:
        pop    ecx                                  ;注意,只有在9X下才可以
        CMP    [ESI].ExceptionCode,0C000001EH      ;非法指令异常->2K/XP
        JNZ    @cantdo3                            ;sMc不成

        mov    [edi].regEip,offset safereturn
        popad
        mov    eax,0
        ret
       
       
       
        push    ebx
        push    esi
        push    edi     
comment $ 调用RtlUnwind展开堆栈       
        lea    ebx,unwindback
        invoke    RtlUnwind,Expt3_frm,ebx,esi,0
        $
        mov    dword ptr [esi+4],2                    ;置展开标志,准备展开,这里是
                                                      ;手动代码
        mov    ebx,fs:[0]
       

selfun:
        ;mov    eax,Expt2_frm                    ;这里显示了ASM手动展开的灵活性
        mov    eax,Expt3_frm                     
        cmp    ebx,eax                            ;按照Jeremy Gordon的好像不大对头
        ;cmp    dword ptr [ebx],-1                ;这样好像有问题,只好如上,请教答案         
        jz      unwindback
        push    ebx
        push    esi                                ; 压入Err和Exeption_registration结构
        call    dword ptr[ebx+4]
        add    esp,8
        mov    ebx,[ebx]
        jmp    selfun

unwindback:
        invoke    MessageBox,0,CTEXT("I am Back!"),addr sztit,40h
        pop    edi
        pop    esi
        pop    ebx                                  ;一定要保存这三个寄存器!

        MOV    EAX,[EDI].regEip
        MOV    DWORD PTR[EAX],90909090H            ;改为nop指令...SMC呵呵这次不神秘了吧
                                                    ;SMC注意连接选项
        popad
    MOV  EAX, ExceptionContinueExecution
        RET

@unwind3:
        invoke    MessageBox,0,CTEXT("Note... unwinding in xhandler3..."),addr sztit,40h
@cantdo3:
        popad
        MOV    EAX,ExceptionContinueSearch
    RET
per_xHandler3 ENDP
;-----------------------------------------
;lame routine for debug
Dispcont    proc
                inc    count
                call    dispMsg
                ret
Dispcont    endp

dispMsg    proc
        local szbuf[200]:byte
        pushad
        mov    eax,dword ptr[esi]
        mov    ebx,dword ptr[esi+4]
        mov    ecx,dword ptr[edi+0b8h]
        mov    edx,dword ptr[edi+0a4h]
        .data
        fmt    db "Context eip--> %8X  ebx--> %8X ",0dh,0ah
                db "Flags  Ex.c-> %8x  flg--> %8X",0dh,0ah
                db "it's the %d times xhandler was called!",0
        .code
        invoke    wsprintf,addr szbuf,addr fmt,ecx,edx,eax,ebx,count
        invoke    MessageBox,0,addr szbuf,CTEXT("related Mess of context"),0
        popad
    ret
dispMsg    endp

;;------------------------------------------------
END    _Start
;---------------------------------下面是上面用到的宏,我的mac.h比较长,就不贴了-----
    ddd    MACRO Text                        ;define data in .data section
        local name                  ;This and other can be used as: ddd("My god!")
        .data                  ;isn't cool?
            name    db Text,0
        .code
        EXITM <addr name> 
    ENDM

  CTEXT MACRO y:VARARG                    ;This is a good macro
        LOCAL sym
    CONST segment
        IFIDNI <y>,<>
            sym db 0
        ELSE
            sym db y,0
        ENDIF
    CONST ends
        EXITM <OFFSET sym>
    ENDM

    m2m MACRO M1, M2                          ;mov is too boring sometimes!
      push M2
      pop  M1
    ENDM   
;-----------------------------------------
  最后更正一点前面介绍的传送给final型的参数是指向EXCEPTION_POINTERS 的指针,压栈前的堆栈
是如下的,不好意思,原来写的时候我也没深入研究,可能模糊了一点,如有错误,请大家指正
push    ptEXCEPTION_POINTERS
call    xHandler
下面补充一个final参数获得的一个例子
;--------------------------------------------
; Ex5,演示final处理句柄的参数获取,更正前面
; 模糊的介绍
;--------------------------------------------
.586
.model flat, stdcall
option casemap :none  ; case sensitive
include hd.h
include mac.h

;;--------------
.data
sztit  db "exceptION MeSs,by hume[AfO]",0
fmt    db "Context eip--> %8X  ebx--> %8X ",0dh,0ah
        db "Flags  Ex.c-> %8x  flg--> %8X",0
szbuf  db 200 dup(0)
;;-----------------------------------------
    .CODE
_Start:
        assume  fs:nothing
        push    offset _final_xHandler0
        call    SetUnhandledExceptionFilter
        xor    ebx,ebx
        mov    eax,200
        cdq
        div    ebx
        invoke    MessageBox,0,ddd("Good,divide overflow was solved!"),addr sztit,40h
        xor    eax,eax
        mov    [eax],ebx
       
    invoke    ExitProcess,0   

;-----------------------------------------
_final_xHandler0:
        push    ebp
        mov    ebp,esp
       
        mov    eax,[ebp+8]      ;the pointer to EXCEPTION_POINTERS
        mov    esi,[eax]        ;pointer to _EXCEPTION_RECORD
        mov    edi,[eax+4]      ;pointer to _CONTEXT
        test    dword ptr[esi+4],1 
        jnz    @_final_cnotdo
        test    dword ptr[esi+4],6 
        jnz    @_final_unwind

        ;call    dispMsg
       

        cmp    dword ptr[esi],0c0000094h
        jnz    @_final_cnotdo

        mov    dword ptr [edi+0a4h],10
        call    dispMsg
     
        mov    eax,EXCEPTION_CONTINUE_EXECUTION      ;GO ON
        jmp    @f

@_final_unwind:
        invoke    MessageBox,0,CTEXT("state:In final unwind..."),addr sztit,0
                                          ;好像不论处理不处理异常,系统展开的时候
                                          ;都不会被调用,right?
@_final_cnotdo:                            ;请教是真的吗?还是我写的有问题
        mov    eax,EXCEPTION_CONTINUE_SEARCH
        jmp    @f       
@@:     
        mov    esp,ebp
        pop    ebp
        ret
;-----------------------------------------
dispMsg    proc
        pushad
        mov    eax,[esi]
        mov    ebx,[esi+4]
        mov    ecx,[edi+0b8h]
        mov    edx,[edi+0a4h]
        invoke    wsprintf,addr szbuf,addr fmt,ecx,edx,eax,ebx
        invoke    MessageBox,0,addr szbuf,CTEXT("related Mess of context"),0
        popad
    ret
dispMsg    endp
;;------------------------------------------------
       
END    _Start
;====================================================================================

BTW:够长了吧,基本内容介绍完毕,更多内容下一部分介绍一点利用Seh的tricks,哪位大侠有什么好的想法
或者有什么错误,请不吝指正,毕竟我是菜鸟吗...

=====================================================================================
一点闲话:请CUT--------------------------
    最近很郁闷,生活上的工作上的,就这样懒懒懒散散地目无光彩地苟活于世,最近看了一点C++,有的地方
头大,于是拿起老家伙asm写了点以前许诺过的东西,感觉还是ASM最有助于理解基本原理~~~不过C/C++给了我
们另外的工具,虽然目标代码不够紧凑(理想状态),毕竟不需要我们每个人写内核,C/C++可以提高生产能力.
    crack也一样,如果一些基本概念都不懂明白还谈什么crack?昨天看精华III一些高手的文章,真是惭愧啊!

    我的专业不是计算机或者以后永远也不会搞计算机,这些当也许只是业余爱好,永远不会放弃的业余爱好.
    以后也许很长一段时间里面我会离开大家,毕竟,面临的还有生活.
    感谢CCG,BCG的各位高手,AfO的成员们,以及那些曾无私指导过以及给我生活动力的朋友们!
    我要奋斗!
    我以此激励自己也同样希望能够激励大家!
                                                        2002.1.28深夜~~沉思中
----------------------------------------cut------------------------------------