1. 程式人生 > >Windows下反反除錯技術彙總

Windows下反反除錯技術彙總

一、前言

對於安全研究人員來說,除錯過程中經常會碰到反除錯技術,原因很簡單:除錯可以窺視程式的執行“祕密”,而程式作者想要通過反除錯手段隱藏他們的“祕密”,普通程式需要防止核心程式碼被除錯逆向,惡意程式碼需要隱藏自己的惡意行為防止被跟蹤。就像病毒和殺軟的關係一樣,為了順利的逆向分析,有反除錯手段就有對應的破解方法-反反除錯。對此,天融信阿爾法實驗室研究人員總結了各類常見反除錯手段以及對應的繞過方案,作為總結希望與君共勉。

關於各種反除錯手段,網路上和各種安全書籍上都有對應的介紹、各種除錯工具外掛已經集成了反除錯功能,但有的只是介紹了反除錯方法,並沒有對應的破解方式,有的反除錯手段已經失效。本文並不是研究新的反除錯方法,而是對windows平臺的反除錯技術進行分類總結,並介紹其原理和應對方法。

合理的分類有助於學習和理解各種反除錯技術的原理,由於理解不同,分類也不盡相同。當你掌握了這些技術後,可以按照自己的理解重新給它們分類。本文按照靜態反除錯方法與動態反除錯方法將反除錯技術分為兩大類,其中靜態反除錯技術的分類原理為:程式啟動時,系統會根據正常執行和除錯執行分配不同的程序環境,通過檢測程序環境來檢測程序是否處於除錯狀態;根據逆向人員的工作環境和程式的正常執行環境不同,可以通過檢測偵錯程式或逆向分析工具實現反除錯。動態反除錯技術的分類原理是:程序執行時的執行流程是否正常、執行狀態是否正常。

本文測試環境:

測試程式碼: https://github.com/alphaSeclab/anti-debug

偵錯程式為原版OllyDbg1.1(http://www.ollydbg.de/odbg110.zip) 因為各種魔改帶外掛的OD已經自帶許多反反除錯功能,會影響測試效果。

測試系統為win7 SP1 32位旗艦版

PE編輯器:LordPE

二、靜態反除錯技術

許多靜態反除錯技術對系統有較強的依賴,本文測試的靜態反除錯技術時使用的是win 7 SP1 32位旗艦版。像PEB中ProcessHeap的Flags與ForceFlags等類似反除錯手段針對的是NT5.X以下版本,win7 下檢測就會失效,所以本文未做介紹。

2.1 程序狀態檢測

我們不一樣:正常啟動的程序和除錯啟動的程序的某些初始資訊是不同的,比如程序環境塊PEB、STARTUPINFO等,這些資訊使用方便,經常被廣泛應用於反除錯技術。

2.1.1 BeingDebugged

PEB結構體的內容如下(部分省略):

image.png

 

上表中列出了Windows 7 32位SP1中PEB的結構體成員,其中我們將會介紹到的與反除錯相關的成員有:偏移為0×002的BeingDebugged和偏移為0×068的NtGlobalFlag。BeingDebugged是一個標誌位,就像它的名字一樣,用來表示程序是否處於被除錯狀態,NtGlobalFlag用來表示程序的堆記憶體特性,除錯和非除錯時其值不同。所以我們只需要獲取這兩個值與正常狀態的值比較,就能知道程序是否處於除錯狀態。

利用fs段暫存器指向的TEB結構可以得到PEB結構體的地址:

mov EAX,DWORD PTR FS:[0x30]

除錯時該值為TRUE,非除錯時該值為FALSE。所以我們可以這樣判斷程序是否被除錯:

 

image.png

正常執行時,該函式返回FALSE,用OD開啟除錯執行時,該函式返回TRUE。

其實有一個API可以直接獲取BeingDebugged的值:IsDebuggerPresent();我們看下這個API的實現程式碼:在OD反彙編視窗按下快捷鍵CTRL+G,輸入函式名跳轉到函式地址

image.pngimage.png

 

可以看到該函式其實也是讀取的PEB中的BeingDebugged標誌位的值。和我們的程式碼相比,它多了一行MOV EAX,DWORD PTRFS:[18],這行程式碼的作用是獲取TEB的地址,我們上面說了,PEB的地址在TEB中,所以可以先獲取TEB的地址,再獲取PEB的地址。

破解方法:除錯時手動將其置0。

OD開啟被除錯程式,在記憶體視窗CTRL+G,輸入FS:[30],記憶體視窗將跳轉到PEB的地址:

image.png

 

選中偏移為2的位置CTRL+E,將1改為0即可。

2.1.2 NtGlobalFlags

當程序處於除錯狀態時,PEB結構體偏移為0×68的NtGlobalFlag的值會被設定為0×70。所以和BeingDebugged成員檢查一樣,我們只需要檢查其值是否為0×70就能判斷程式是否被除錯。

 

image.png

破解方法:和修改BeingDebugged的值一樣,記憶體視窗CTRL+G輸入fs:[30]跳轉到PEB的地址,選中偏移0×68的地方,CTRL+E將其改為0即可。

image.png

 

2.1.3 NtQueryInformationProcess()

除了利用PEB查詢程序相關的除錯資訊外,還可以利用NtQueryInformationProcess()API來獲取其他各種與程序除錯相關的資訊。

 

image.png

其中第1個引數是要查詢的程序的控制代碼值,第2個引數是要查詢程序的哪些資訊,該引數是一個列舉型別的值,可以查詢的資訊如下:

 

image.png

第2個引數不同的值對應於第3個引數指向不同型別的結構體,比如第2個引數填0(ProcessBasicInformation),就表示你想獲取PEB的資訊,然後就可以判斷BeingDebugged,NtGlobalFlag的值了,和我們前面講到的一樣,但是這種實現方式稍顯複雜。

這節我們主要使用該函式查詢2個值:ProcessDebugPort、ProcessDebugObjectHandle,對應的列舉值為7和0x1E。

程序處於除錯狀態時,系統會為它分配一個除錯埠,第2個引數傳7時,NtQueryInformationProcess()就能獲取除錯埠的值,除錯狀態該值為0xFFFFFFFF,正常執行時該值為0。

 

同樣,有一個API也可以檢測除錯埠的值:CheckRemoteDebuggerPresent()。CTRL+G定位到該函式實現部分,發現該函式其實就是內部呼叫了一下第2個引數為7的NtQueryInformationProcess:(實際呼叫的Zw版的函式,功能相同)

image.png

程序被除錯時會生成除錯物件,當函式的第2個引數值為0x1E(ProcessDebugObjectHandle)時就能獲取除錯物件控制代碼,正常執行時該值為0,除錯執行時該值為非0值。

 

image.png

破解方法:如果被除錯程式只是呼叫幾次API,而不是在多個地方反覆呼叫,我們可以在呼叫的時候手動修改特定引數下的呼叫結果:修改返回值或函式引數指向的記憶體。像上面的例子一樣,我們可以在該函式呼叫結束的後手動修改引數指向的記憶體,修改呼叫結果。當函式呼叫傳參為7/0x1E的時候,修改其第3個引數獲取的值:

image.png

在call處下斷,命中斷點後,棧區右鍵,定位到第3個引數的緩衝區:

image.png

 

F8步過等函式呼叫完畢,修改第3個引數緩衝區內的值

image.png

CTRL+E,修改為0。

如果該函式在多個地方被反覆呼叫,那麼我們可以採取API HOOK的技術,修改函式內部執行程式碼的流程。

OD重新執行程式,反彙編區CTRL+G,輸入ZwQueryInformationProcess(實際呼叫的是該函式),跳轉到函式反彙編指令處。

image.png

 

第1條指令正好5個位元組,我們從此處HOOK。

ALT+M檢視程序的記憶體對映表,找到其程式碼區範圍:

image.png

 

程式碼起始地址:0×1281000,大小為0×5000。

回到反彙編視窗ALT+C:CTRL+G跳轉到0×1281000+0×4000的位置

image.png

 

這段空間是原程式程式碼區對齊用的,沒有實際的程式碼,我們在這裡實現我們的HOOK程式碼,將ZwQueryInformationProcess的第1條指令修改為jmp 1285000,執行HOOK程式碼,先判斷第2個引數是不是7或0x1E,是則把第3個引數指向的緩衝區填0,直接返回,不是則執行ZwQueryInformationProcess的第1條指令mov eax,0xEA,然後跳轉到ZwQueryInformationProcess第2條指令處繼續執行原函式功能。

 

image.png

ZwQueryInformationProcess函式的兩個MOV和後面的CALL RETN指令均可作為HOOK點,這裡選擇的第1條指令HOOK,對於這種反反除錯的手段,程式可以檢查API的第1個指令是不是原指令來判斷是不是有HOOK程式碼來繼續實現反除錯,和動態反除錯的API斷點相似,後面的動態反除錯會介紹怎麼應對HOOK檢測。

2.1.4 STARTUPINFO

程式正常啟動(雙擊啟動)時,實際是資源瀏覽器通過CreateProcess()函式建立程序啟動的:

 

image.pngexplorer啟動程式時,會把倒數第2個引數STARTUPINFO結構體中的值設定為0,但偵錯程式啟動程式的時候不會,所以我們可以通過判斷該結構體中的某些值是否為0來判斷是否被除錯:

 

image.png這裡我們選擇判斷結構體中視窗座標和大小這幾個值(也可以多選幾個)是否為0來實現反除錯檢測:

image.png  

破解方法:同NtQueryInformationProcess一樣,當函式呼叫次數較少時,可以直接修改函式呼叫結束後的引數值:

image.png

 

函式呼叫處下斷,命中斷點後,資料區定位到第1個引數指向的結構體地址,操作方法同2.1.3中修改NtQueryInfomationProcess呼叫結果相同。F8步過,CTRL+E修改結構體中對應的值:

image.png

當函式呼叫次數較多時-像NtQueryInformationProcess,我們採用的是API HOOK,那麼這個函式能否使用API HOOK技術呢?我們只是修改該結構體中的部分值,而不是全部值,所以修改結構體內容要發生在函式執行完畢,所以HOOK點要選擇函式將要返回時,一般這種情況是不容易HOOK的,反彙編區CTRL+G 輸入GetStartupInfoW,看下GetStartupInfoW的函式實現尾部:image.png

 

尾部POP EBP和RETN 4共4個位元組,不符合我們的要求,而且返回前還有JNZ跳轉另一個返回分支,不是我們期望的HOOK點。該函式實際需要填充引數指向的結構體的值,我們看下函式體中它填充的值來自哪裡:

 

image.png由彙編程式碼實現,我們可以發現,該函式實際是先定位到PEB中偏移為0×10的_RTL_USER_PROCESS_PARAMETERS結構體(參考2.1.1中的PEB資訊)地址處,然後獲取引數STARTUPINFO的地址,分別將兩個地址值賦值給ECX和EAX,以這兩個暫存器為偏移基址,分別獲取_RTL_USER_PROCESS_PARAMETERS結構體內的值,並賦值給STARTUPINFO結構體。所以,我們只需要將結構體_RTL_USER_PROCESS_PARAMETERS中的相應值置0即可實現類似API HOOK的效果。_RTL_USER_PROCESS_PARAMETERS結構體內容如下:(win7 32位SP1)

image.png  

STARTUPINFO的dwX、dwY、dwXSize、dwYSize分別對應該結構體中偏移為0x4C、0×50、0×54、0×58處的值,下面我們找到結構體_RTL_USER_PROCESS_PARAMETERS在記憶體中的位置:OD資料區CTRL+G輸入FS:[0x30]定位到PEB地址,找到偏移0×10處的_RTL_USER_PROCESS_PARAMETERS地址:image.png

 

0x4E1298就是我們要找的地址,資料視窗CTRL+G跳轉到該地址處:image.png

 

為方便我們找到那幾個偏移值,資料視窗右鍵把資料以地址方式顯示:image.pngimage.png

 

找到對應的偏移處的值,CTRL+E將其置為0即可。我們這裡只比較了4個值,你也可以把結構體中其他的值置0。

2.1.5 SedebugPrivilege

一般程式正常啟動時是不具備除錯許可權(SedebugPrivilege)的,除非自己有提權的需要主動開啟,但是偵錯程式啟動程式的時候,由於偵錯程式本身會開啟除錯許可權,所以被除錯程序會繼承除錯許可權,因此我們可以通過檢查程序是否具有除錯許可權來進行反除錯。

檢查程序是否具有除錯許可權的方式很簡單,系統啟動的時候會啟動一個核心程序csrss.exe,我們可以通過判斷能否使用OpenProcess開啟該程序來檢查當前程序是否具有除錯許可權,因為只有擁有管理員許可權+除錯許可權的程序才能開啟csrss.exe的控制代碼。嚴格來說這種檢查方法是不太嚴格的,因為當程序有除錯許可權無管理員許可權的時候也不能開啟csrss.exe的控制代碼,幸運的是,大多數偵錯程式都會要求提供管理員許可權,所以被除錯程式也會同時擁有管理員許可權+除錯許可權。

通過CsrGetProcessId函式可以獲取csrss.exe的PID,然後通過OpenProcess嘗試開啟csrss.exe的控制代碼:

image.png  

破解方法:在OpenProcess函式呼叫上或函式起始地址下斷,判斷PID的值是不是csrss.exe的PID,注意:有兩個csrss.exe的程序,通過工作管理員可以檢視其PID的值,不要只關心一個PID。如果是這兩個中的一個,直接執行到函式返回,並修改EAX的值為0即可(EAX就是函式的返回值):如下圖所示(0×228 == 552)image.pngimage.pngimage.png

 

 

除了修改函式返回結果其實也可以直接修改查詢的字串。有的反除錯程式會使用遍歷程序的方式查詢csrss.exe的PID值,但最終還是會呼叫OpenProcess,破解方式與上面相同,這裡介紹另外一種破解方式:直接搜尋“csrss.exe”字串,將其緩衝區直接置0即可,這樣它就找不到csrss.exe程序了:

遍歷程序查詢csrss.exe:

 

image.png破解方式:ALT+M轉到記憶體視窗,右鍵search或快捷鍵ctrl+B:image.png

 

在unicode欄輸入csrss(當然也有可能是asc字串,示例程式碼用的是unicode)image.png

 

成功找到csrss.exe的字串地址,ctrl+L繼續找還有沒其他地址,發現只有這一處,CTRL+E將其置0即可。image.png

2.2  除錯環境檢測

在2.1中介紹了各種程序本身狀態檢測的反除錯技術,其原理是除錯狀態下的程序和正常執行時程序本身不同,這些檢查是固定不變的,當所有的檢查項都被我們偽裝通過了之後,這些反除錯手段就失效了。本節將介紹針對除錯環境檢測的反除錯手段及反反除錯手段。安全研究員要除錯程式,他的工作環境和正常的工作環境是不同的,像各種分析工具、虛擬機器環境等等,這些工作環境的檢測就成為除程序檢測外的另一種反除錯手段。這時候不管程序有沒有被除錯,只要發現程式所處環境不正常,就認為自己被除錯分析了.

2.2.1  登錄檔檢測

當程式執行發生錯誤的時候,會有一個錯誤彈框,讓使用者選擇關閉程式還是除錯程式:image.png

 

正常使用者只能選擇關閉程式,因為他沒有偵錯程式,對於安全研究人員則不同,他們的工作環境上搭配有對應的偵錯程式,當程式崩潰時,他們可以選擇設定好的JIT偵錯程式除錯程式。以OD舉例,將OD設定為JIT偵錯程式:Options->Just-in-time debugging:image.png

一旦設定了JIT偵錯程式,系統就會更改登錄檔項中對應JIT的值:image.png

 

這個登錄檔的位置為:

 

image.png我們可以通過查詢該登錄檔項對應的值是否包含常用偵錯程式的字串來檢測偵錯程式:

image.png  

破解方法:搜尋字串AeDebug,步驟和2.1.5相同,只是這裡會搜到兩個結果(一個32位路徑,一個64位路徑),將其全部置0即可。image.png

 

2.2.2  視窗檢測

視窗檢測即通過查詢當前系統中執行的程式視窗名稱是否包含敏感程式來進行反除錯。常用的函式有FindWindow、EnumWindows。FindWindow可以查詢符合指定類名或視窗名的視窗控制代碼,EnumWindows可以列舉所有頂層視窗的控制代碼值,並傳給回撥函式。

image.png  

破解方法:搜尋字串OllyDbg或OLLYDBG(你使用的偵錯程式的類名或視窗名),將其置0即可。image.png

 

2.2.3 父程序檢測

程式正常啟動(雙擊)時,其父程序為exeplorer.exe,除錯啟動時其父程序為偵錯程式,所以檢查其父程序是否為explorer.exe也可以作為一個反除錯手段。但是這種檢測方式容易誤判,因為程式也可能是由其他正常程序通過CreateProcess正常啟動。NtQueryInformationProcess獲取當前程序的父程序PID,通過遍歷程序來獲取該PID對應的程序名是否explorer.exe來實現反除錯。這裡還有另一種選擇:獲取exeplorer.exe的PID和當前程序的父程序PID比較,但是這種檢測遇到一種情況會失效-系統啟動兩個explorer.exe,這時候會有兩個explorer的PID。所以示例程式碼只演示比較程序名而不是比較PID。

image.png  

 

 

 

破解方法:除了字串搜尋法外,可以選擇把自己的偵錯程式exe檔案改成explorer.exe再執行,這樣檢測父程序名稱時偵錯程式的名稱也變成explorer.exe了,另一種方法就是修改_wcsicmp/_stricmp的比較結果,讓其返回0:image.png

 

 

 

 

2.2.4  程序掃描

 

 

程序掃描是比父程序檢測更暴力的檢測方式:只要系統程序列表內有敏感程序,就執行反除錯手段。

 

 

image.png  

 

 

 

破解方法:最直接的還是更改偵錯程式名稱,隨便換個名字即可,另一種方法就是查詢字串”OllyDbg.exe”,將其置0即可。image.png

 

 

 

 

2.2.5  核心物件掃描

 

 

在2.1.3中我們介紹了查詢除錯物件控制代碼的方式實現反除錯,其實也可以通過查詢這個除錯物件來實現反除錯。當除錯會話建立時會建立一個“DebugObject”型別的核心物件,通過遍歷核心物件連結串列查詢是否包含該型別的核心物件即可實現反除錯:

 

 

image.png  

 

 

 

破解方法:查詢字串“DebugObject”,將其置0即可。image.png

 

 

 

 

2.2.6 除錯模式檢測

 

 

安全研究工作一般是在虛擬機器環境中進行的,當進行核心除錯時,虛擬機器系統會處於被除錯狀態,VirtualKD (http://virtualkd.sysprogs.org)可以輕鬆實現核心除錯。image.png

 

 

當系統處於被除錯狀態時,我們可以通過檢測該狀態來實現反除錯:

 

 

image.png  

 

 

 

破解方法:系統正常啟動,不開啟除錯模式,或開啟除錯模式但是不建立核心除錯會話。如果有核心除錯的需要,可以函式呼叫處下斷,修改函式執行結果,或HOOK API。 

 

 

2.2.7  偵錯程式攻擊

 

 

這裡說的偵錯程式攻擊並不是指對偵錯程式造成破壞,而是指讓偵錯程式無法除錯程式。比如ZwSetInformationThread()函式傳參ThreadHideFromDebugger時,如果程式正常執行,那麼該函式就相當於什麼都沒做,但如果程式處於被除錯狀態,那麼該函式就可以使當前執行緒(一般是主執行緒)脫離偵錯程式,使偵錯程式無法繼續接收該執行緒的除錯事件,效果就像是偵錯程式崩潰了一樣:

 

 

image.png  

 

 

 

破解方式:API HOOK,當ZwSetInformationThread第2個引數為17的時候,函式直接返回。HOOK步驟參考2.1.3,HOOK程式碼如下:

 

 

image.png  

 

 

 

另一種偵錯程式攻擊方式就是利用OD載入程序時的一個BUG,在PE檔案格式中,資料目錄表項的個數一般是0×10個,如果大於這個值,雙擊執行時,系統載入器會忽略這個值,但是OD載入程序時會認為PE檔案格式錯誤,拒絕載入。image.png

 

 

image.png其次,雙擊執行系統載入器載入程序區段時,採用的是區段資訊表中區段在虛擬記憶體中的大小和區段在檔案中大小的較小值,而OD則固定採用檔案大小,如果把區段在檔案中的大小值改的很大,則系統載入器可以正常執行程式,而OD則會認為PE檔案格式錯誤,拒絕載入。image.pngimage.png

 

 

 

 

 

 

破解方法:把資料目錄表改回0×10,RSize改成VSize為0×200的整數倍即可。

 

 

三、動態反除錯技術

 

 

在第二章中介紹了基於程序狀態和執行環境檢測的靜態反除錯技術,靜態反除錯技術其實是對整個程式的保護,反除錯手段相對“死板”,破解方式也比較簡單。其實程式開發者只是想對程式的關鍵演算法和核心資料進行保護、避免逆向分析。所以他們大多利用的是動態反除錯手段:除錯行為檢測。

 

 

動態反除錯的原理基於除錯行為本身,比如,除錯執行的程式比正常執行的程式執行速度慢,所以可以用時鐘檢測來進行反除錯;遇到異常時,偵錯程式會先接收異常事件而不是直接傳遞給程序本身,所以可以利用異常處理機制來實現反除錯;逆向分析核心程式碼時需要下斷點、單步跟蹤等,可以檢測斷點、單步等實現反除錯。

 

 

動態反除錯的主要目的是干擾偵錯程式,使其無法正常跟蹤程式的執行流程或加大其逆向分析難度。相比靜態反除錯,其隱蔽性較強,技術難度更高,破解難度也更大。

 

 

3.1   時鐘檢測

 

 

程式被除錯時什麼事都得向偵錯程式通知報備一下,就像是偵錯程式的提線木偶,偵錯程式還要和使用者互動,等待使用者命令再下發給程式,哪有完全自主來的爽。所以在偵錯程式中跟蹤程式程式碼比程式正常執行耗費的時間要多很多。時鐘檢測技術就是通過計算關鍵內容執行時間差異來判斷程序是否處於被除錯狀態。同時程式在虛擬機器中的執行速度也比正常速度慢,所以時鐘檢測技術一般也用於反虛擬機器/反模擬器技術。計算執行時差的方式一般有兩種:讀取CPU時鐘計數器、時間計數相關API。

 

 

3.1.1      CPU記數器

 

 

x86 CPU中存在一個名為TSC(Time Stamp Counter)的64位暫存器。CPU對每個時鐘週期計數,然後儲存到TSC。RDTSC是一條彙編指令,用來將TSC的值讀入EDX:EAX暫存器(有的程式也會使用RDTSCP彙編指令,用法相同)。

 

 

 

 

 

image.png破解方法:F9執行步過這段檢測程式碼,如果有單步除錯的需要則修改RDTSC的執行結果,把EAX,EDX置0或其他值,只要使兩次RDTSC結果相同即可。image.png

 

 

 

 

3.1.2      時間API

 

 

時間計數相關的API有很多,在時鐘檢測上RDTSC的效率和準確度相比這些API是最高的。但這些API也是一種反除錯手段,這些API有:QueryPerformanceCounter、GetTickCount、GetSystemTime、GetLocalTime等,具體參考MSDN(https://docs.microsoft.com/zh-cn/windows/desktop/SysInfo/time-functions)。

 

 

這些API的使用方式大同小異,這裡只介紹QueryPerformanceCounter的使用:

 

 

image.png  

 

 

 

破解方法:改變函式呼叫結果,使兩次獲取時間相同,注意:一般這些時間API也會用於其他用途,所以除非明確知道所有的該API呼叫都是用來反除錯,否則不要隨便HOOK。image.png

 

 

 

 

3.2        異常處理

 

 

異常常用於動態反除錯技術。正常執行的程序發生異常時,在SEH(Structured Exception Handling)機制的作用下,OS會接收異常,然後呼叫程序中註冊的SEH處理。但是,若程序正被偵錯程式除錯,那麼偵錯程式就會先於SEH接收處理。利用該特徵可判斷程序是正常執行還是除錯執行,然後根據不同的結果執行不同的操作,這就是利用異常處理機制不同的反除錯原理。

 

 

3.2.1 SEH

 

 

Windows中的一些典型異常如下:

 

 

https://msdn.microsoft.com/zh-tw/library/aa915076.aspx

 

image.png  

 

 

 

以INT3異常EXCEPTION_BREAKPOINT為例,當該異常觸發時,若程式處於正常執行狀態,則自動呼叫已經註冊過的SEH;若程式處於除錯執行狀態,則系統會停止執行程式將控制權轉給偵錯程式。反除錯程式一般會在SEH中更改程式的執行流程,若控制權交給偵錯程式,而偵錯程式又沒有執行SEH程式碼,程式流程就會走向未知。image.png

 

 

破解方法:OD除錯選項中,設定忽略指定異常並傳遞給程式即可,意思就是這個異常不是OD設定的,是程式自己觸發的,OD不處理,程式自己處理吧。分析明白異常處理邏輯後,找到異常處理更改後(如果異常處理程式更改了)的EIP,繼續分析。image.png

 

 

 

 

這裡只是用INT3斷點舉例,要想主動觸發其他型別的異常可以使用RaiseException()函式。這種反除錯手段看似有點雞肋,其實它的主要目的是在異常處理中改變程式的執行流程,增加逆向分析難度,除非把異常處理程式碼分析明白,否則就搞不懂異常處理完畢程式的新EIP在哪,從何處繼續分析。而且這種把忽略異常把異常交給程式自己處理的方法並不是萬能的,後面的INT 2D會“教OD做人”。

 

 

3.2.2  SetUnhandledExceptionFilter()

 

 

主動觸發的異常可以被偵錯程式交給程式自己處理,那麼當SEH不存在,或SEH處理不了該異常的時候怎麼辦呢?這時候系統會呼叫UnhandledExceptionFilter()函式,該函式會檢查程序是否處於除錯狀態,若是,就把異常傳遞給偵錯程式,否則就彈個錯誤對話方塊,然後結束程式:image.png

 

 

 

 

SetUnhandledExceptionFilter()可以新增一個異常處理函式來替換彈框行為,沒有偵錯程式的時候就會把異常傳遞給該異常處理函式去處理:image.png

 

 

 

 

 

破解方法:首先像3.2.1一樣要讓OD把這些程式主動觸發的異常忽略並交給程式處理,然後異常處理流程就走到UnhandledExceptionFilter,因為UnhandledExceptionFilter檢測到沒有偵錯程式就會把異常交給SetUnhandledExceptionFilter註冊的函式,而它檢查有無偵錯程式的方式就是2.1.3中提到的NtQueryInformationProcess函式第2個引數傳7(ProcessDebugPort),查詢除錯埠。後面的處理方式就和2.1.3中一樣,把NtQueryInformationProcess()函式 HOOK掉即可。

 

 

3.2.3 INT 2D

 

 

INT 2D是一個特殊的指令,原為核心模式中用來觸發斷點異常的,也可以在使用者模式下正常執行時觸發異常。但程式在OD中除錯執行時有以下特點:

 

①   不會觸發異常,只是忽略

②   INT 2D的下一條指令的第一個位元組會被忽略。

③   F7/F8單步命令跟蹤INT 2D時,程式不會停在下條指令開始的地方,而是一直執行,直到遇到斷點。

對於OD來說,忽略異常設定也沒用,因為OD並沒有把INT 2D當作異常,而是直接忽略。

 

 

image.png  

 

 

 

破解方式:因為OD直接忽略INT 2D異常,所以設定忽略異常傳遞給程式自身處理無效,那麼我們直接把INT 2D改變成OD不會忽略的異常即可,比如把它改成INT3異常。這樣OD就能把異常正常傳遞給異常處理程式了。

 

 

3.3 0xCC探測

 

 

在程式除錯過程中,我們一般會設定許多軟體斷點。軟體斷點的原理其實就是偵錯程式在對應設定斷點的位置上修改該地址的位元組為0xCC,0xCC就是x86指令中的INT 3。若是關鍵位置檢測到該指令,即可判斷程序處於除錯狀態。檢測時要注意不是所有的位置都可以,因為0xCC既可以是INT 3指令,也可以是其他指令的運算元。基於這種原理的反除錯技術稱為“0xCC探測技術”。

 

 

3.3.1 API斷點

 

 

在2.1.3中我們介紹了API HOOK,其中提到HOOK程式碼可以通過檢查HOOK點是否原API指令來檢測HOOK實現反除錯。API斷點的檢測原理和檢測HOOK程式碼相同,API斷點一般下在API的首地址處或函式返回地址處。所以檢測這些易被下斷的地址首位元組是否為0xCC即可判斷程式是否正在被除錯。

 

 

image.png  

 

 

 

破解方式:把斷點下在函式中間,或使用硬體斷點下斷。

 

 

3.3.2 校驗和

 

 

為防止除錯時把斷點下在函式中間,反除錯程式除探測0xCC外,通常採用比較特殊程式碼區域(易被下斷點的區域)的校驗和的值。這樣,在該程式碼區域內除錯時,只要偵錯程式在該區域內設定一些0xCC斷點,如此一來,新的校驗值就和原來的不一樣了,這樣就能判斷出程序是否被除錯了。

 

 

 

 

 

image.png破解方法:遇到校驗和檢測的時候,首選F9執行過被檢測程式碼區,需要單步分析的時候則使用F7單步,需要步過使用硬體斷點步過。也可以通過下記憶體訪問斷點,找到計算校驗和程式碼的地方,改變比較跳轉。image.pngimage.pngimage.png

 

 

 

 

 

 

 

 

3.4  硬體斷點檢測

 

 

在3.3中我們知道0xCC斷點容易被檢測到,這時需要硬體斷點來臨時過渡。硬體斷點的實現實際依賴於幾個除錯暫存器:Dr0~Dr7。Dr0~Dr3儲存硬體斷點的地址,Dr4、Dr5保留,Dr6、Dr7用於說明哪個硬體斷點觸發的相關屬性。所以同時最多能設定4個硬體斷點。只要檢查Dr0~Dr4這幾個暫存器的值是否為0就知道有沒有沒下硬體斷點、有幾個硬體斷點。查詢除錯暫存器的方式一般有兩種:API直接查詢暫存器的值、主動觸發異常查詢暫存器的值。

 

 

3.4.1  API直接查詢

 

 

直接API查詢暫存器的值:image.png

 

 

 

 

 

破解方式:修改引數中context.ContextFlags的值,或HOOK GetThreadContext的引數值,把context.ContextFlags的值改為CONTEXT_INTEGER(0×10002)值,不讓該函式查詢除錯暫存器的值即可。image.png

 

 

 

 

或HOOK GetThreadContext,HOOK API首地址:

 

 

image.png  

 

 

 

注意,GetThreadContext會被系統函式呼叫,所以一般不要HOOK該函式,或判斷呼叫是否來自使用者程式碼再決定是否執行HOOK程式碼。

 

 

3.4.2  異常間接查詢

 

 

主動觸發異常檢查暫存器的值:image.png

 

 

 

 

 

破解方法:這種反除錯技術沒有直接呼叫API而是呼叫SEH根據異常CONTEXT查詢暫存器的值不容易被發現,特別是這個異常處理函式需要OD主動忽略異常才能傳遞給程式處理,這時候如果逆向分析人員沒有追蹤分析異常處理函式,就很容易“被反除錯”。這種反除錯就只能跟蹤異常處理函式本身,然後在查詢除錯暫存器的地方改變其查詢比較結果即可。

 

 

3.5 單步檢測

 

 

逆向分析人員在分析關鍵程式碼區的時候除了在API下斷,還需要不斷的單步分析,OD的F7單步步入和F8單步步過的原理就是設定單步異常或0xCC斷點。反除錯程式可以針對這一點設定陷阱,檢測TF或0xCC實現反除錯。

 

 

3.5.1  rep/call步過

 

 

當遇到call指令或rep指令的時候,逆向人員經常會採用F8步過的方式快速步過指令執行序列。這時候,call指令或rep指令的下一條指令起始位元組就會被設定一個軟體斷點(0xCC)。反除錯程式可以在執行call指令或rep指令的時候檢測下條指令是不是被下0xCC斷點或直接把下條指令改成NOP指令使0xCC斷點失效來實現反除錯:

 

 

使0xCC斷點失效原理:image.png

 

 

當12F207B遇到rep或call習慣性F8步過的時候,12F207D的0x8B就會變成0xCC(INT3),這時rep指令或call指令會把12F207D重新恢復成原指令位元組8B。這樣rep或call步過後,本應斷下的0xCC斷點並不會斷下,偵錯程式沒有重新得到控制權,程式繼續執行,又得重新分析。當然這種反除錯手段麻煩的一點在於重新把原位元組(0x8B)寫回去需要把程式碼段屬性修改為可寫。不過這種反除錯手段一般在惡意程式碼中比較常見,而惡意程式碼經常申請堆記憶體執行惡意行為,而申請的堆記憶體時的屬性若是可讀可寫可執行的,就不存在這個麻煩。

 

 

檢測是否存在因除錯步過而設定的0xCC斷點:image.png

 

 

 

 

 

這種檢測rep/call步過的0xCC斷點的反除錯技術可以應用在任何地方。這裡的例子只是為了說明其原理,實際應用實可以有多種變化。

 

 

破解方法:碰到rep或call的時候要多注意esi/edi這些敏感暫存器實際指向的地址,而且這種步過檢測會有讀取/寫入程式碼區位元組的行為,逆向分析遇到程式碼區有讀取/寫入行為的時候要多使用記憶體斷點和硬體斷點,儘量不用軟體斷點。

 

 

3.5.2 TF檢測

 

 

當EFLAGS的TF標誌位被置1時,CPU將進入單步執行模式。單步執行模式中,CPU執行1條指令後即觸發一個EXCEPTION_SINGLE_STEP異常,然後TF會自動清零。TF檢測一般有兩種和方式,第1種:主動觸發TF異常、與SEH結合使用探測偵錯程式。image.png

 

 

 

 

 

破解方法:這種先pushfd修改後再popfd的方式是最常見的TF標誌反除錯手段,破解方法和3.2中異常處理一樣,OD除錯設定項就可以解決,不過需要仔細分析異常處理函式過程。image.png

 

 

 

 

第二種檢測TF的方式相反,不主動設定TF標記位,檢測是否關鍵區有程式碼被單步除錯:檢測偵錯程式是否在設定TF標記位。檢測原理是:image.png

 

 

 

 

 

但前面已經說過,當F7單步執行pushfd時,TF標誌位會被自動清零,所以後面的test結果永遠為0。這時候需要用到一條特殊的指令:pop ss。pop ss會將異常和中斷掛起,直到下一條指令執行完畢:image.png

 

 

 

 

 

單步執行pop ss時,TF異常會被掛起,執行完下一條指令才會處理TF異常,所以程式不會停在pushfd這條指令處,而是停在test指令處。image.png

 

 

破解方法:直接執行跳過這段程式碼,或修改test比較結果。image.png

 

 

3.6自除錯

 

 

同一個程序不允許同時被兩個偵錯程式除錯,利用這一點可以自己先除錯執行自己,防止被另一個偵錯程式繼續除錯。一般這種反除錯程式會建立一個用於同步的核心物件,用第1次開啟的程序主動第2次開啟程序,並除錯第2個程序。

 

 

3.6.1 CreateProcess

 

 

程序第1次執行時會嘗試訪問同步核心物件,如果不存在,則說明當前程序第1次執行,建立一個核心物件,並以除錯方式建立程序開啟“自己”。這時若偵錯程式首次除錯執行程序則相當於在除錯一個偵錯程式,由於第2次執行的程序是被第1次執行的除錯開啟的,所以偵錯程式也無法繼續除錯第2次執行的程序。

 

 

image.png破解方式:

 

1、 除錯開啟程式時,修改OpenXXX等開啟同步物件的API返回值,使程式走正常流程。

2、 主動建立一個同名核心物件CreateXXX,使OpenXXX等開啟同步物件的操作成功,走向正常流程。

 

這裡只是簡單演示這種自除錯的原理,在實際逆向中,被逆向程式可能是個加密、壓縮、程式碼被偷取等等不完整的程序,需要在第2次被自身除錯開啟時在除錯迴圈中完成自修補。這樣的反除錯才是難對付的,大大增加了逆向分析難度。需要逆向分析人員程式的除錯迴圈進行跟蹤分析,把修補後的完整程式從記憶體中DUMP出來再逆向分析。

 

 

3.6.2  DebugActiveProcess

 

 

自除錯除上節講的CreateProcess()以除錯方式開啟程序外,還可以選擇正常建立自身,然後馬上附加建立程序的操作來實現。DebugActiveProcess()就可以做到這一點。image.png

 

 

破解方式:由於這時是先建立程序再馬上附加的方式實現自除錯,所以除3.5.1的破解方式外,我們還可以讓程式在建立自身程序後直接退出,不讓其附加建立的程序,由偵錯程式去附加。而面臨的問題和3.5.1相同,自除錯只是個手段,自修補才是核心,不讓它自除錯程式就不能補全自身,給逆向分析帶來麻煩。

 

 

四、總結

 

 

通過本文總結的這些反除錯技術可以看出,靜態反除錯技術偏於“大格局”,像程序本身的一些標誌位查詢、除錯環境檢查等,動態反除錯偏於“小細節”,像異常處理、斷點檢測等。這些技術如果只單純應用其中的一種,其實是不難發現與破解的,但如果他們綜合利用,互相隱蔽,那就大大增加了發現和破解的難度。這些反除錯技術綜合來說還是“死的”,弄明白原理就不難破解,所以現在的優秀反除錯技術已經不侷限於用這些“死板的”技術去調戲逆向分析人員了,他們開始使用像程式碼混淆、花指令、VMP等手段在精神上給逆向分析人員造成魔法傷害,而這種反除錯手段的破解方式就是:老實逆向分析,同樣的加花、混淆、VMP保護手段分析出原理了,以後就能增加魔抗、快速破解反除錯手段了。所以,終級的反反除錯手段還是增加自己的基本功-“他強任他強 清風拂山崗”。

 

 

參考資料:

 

 

1.《逆向工程核心原理》

 

2. The “Ultimate” Anti-debugging Reference

 

3. 反除錯技術總結

 

4. An Anti-Reverse EngineeringGuide

 

5. Anti Debugging ProtectionTechniques With Examples

 

6. Anti-debugging Techniques CheatSheet

 

7. OpenRCE

*本文作者:alphalab,轉載請註明來自FreeBuf