SEH大概算得上是WINDOWS下公開的祕密了,什麼?您還不知道?不要緊,下面我來簡單地介紹一下。SEH即結構化異常處理(Structured Exception Handling),簡單地說就是當程序出現錯誤時,系統把當前的一些信息壓入堆棧,而後轉入咱們設置好的異常處理程序中執行,在異常處理程序中咱們能夠終止程序或者修復異常後繼續執行。異常處理處理分兩種,頂層異常處理和線程異常處理,下面咱們要用到的是線程異常處理。具體作法是,每一個線程的FS:[0]處都是一個指向包含異常處理程序的結構的指針,這個結構又能夠指向下一個結構,從而造成一個異常處理程序鏈。當發生異常時,系統就沿着這條鏈執行下去,直到異常被處理爲止。咱們可使FS:[0]指向咱們本身寫的異常處理程序,從而本身處理異常。這裏只是關於異常處理的簡單介紹,具體內容請參考看雪學院的《加密與解密》及相關的windows編程書籍。
咱們都知道用調試器(下面的介紹都以當前流行的調試器OllyDbg爲例)能夠設置斷點,那麼當設置斷點時調試器到底是怎樣工做的呢?這要分幾種狀況了,一種是代碼斷點,即Cracker在某行代碼上下斷點,這時調試器自動把這行代碼的首字節改成CC(即INT3中斷,這個修改在OD中不會顯示)這樣每當程序運行到這裏都會產生中斷,而調試器能夠接管這個中斷,從而實現對程序的控制;另外一種是內存斷點,即當程序對某處內存有操做(讀或寫)時產生中斷,這是直接利用CPU的調試寄存器DRx來完成的;還有一種不太像中斷的「中斷」,即單步中斷,也就是說當你在調試器中選擇「步過」某條指令時,程序自動在下一條語句停下來,這其實也屬於一種中斷,並且能夠說是最經常使用的一種形式了,當咱們須要對某段語句詳細分析,想找出程序的執行流程和註冊算法時必需要進行這一步。是80386以上的INTEL CPU中EFLAGS寄存器,其中的TF標誌位表示單步中斷。當TF爲1時,CPU執行完一條指令後會產生單步異常,進入異常處理程序後TF自動置0。調試器經過處理這個單步異常實現對程序的中斷控制。持續地把TF置1,程序就能夠每執行一句中斷一次,從而實現調試器的單步跟蹤功能。
講到這裏,不知聰明的您看出什麼問題沒有:若是咱們的程序自己就含有對單步異常的處理程序會怎麼樣呢?呵呵,據筆者的實驗是,OD會不理睬咱們程序本身的單步異常處理程序而自顧自地把異常處理接管了。這其實就給了咱們一種很巧妙的方法,咱們能夠本身把TF置1,而後把註冊算法中十分關鍵的運算放在咱們程序本身的單步異常處理程序中。這樣當程序在正常條件下執行時,一旦產生單步異常就會轉到咱們本身寫好的異常處理中繼續進行而不會受到影響,若是程序被調試,而Cracker選擇了按F8步過這段程序,那麼這時產生的單步異常會被調試器忽略,這樣那些關鍵的代碼就得不到執行,從而產生使人十分迷惑的結果。
好了,說了這麼多,下面看一個實際的例子:(MASM32 8.2下編譯經過)算法
.386
.model flat,stdcall
option casemap:none編程
include windows.inc
include kernel32.inc
include user32.inc
includelib kernel32.lib
includelib user32.libwindows
MY_DLG equ 1000
IDC_INPUT equ 1001
IDC_OUTPUT equ 1002
ID_OK equ 1003加密
.dataspa
szTit db 'SEH CrackMe',0
szGood db 'Good Job!!',0
szBad db 'You Failed, Try again!!',0.net
.data?
num dd ?
pSuc dd ?
.code線程
singlestepHandler proc c pExcept,pFrame,pContext,pDispath
pushad
assume esi:ptr EXCEPTION_RECORD,edi:ptr CONTEXT
mov esi,[pExcept]
mov edi,[pContext]
cmp [esi].ExceptionCode,STATUS_SINGLE_STEP
jnz @continue ;檢查是否爲單步異常
inc [edi].regEax ;EAX = EAX + 1
mov ebx,[edi].regEip
cmp byte ptr [ebx],90h ;檢查下一個字節是否90(NOP)
jz @finish ;若是遇到NOP則結束跟蹤
or [edi].regFlag,100h ;不然繼續對TF位置1指針
@finish:
popad
mov eax,ExceptionContinueExecution
jmp @exit
@continue:
popad
mov eax,ExceptionContinueSearch
@exit:
ret調試
singlestepHandler endpcode
DlgProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM
.if uMsg == WM_CLOSE
invoke EndDialog,hWnd,0
.elseif uMsg == WM_COMMAND
mov eax,wParam
.if eax == ID_OK
xor eax,eax ;EAX清零
pushfd ;把EFLAGS壓棧
or dword ptr [esp],100h ;由於TF在EFLAGS的第9位
popfd ;EFLAGS出棧,用這種方法把TF置1
inc eax ;從這裏開始「單步跟蹤」
inc eax
inc eax
dec eax ;對EAX進行一些操做,若是沒有異常EAX=2
nop ;用NOP標誌跟蹤結束
mov [num],eax
invoke GetDlgItemInt,hWnd,IDC_INPUT,addr pSuc,TRUE
.if [num] == eax ;與輸入進行比較
invoke SetDlgItemText,hWnd,IDC_OUTPUT,addr szGood
.else
invoke SetDlgItemText,hWnd,IDC_OUTPUT,addr szBad
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
DlgProc endp
start:
assume fs:nothing
push offset singlestepHandler
push fs:[0]
mov fs:[0],esp ;安裝咱們本身的異常處理程序
invoke GetModuleHandle,NULL
invoke DialogBoxParam,eax,MY_DLG,NULL,addr DlgProc,NULL
pop fs:[0]
add esp,4 ;卸載異常處理程序
invoke ExitProcess,0
end start
該程序若是在正常狀況下運行,正確的註冊碼應該是6,見下圖: 而若是用OD調試,卻會發現正確的註冊碼是2,以下圖所示。怎麼樣?若是是一個菜鳥Cracker,準會暈頭轉向了吧? 爲何會出現這種使人迷惑的結果呢?下面咱們來仔細分析一下。在對話框消息處理過程當中,首先用OR運算而後POPFD來設置TF位,這樣在正常狀況下,執行完一條指令後就進入咱們寫的異常處理程序,在異常處理程序使EAX加1,而後繼續置TF位爲1,這樣每執行一句都會中斷,直到執行到NOP這一句後再也不繼續「單步跟蹤」,在消息處理過程當中EAX加3減1,結果應爲2,但由於共執行了4條指令,每次在異常處理程序中EAX都會加1,所以正常狀況下結果應爲6。若是用OD等調試,由於不會執行異常處理程序,結果就爲2。這只是一個最簡單的例子,若是咱們把十分複雜的算法判斷都寫進單步異常處理程 序中,是否是就會讓Cracker很鬱悶呢? 以上只是一個簡單的例子,我在這裏只是提供一種思路,具體怎樣把註冊算法與異常處理程序巧妙結合起來,以起到最佳的保護的效果,就要靠聰明的讀者您本身了。^_^若是您有什麼意見建議歡迎與