[譯]The other ways to detect OllyDbg 檢測OllyDbg的另類方法

看雪資料發表於2015-11-15

原文連結:http://bbs.pediy.com/showthread.php?s=&threadid=4013

這是linhanshi兄放在他的網站上的,我覺得不錯就翻譯了下,水平很爛,多多包涵!

The other ways to detect OllyDbg 檢測OllyDbg的另類方法

Pumqara作/RoBa[TT]譯

前言

現在是2004年了,RING-3級偵錯程式被越來越多地使用,因為它們有圖形介面並且比RING-0級偵錯程式(比如SOFTICE)更加方便。在這篇文章中我將講述如何檢測最好的RING-3偵錯程式之一――OllyDbg。許多人都聽說過IsDebugerPresent和fs:[20]檢測手段,但是有沒有其他的新方法呢?下面我向你介紹我自己的一些檢測手段。我會給出詳細的解釋,因此你可以用你自己的想象力來完善它們。

方法一:FindWindow

這個方法是基於FindWindow函式。像所有的對話方塊一樣,OllyDbg的主對話方塊(視窗?)也有它的標題和類名。使用這個API函式我們可以判斷OllyDbg的主視窗是否開啟。Microsoft這樣寫道:
------------------------------------------------------------------------------------------------
FindWindow函式能夠獲得視窗類名或標題為特定字串的頂層視窗的控制程式碼。該函式不搜尋子視窗。

HWND FindWindow(

LPCTSTR lpClassName, //視窗類名的地址
LPCTSTR lpWindowName //視窗標題的地址
);

引數說明

lpClassName
指向一個以NULL結尾的表示視窗類名的字串的指標,或者是一個標識視窗類名字串的atom。如果該引數是一個atom,它必須是被函式GlobalAddAtom預先建立的一個全域性atom。這個16位的atom必須放在lpClassName的低8位,lpClassName的高8位必須為0。

lpWindowName
指向一個以NULL結尾的表示視窗名稱(即標題)的字串的指標。如果這個引數為NULL,所有的視窗都被認為是符合條件的。

返回值

如果搜尋成功,返回找到的符合條件的視窗控制程式碼。
如果搜尋失敗,返回值為NULL。要得到詳細的錯誤資訊可以呼叫GetLastError。
------------------------------------------------------------------------------------------------
我的程式片斷:

程式碼:
.data strOllyClsName db "OLLYDBG",0 .code invoke FindWindow, ADDR strOllyClsName, NULL cmp eax, 00000000h jne Olly_Detected/

方法二:CreateToolhelp32Snapshot, Process32First/Next

這是一個有趣的方法。它基於四個API函式(CreateToolhelp32Snapshot, Process32First, Process32Next, GetCurrentProcessId)和一個結構( PROCESSENTRY32 )。先看看MSDN怎麼說:
------------------------------------------------------------------------------------------------
[CreateToolhelp32Snapshot]

CreateToolhelp32Snapshot函式為指定的程式(可以包括程式使用的堆[HEAP],模組[MODULE]和執行緒[THREAD])建立一個快照[snapshot]。

HANDLE WINAPI CreateToolhelp32Snapshot(
DWORD dwFlags,
DWORD th32ProcessID
);

引數說明:

dwFlags

[輸入]TH32CS_INHERIT - 宣告快照控制程式碼是可繼承的。
TH32CS_SNAPALL - 在快照中包含系統中所有的程式和執行緒,還包括在th32ProcessID中指定的程式的堆和模組TH32CS_SNAPHEAPLIST - 在快照中包含在th32ProcessID中指定的程式的所有的堆。要列舉出程式的堆,檢視Heap32ListFirst。
TH32CS_SNAPMODULE - 在快照中包含在th32ProcessID中指定的程式的所有的模組。要列舉出程式的模組,檢視Module32ListFirst。
TH32CS_SNAPPROCESS - 在快照中包含系統中所有的程式。要列舉出所有程式,檢視Process32First.
TH32CS_SNAPTHREAD - 在快照中包含系統中所有的執行緒。要列舉出所有執行緒,檢視Thread32First.

th32ProcessID
[輸入]指定將要抓取的程式ID。如果該引數為0表示抓取當前程式。該引數只有在設定了TH32CS_SNAPHEAPLIST,TH32CS_SNAPMOUDLE或TH32CS_SNAPALL後才有效,在其他情況下該引數被忽略,所有的程式都會被抓取。 

Return Values

如果呼叫成功,返回快照的控制程式碼。
如果呼叫失敗,返回INVAID_HANDLE_VALUE。

Remarks

The snapshot taken by this function is examined by the other tool help functions to provide their results. Access to the snapshot is read only. The snapshot handle acts like an object handle and is subject to the same rules regarding which processes and threads it is valid in.

要列舉出所有程式的堆和模組狀態,指定TH32CS_SNAPALL並且把th32ProcessID設為0。然後,對於快照中每個新增的程式,再次呼叫CreateToolhelp32Snapshot,設定要抓取的新程式ID和TH32CS_SNAPHEAPLIST或TH32CS_SNAPMOULE.

要刪除快照,使用CloseHandle函式。
------------------------------------------------------------------------------------------------
[Process32First]

Process32First得到一個系統快照裡第一個程式的資訊。

BOOL WINAPI Process32First(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);

引數說明

hSnapshot 
[輸入] 先前呼叫CreateToolhelp32Snapshot函式時返回的指向系統快照的控制程式碼

lppe 
[輸入,輸出] 指向一個PROCESSENTRY32結構的指標

返回值

如果程式列表的第一個入口已經被複制到緩衝區中則返回TRUE,否則返回FALSE。如果快照中不包含程式資訊,通過GetLastError會返回一個ERROR_NO_MORE_FILES錯誤。

備註

呼叫該函式的程式必須把PROCESSENTRY32中的成員dwSize設定為該結構的大小(用位元組數表示)。Process32First會把dwSize改為寫入該結構的位元組數。這個值永遠不會比dwSize的初始值更大,但是有可能更小。如果這個值變小了,任何偏移量大於這個值的成員都是不可靠的。

要得到同一快照中其它程式的資訊,使用Process32Next函式。
------------------------------------------------------------------------------------------------
[Process32Next]

Process32Next可以得到在一個系統快照中下一個程式的資訊。

BOOL WINAPI Process32Next(
HANDLE hSnapshot,
LPPROCESSENTRY32 lppe
);

引數說明

hSnapshot 
[輸入] 先前呼叫CreateToolhelp32Snapshot函式時返回的指向系統快照的控制程式碼

lppe 
[輸出] 指向一個PROCESSENTRY32結構的指標 

返回值

如果程式列表的下一個入口已經被複制到緩衝區中則返回TRUE,否則返回FALSE。如果快照中不包含程式資訊,通過GetLastError會返回一個ERROR_NO_MORE_FILES錯誤。

備註

要得到快照中第一個程式的資訊,使用Process32First函式。
------------------------------------------------------------------------------------------------
[PROCESSENTRY32]

當一個快照建立後,PROCESSENTRY32描述了在系統地址空間中一系列程式中的一條。

typedef struct tagPROCESSENTRY32 {
DWORD dwSize;
DWORD cntUsage;
DWORD th32ProcessID;
ULONG_PTR th32DefaultHeapID;
DWORD th32ModuleID;
DWORD cntThreads;
DWORD th32ParentProcessID;
LONG pcPriClassBase;
DWORD dwFlags;
TCHAR szExeFile[MAX_PATH]; 

} PROCESSENTRY32, *PPROCESSENTRY32;

成員變數

dwSize 
用位元組數表示的結構大小。在呼叫Process32First前,把這個成員設為sizeof(PROCESSENTRY32)。如果你不對dwSize進行初始化,Process32First會呼叫失敗。

cntUsage 
該程式被引用的次數。只有一個程式的引用次數不為0時這個程式才存在。一旦它的引用次數為0,程式就終止了。

th32ProcessID 
該程式的標識。(ID)

th32DefaultHeapID 
Identifier of the default heap for the process. The contents of this member has meaning only to the tool help functions. It is not a handle, nor is it usable by functions other than the ToolHelp functions. 

th32ModuleID 
Module identifier of the process. The contents of this member has meaning only to the tool help functions. It is not a handle, nor is it usable by functions other than the ToolHelp functions. 


cntThreads 
該程式啟動的執行緒數目。 

th32ParentProcessID 
建立該程式的父程式的標識。

pcPriClassBase 
Base priority of any threads created by this process.
 
dwFlags 
未使用,保留

szExeFile 
指向一個以NULL結尾的字串的指標,該字串說明了程式所屬的可執行檔案。

在Windows Me/98/95中,這個檔名包含路徑。
------------------------------------------------------------------------------------------------
[GetCurrentProcessId]

GetCurrentProcessId返回撥用該函式的程式的標識。

DWORD GetCurrentProcessId(VOID)

引數說明:

該函式沒有引數。 

返回值

返回值為呼叫該函式的程式的標識。

備註

這個程式標識(ID)在系統中確定唯一的程式,直到程式終止。
------------------------------------------------------------------------------------------------
我們的目標是檢測我們的程式的父程式是不是OllyDbg.

計劃:
1.)用GetCurrentProcessId得到我們程式的程式ID。
2.)用CreateToolhelp32Snapshot和Process32First/Next依次比較每一個PROCESSENTRY32結構中的th32ProcessID成員是不是我們的程式ID,直到找出為止。
3.)得到PROCESSENTRY32結構中的th32ParentProcessID。(譯註:即父程式ID)
4.)用CreateToolhelp32Snapshot開始一輪新的比較,但這次比較的是每個th32ProcessID成員是不是我們在第3步得到的父程式的ID,直到找出為止。
5.)得到PROCESSENTRY32中的szExeFile成員,看是不是"Ollydbg.exe"
6.)如果是的話就知道我們的程式正在OllyDbg控制下執行了。

示例程式碼:
程式碼:
.586 .model flat, stdcall option casemap:none include d:\masm32\INCLUDE\Windows.inc include d:\masm32\INCLUDE\user32.inc include d:\masm32\INCLUDE\kernel32.inc includelib d:\masm32\lib\user32.lib includelib d:\masm32\lib\kernel32.lib .data strCaption db "OllyDbg Detector!",0 strFound db "OllyDbg found!",0 strNotFound db "OllyDbg NOT found!",0 strOllyDbg db "OLLYDBG.EXE",0h valCurrentPiD dd 0  valParentPiD dd 0 hSnapShot dd 0 .data? proces PROCESSENTRY32 <> .code start:  ; 建立快照 invoke CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS,NULL mov hSnapShot,eax ; 得到當前程式ID invoke GetCurrentProcessId mov valCurrentPiD,eax lea esi,offset proces assume esi:ptr PROCESSENTRY32 mov [esi].dwSize,sizeof PROCESSENTRY32 ; 開始第一輪搜尋 ; 用valCurrentPiD查詢當前程式 invoke Process32First,hSnapShot,addr proces lea esi,offset proces assume esi:ptr PROCESSENTRY32 mov ebx,valCurrentPiD cmp ebx,[esi].th32ProcessID jne nope1 nope1: invoke Process32Next,hSnapShot,addr proces lea esi,offset proces assume esi:ptr PROCESSENTRY32 mov ebx,valCurrentPiD cmp ebx,[esi].th32ProcessID jne nope1 push [esi].th32ParentProcessID pop valParentPiD invoke CloseHandle,hSnapShot ; 再次建立快照 invoke CreateToolhelp32Snapshot, TH32CS_SNAPPROCESS,NULL mov hSnapShot,eax mov [esi].dwSize,sizeof PROCESSENTRY32 ; 開始第二輪搜尋 ; 使用valParentPiD查詢當前程式的父程式 invoke Process32First,hSnapShot,addr proces lea esi,offset proces assume esi:ptr PROCESSENTRY32 mov ebx,valParentPiD cmp ebx,[esi].th32ProcessID jne nope2 nope2: invoke Process32Next,hSnapShot,addr proces lea esi,offset proces assume esi:ptr PROCESSENTRY32 mov ebx,valParentPiD cmp ebx,[esi].th32ProcessID jne nope2 ; 從完整路徑中提取出檔名 lea eax, [esi].szExeFile push eax invoke lstrlen,eax sub eax,11 pop ebx add ebx,eax ; 把檔名變成大寫,與"OLLYDBG.EXE"比較  invoke CharUpper,ebx invoke lstrcmp,ebx,addr strOllyDbg .IF eax==0 invoke MessageBox,0,addr strFound,addr strCaption,0 .ELSE invoke MessageBox,0,addr strNotFound,addr strCaption,0 .ENDIF invoke CloseHandle,hSnapShot invoke ExitProcess,0 end start

方法三:SetUnhandledExceptionFilter

(譯註:關於SEH在Hume的<<SEH in Asm>>一文中有經典論述,在此省略,僅列出程式碼,具體細節請參考英文原文和<<SEH in Asm>>一文)
程式碼:
.586 .model flat, stdcall option casemap:none include d:\masm32\INCLUDE\Windows.inc include d:\masm32\INCLUDE\user32.inc include d:\masm32\INCLUDE\kernel32.inc includelib d:\masm32\lib\user32.lib includelib d:\masm32\lib\kernel32.lib .data strCaption db "OllyDbg Detector",0 strNotFound db "OllyDbg NOT found!",0 .code ExcpHandler proc mov eax, dword ptr [esp+4] ; eax = EXCEPTION_POINTERS  mov eax, [eax+4] ; eax = CONTEXT  assume eax:ptr CONTEXT mov [eax].regEip, offset safe_address ; Change regEip pushad  invoke MessageBox,0,addr strNotFound,addr strCaption,0 popad xor eax, eax ;\ ; ) Set EXCEPTION_CONTINUE_EXECUTION dec eax ;/ retn 4 ; Normalize stack and return ExcpHandler endp start:  invoke SetUnhandledExceptionFilter,offset ExcpHandler mov ebx,dword ptr [0FFFFFFFFh] ;Exception is here! safe_address: invoke ExitProcess,0 end start 

方法四:API重定位

這是我自己發現的一種方法,它基於OllyDbg呼叫API函式時的處理方法。當被除錯的程式呼叫API函式時,Oleh Yuschuk(OllyDbg的作者)在他的偵錯程式中使用API重定位方法來處理。下面看一個例子:

0401000 >PUSH ASD.00403033 ; /FileName = "kernel32.dll"
00401005 CALL ; \LoadLibraryA
|
|
'--> 0040105C JMP DWORD PTR DS:[402004] ; JMP DWORD PTR DS:[<&KERNEL32.LoadLibraryA>]
|
|
'--> 87FF4120 PUSH BFF776D0 ; PUSH KERNEL32.LoadLibraryA
87FF4125 JMP KERNEL32.BFF957CA 

看上去他手動裝載了輸入表並且手動填充了IAT,所有的API地址都被重定到位一個分配好的緩衝區裡。在這個緩衝區中他用一種奇怪的方式呼叫函式。但是對我們來說這裡有一個更重要的問題:他同樣模仿了GetProcAddress函式,因此這個函式返回的是重定位以後的地址。舉個例子來說,IsDebuggerPresent在記憶體中的實際地址為BFF946F6,但是這個函式返回的是重定位以後的地址87FF4110。我們怎樣用這個來實現OllyDbg檢測呢?嗯,有很多方法,下面是最簡短的一種:

1.)載入 kernel32.dll 庫,這時將會返回它的基地址(它載入到記憶體的位置)
2.)呼叫 GetProcAddress 函式得到 ExitProcess 的地址
3.)將上面的返回值(用GetProcAddress得到的ExitProcess的地址)和kernel32.dll的基地址比較
4.)如果這個返回值(第2步所得)大於基地址(第1步所得),我們可以肯定這個API函式是被直接呼叫的,否則這個API就是被間接呼叫。

示例程式:
程式碼:
.586 .model flat, stdcall option casemap:none include d:\masm32\INCLUDE\Windows.inc include d:\masm32\INCLUDE\user32.inc include d:\masm32\INCLUDE\kernel32.inc includelib d:\masm32\lib\user32.lib includelib d:\masm32\lib\kernel32.lib .data strCaption db "OllyDbg Detector",0 strFound db "OllyDbg found!",0 strNotFound db "OllyDbg NOT found!",0 strLibrary db "kernel32.dll",0 strFunction db "ExitProcess",0 .code start:  invoke LoadLibrary,addr strLibrary push eax ; EAX為kernel32.dll的基地址 invoke GetProcAddress,eax,addr strFunction ; eax為ExitProcess的地址或者重定位以後的地址 pop ebx ; EBX為kernel32.dll的基地址  cmp eax,ebx ; 是否ExitProcess的地址 < Kernel32.dll的基地址    jl Olly_Detected invoke MessageBox,0,addr strNotFound,addr strCaption,NULL invoke ExitProcess,0 Olly_Detected: invoke MessageBox,0,addr strFound,addr strCaption,NULL invoke ExitProcess,0 end start 

結束語:

希望您喜歡這篇文章。祝您好運,Pumqara。

譯者菜評:

這篇文章提出了四種檢測OllyDbg的方法,其中第一種應該是最簡單最常見的,第三種方法用SEH在看雪的書中也有涉及,但第二種和第四種方法非常新穎,而且只有當程式正被除錯的時候才會檢測到,這樣也更“人性化”一點,不像FindWindow或者傳統的遍歷程式一樣,發現有偵錯程式不管人家在幹什麼一律拉出去斃了:D。
本來挺通順的文章,經我折騰後怎麼讀都彆扭,那些程式執行緒什麼的本來我就攪不清,一翻譯更亂了,哪位能幫忙改下。看來以後還要好好學習,提高水平。感謝linhanshi兄提供這篇文章,感謝您耐心把它讀完。:)


相關文章