SEH IN ASM 研究(二)---提高篇:關於異常處理的巢狀和堆疊展開 (15千字)

看雪資料發表於2002-01-28

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------------------------------------

相關文章