seh to fasm 研究

seh to fasm 研究

作者:小魚

爲啥有此文呢,可能你會說seh文章很多啊。那請你去各大搜索引擎搜索下相關fasm的seh文章以及官方搜索
下更是甚少,幾乎沒有。所以就有了此文,希望能幫到一些和我一樣喜歡fasm的朋友們。


1.概念

 SEH的英文全稱是"Structured Exception Handling", 即"結構化異常處理",是windows提供給我們異常
處理機制。
 在高級語言中的_try{} _finally{} 和 _try{} _except {} 也就是利用seh機制。只是它們是將本身的s
eh機制進行了包裝,其實如果你想,我們彙編語言也可以完全通過宏來進行包裝。但是我們既然是程序愛好者
,那麼我們就應該對各種底層機制非常瞭解,所以今天就讓我帶領大家進入seh的天堂吧!!!!!

引用:

發生異常時系統的處理順序(by Jeremy Gordon):

1.系統首先判斷異常是否應發送給目標程序的異常處理例程,如果決定應該發送,並且目標程序正在被調試,則系統
掛起程序並向調試器發送EXCEPTION_DEBUG_EVENT消息.呵呵,這不是正好可以用來探測調試器的存在嗎?

2.如果你的程序沒有被調試或者調試器未能處理異常,系統就會繼續查找你是否安裝了線程相關的異常處理例程,

如果你安裝了線程相關的異常處理例程,系統就把異常發送給你的程序seh處理例程,交由其處理.

3.每個線程相關的異常處理例程可以處理或者不處理這個異常,如果他不處理並且安裝了多個線程相關的異常處

理例程,可交由鏈起來的其他例程處理.

4.如果這些例程均選擇不處理異常,如果程序處於被調試狀態,操作系統仍會再次掛起程序通知debugger.

5.如果程序未處於被調試狀態或者debugger沒有能夠處理,並且你調用SetUnhandledExceptionFilter安裝了

最後異常處理例程的話,系統轉向對它的調用.

6.如果你沒有安裝最後異常處理例程或者他沒有處理這個異常,系統會調用默認的系統處理程序,通常顯示一個

對話框, 你可以選擇關閉或者最後將其附加到調試器上的調試按鈕.如果沒有調試器能被附加於其上或者調試

器也處理不了,系統就調用ExitProcess終結程序.

7.不過在終結之前,系統仍然對發生異常的線程異常處理句柄來一次展開,這是線程異常處理例程最後清理的機會.

如果你看了上面的步驟一頭霧水的話,彆着急,化點時間慢慢理解或者進入下一部分實例操作.
 
 


Windows下的異常處理有兩種方式:

1.finally型

 finally類型是你的異常在未得到線程相關處理例程的處理,在操作系統即將結束程序前調用的處理例程,
所以它是與進程相關的而不是和線程相關的。因爲無論哪個是哪個線程發生異常未能被處理都會調用這個例程。


實戰演練:
 
 
format PE GUI 4.0
entry start

include 'win32ax.inc'

SIZE_OF_80387_REGISTERS1             equ  80
MAXIMUM_SUPPORTED_EXTENSION1         equ 512
ExceptionContinueExecution1          equ   0
EXCEPTION_MAXIMUM_PARAMETERS         equ  15

struct  EXCEPTION_RECORD
  ExceptionCode         dd      ?
  ExceptionFlags        dd      ?
  pExceptionRecord      dd      ?
  ExceptionAddress      dd      ?
  NumberParameters      dd      ?
  ExceptionInformation  dd EXCEPTION_MAXIMUM_PARAMETERS dup(?)
ends

  struct  FLOATING_SAVE_AREA1
  ControlWord   dd         ?
  StatusWord    dd         ?
  TagWord             dd           ?       ;  **
  ErrorOffset   dd         ?
  ErrorSelector   dd       ?       ;  **
  DataOffset    dd         ?
  DataSelector  dd         ?
  RegisterArea  db  SIZE_OF_80387_REGISTERS1 dup(?)
  Cr0NpxState   dd         ?
ends


  struct  CONTEXT1
  ContextFlags  dd         ?
  iDr0          dd         ?
  iDr1          dd         ?
  iDr2          dd         ?
  iDr3          dd         ?
  iDr6          dd         ?
  iDr7          dd         ?
  FloatSave     FLOATING_SAVE_AREA1 <>
  reg_Gs        dd         ?             ;  gs register 
  reg_Fs        dd         ?             ;  fs register 
  reg_Es        dd         ?             ;  es register 
  reg_Ds        dd         ?             ;  ds register
  reg_Edi       dd         ?
  reg_Esi       dd         ?
  reg_Ebx       dd         ?
  reg_Edx       dd         ?
  reg_Ecx       dd         ?
  reg_Eax       dd         ?
  reg_Ebp       dd         ?    ;  SEH
  reg_Eip       dd         ?    ;  SEH
  reg_Cs        dd         ?             ;  cs register 
  reg_Flag      dd         ?             ;  eflags register 
  reg_Esp       dd         ?             ;  esp register    ;  SEH
  reg_Ss        dd         ?             ;  ss register

  ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION1 dup(?)
ends


 .data
  
  szMsg  db '異常發生的位置: %08x, 異常代碼: %08x, 標誌: %08x', 0
  szText  db '嘿嘿俺安全了', 0
  szCap  db 'Finally 異常處理例子', 0
  lpOldExcep rd 1
  
 .code

 proc Finally_Handler lpExp:DWORD
  locals
   szBuffer db 200 dup (?)
  endl
  
  pushad
  mov eax,[lpExp]
 
  virtual at eax
   .lpExcept dd ?
   .lpContext dd ?
  end virtual
 
  mov esi, [.lpExcept]
  mov edi, [.lpContext]
  
  callw wsprintf, addr szBuffer, szMsg, [edi+CONTEXT1.reg_Eip],/
    [esi+EXCEPTION_RECORD.ExceptionCode], [esi+EXCEPTION_RECORD.ExceptionFlags]
  add esp, 14h
  mov [edi+CONTEXT1.reg_Eip], _safe
  callw MessageBox, NULL, addr szBuffer, szCap, MB_OK
  
  popad
  xor eax, eax  ;使eax = -1,因爲windows 就是根據返回值來決定下一步如何處理的。
  dec eax    ; eax = -1 表示將恢復之前保存的環境快,然後繼續執行
  ret     ; eax = 1  表示結束這個進程,不彈出對話框
 endp     ; eax = 0  表示按照默認的異常處理例程去處理,也就是彈出錯誤對話框,然後結束進程
  
start:

  callw SetUnhandledExceptionFilter, Finally_Handler
  mov [lpOldExcep], eax
  ; Exception code
  xor ecx, ecx
  mov eax, 200
  cdq
  div ecx
 _safe:
  callw MessageBox, NULL, szText, szCap, MB_OK
  callw SetUnhandledExceptionFilter, [lpOldExcep]
  callw ExitProcess, NULL
  
  
 .import
 
  library kernel32, 'kernel32.dll',/
    user32, 'user32.dll'
   
  include 'api/kernel32.inc'
  include 'api/user32.inc'
  
  

我們來分析下這段代碼:

  這句程序首先通過SetUnhandledExceptionFilter函數來設置異常處理例程,這個函數只有一個參數
那就是異常處理例程的地址。 我們的異常處理函數也只有一個參數,這個參數是指向EXCEPTION_POINTERS結構的指針。

struct  EXCEPTION_POINTERS
 .lpExcept dd ?
 .lpContext dd ?
ends

 這個結構中的第一個成員是指向EXCEPTION_RECORD結構的指針。 第二個成員是指向CONTEXT結構的指針。
EXCEPTION_RECORD結構保存了異常產生的原因、產生的位置等情況。CONTEXT結構保存了異常產生時刻的運行環境。

 首先我們通過 mov eax,[lpExp] 獲得我們這個回調函數的參數值。
 
 緊接着我們通過我們fasm的宏 virtual 來建立虛擬數據,這個虛擬數據僅僅包含在源代碼中,並不包含在
編譯的程序中。fasm也提供了assume,但是我覺得virtual更爽些,(*^__^*) 嘻嘻……

 virtual宏的格式:
 
 virtual at 偏移地址
  ;定義虛擬數據
 end virtual
 
 這樣我們就可以直接引用這些虛擬數據,而這些虛擬數據的地址就是virtual at後面的地址 + 數據字節大小。因爲
eax寄存器中保存的是我們的EXCEPTION_POINTERS結構的指針,所以此時我們就可以通過建立EXCEPTION_POINTERS結構虛擬
數據,然後通過這些數據標號名 我們就可以來引用了,這樣給我們的源代碼的可讀性帶來好處,否則我們必須通過raw asm。
很多搞病毒的朋友就喜歡raw asm,它們貌似就喜歡別人看不懂自己的代碼,以此爲樂,好了不扯了。

 然後我們通過
 
     mov esi, [.lpExcept]
     mov edi, [.lpContext]
   來獲得指向 EXCEPTION_RECORD結構的指針,和CONTEXT結構的指針,接下來我們就可以通過+結構中偏移來進行變址尋址
 了。

 然後我們通過mov [edi+CONTEXT1.reg_Eip], _safe   ; [edi+結構偏移],這樣通過變址尋址就可以訪問結構中各個
成員。


    這裏通過改寫context結構的中的eip寄存器,這樣我們等下返回-1,windows就會恢復我們之前的contexe結構,然後去
執行,由於這裏我們改寫了eip寄存器,所以我們就可以跳過發生異常錯誤的地址,從而跳到安全的地址中執行。


     返回 1 表示我已經處理了異常,可以優雅地結束了
     返回 equ 0 表示我不處理,其他人來吧,於是windows調用默認的處理
程序顯示一個錯誤框,並結束
     返回 -1 表示錯誤已經被修復,請從異常發生處繼續執行 。你可以試着讓程序返回0和-1然後編譯程序,就會理解我
所有蒼白無力的語言...

;---------------------------------------------------------------------------------------------------------------

  SHE
 
2. Thread Exception Handler, 線程相關的異常處理,通常每個線程的fs段寄存器指向一個TIB結構。該結構如下:

struct NT_TIB
 ExceptionList dd ? ;seh 鏈入口
 StackBase  dd ?   ;堆棧基地址
 StackLimit  dd ? ;堆棧大小
 SubSystemTib dd ?
 FiberData  dd ?
 AribitraryUserPointer dd ?
 Self   dd ? ; 本nt_tib結構的線性地址
ends

由於fs:[0] 中就是[NT_TIB.ExceptionList], ExceptionList成員的值指向的是EXCEPTION_REGISTRATION。該結構如下:

struct EXCEPTION_REGISTRATION
 prev  dd ? ;前一個EXCEPTION_REGISTRATION結構的地址
 handler  dd ? ;異常處理回調函數的地址
ends

第一個成員是之前EXCEPTION_REGISTRATION 結構的地址,handler是我們異常處理例程的地址。。既然是這樣的話,
我們來思考,我們只要自己建立一個EXCEPTION_REGISTRATION結構,然後修改TIB結構中的ExceptionList成員爲我們
的結構,這樣我們的就將關聯了此線程的異常處理例程,只要有異常發生,windows就會首先調用線程相關的異常處理
例程,如果失敗,或者是不存在線程相關的異常處理例程的話,再去調用進程相關的異常處理例程,也就是上面我們說
的finally型的。

 好,因爲最常用的是用堆棧來建立,所以我就採用堆棧來建立吧,當然靜態內存也是可以的。

例子:

format PE GUI 4.0
entry start

include 'win32ax.inc'


 .data
  
  szMsg db 'seh測試', 0
  szCap db '線程相關異常處理例程', 0
  
 
 .code

Thread_Handler:
  callw MessageBox, NULL, szMsg, szCap, MB_OK
  mov eax, 1 ; eax = 1表示由其他例程進行處理 , eax = 0表示已經修復異常,恢復context繼續執行。
  ret
  
  
start:
  push Thread_Handler
  push dword [fs:0]
  mov dword [fs:0], esp
  ;Exception Code
  xor eax, eax
  xchg [eax], eax
  callw MessageBeep, MB_OK 
  callw ExitProcess, 0
 
 
 
 .import
 
  library kernel32, 'kernel32.dll',/
    user32, 'user32.dll'
    
  include 'api/kernel32.inc'
  include 'api/user32.inc'
 
 
 哈哈,這個夠簡單吧。由於我只是想讓大家先了解下框架,等下我們再來深入的探究。由於我們沒有
獲得這個回調函數的參數的相信信息,所以我們不能修改相應的寄存器值,所以我們的這個程序運行僅僅是
彈出一個對話框,緊接由系統默認的異常處理例程處理,自然就有那個討厭的對話框。

 注意:這個返回值和finally型可不一樣。
 
 、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、、
 好像目前而來我們並沒有得到很多的好處,除了在異常發生後,執行一點我們那微不足道的代碼,事實上seh
 可以修復我們的代碼並且可以讓我們從我們想執行的地方開始執行。好了,我們繼續來深入的探析seh機制。
 
 
 
 
 3.深入學習
 
 首先Thread Exception Handler回調函數的參數定義和finally型不同,其函數格式不是stdcall 的形式,
而是c的格式。也就是需要我們自己修正堆棧

 這個回調函數有四個參數,但是一般是前3個參數是必須的,最後一個參數沒有什麼用處。
 
 proc Thread_Handler lpExceptRecord:DWORD, lpSeh:DWORD, lpContext:DWORD, lpDispatcherContext:DWORD
 
 endp
 
 第一個參數lpExceptRecord的是EXCEPTION_RECORD結構, 其實我們在學習finally型的異常處理例程時候已經學習了。

EXCEPTION_RECORD這個結構中包含了我們異常的相關信息,例如異常產生的原因,異常的發生位置等信息。

 第二個參數lpSeh指向的註冊回調函數時使用的EXCEPTION_REGISTRATION結構的地址。也就是在上面哪個例子中
我們在堆棧中構造的這個結構的地址。

 第三個參數lpContext指向的是我們的contexe結構,這裏我就不多說了和我們上面的finally型介紹的是一樣的。
 
 第四個參數沒有什麼用,所以這裏我就不說了。
 
 
 由於這個調用例程是的c的約定形式,所以就不能用proc僞指令了。我們直接通過標號,然後通過esp做堆棧指針,
來訪問參數。

 例子:
 
format PE GUI 4.0
entry start

include 'win32ax.inc'

SIZE_OF_80387_REGISTERS1            equ  80
MAXIMUM_SUPPORTED_EXTENSION1        equ  512
ExceptionContinueExecution1         equ   0

 

 struct  FLOATING_SAVE_AREA1
  ControlWord   dd         ?
  StatusWord    dd         ?
  TagWord             dd           ?       ;  **
  ErrorOffset   dd         ?
  ErrorSelector   dd       ?       ;  **
  DataOffset    dd         ?
  DataSelector  dd         ?
  RegisterArea  db  SIZE_OF_80387_REGISTERS1 dup(?)
  Cr0NpxState   dd         ?
ends


  struct  CONTEXT1
  ContextFlags  dd         ?
  iDr0          dd         ?
  iDr1          dd         ?
  iDr2          dd         ?
  iDr3          dd         ?
  iDr6          dd         ?
  iDr7          dd         ?
  FloatSave     FLOATING_SAVE_AREA1 <>
  reg_Gs        dd         ?             ;  gs register 
  reg_Fs        dd         ?             ;  fs register 
  reg_Es        dd         ?             ;  es register 
  reg_Ds        dd         ?             ;  ds register
  reg_Edi       dd         ?
  reg_Esi       dd         ?
  reg_Ebx       dd         ?
  reg_Edx       dd         ?
  reg_Ecx       dd         ?
  reg_Eax       dd         ?
  reg_Ebp       dd         ?    ;  SEH
  reg_Eip       dd         ?    ;  SEH
  reg_Cs        dd         ?             ;  cs register 
  reg_Flag      dd         ?             ;  eflags register 
  reg_Esp       dd         ?             ;  esp register    ;  SEH
  reg_Ss        dd         ?             ;  ss register

  ExtendedRegisters db MAXIMUM_SUPPORTED_EXTENSION1 dup(?)
ends


struct SEH
        PrevLink        dd      ?       ;the address of the previous seh structure 
        CurrentHandler  dd      ?       ;the address of the exception handler 
        SafeOffset      dd      ?       ;The offset where it's safe to continue execution 
        PrevEsp         dd      ?       ;the old value in esp 
        PrevEbp         dd      ?       ;The old value in ebp 
ends

 

 .data
  szMsg db '除法的商是%d', 0
  szCap db '異常處理例程', 0
  szBuf db 200 dup (?)
  
 .code

Theard_Handler:
 
 virtual at esp+4
  .lpExceptRecord dd ?
  .lpSeh   dd ?
  .lpContext  dd ?
  .lpDisPatcherContext dd ?
 end virtual
  
  mov edi, [.lpContext]
  mov [edi+CONTEXT1.reg_Ecx], 20
  lea eax, [Execute]
  mov [edi+CONTEXT1.reg_Eip], Execute ;從偶們指定的地方開始執行
  sub eax, eax  ;返回0,表示context修復,恢復context執行。
  ret 16
  
  
 
start:
  push Theard_Handler
  push dword [fs:0]
  mov dword [fs:0], esp
  xor ecx, ecx
  mov eax, 200
  cdq
Execute:
  div ecx
  callw wsprintf, szBuf, szMsg, ecx
  add esp, 12
  callw MessageBox, NULL, szBuf, szCap, MB_OK
  callw ExitProcess, NULL
  
 
 
 .import
 
  library kernel32, 'kernel32.dll',/
    user32, 'user32.dll'
    
  include 'api/kernel32.inc'
  include 'api/user32.inc'
 
這個例子中我們通過在異常處理例程中恢復ecx寄存器的值和eip的值,使程序繼續執行,並顯示除法的結果。

好,到這裏這篇文章已經基本介紹完了,當然本文不可能把seh的文章能全部的介紹完,只能帶領大家進入seh的領域,把文章中的代碼和分析多多讀下,你會發現的更多。。


參考文獻: 冷雨飄心的SEH in ASM研究
  

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章