用單步異常檢測OllyDbg的巧妙方法 轉

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

MY_DLG equ 1000
IDC_INPUT equ 1001
IDC_OUTPUT equ 1002
ID_OK equ 1003

.data

szTit db 'SEH CrackMe',0
szGood db 'Good Job!!',0
szBad db 'You Failed, Try again!!',0

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

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很鬱悶呢?
以上只是一個簡單的例子,我在這裏只是提供一種思路,具體怎樣把註冊算法與異常處理程序巧妙結合起來,以起到最佳的保護的效果,就要靠聰明的讀者您自己了。^_^如果您有什麼意見建議歡迎與

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