一、反調(diào)試技術(shù)
反調(diào)試技術(shù)是一種常見的反檢測技術(shù),因為惡意軟件總是企圖監(jiān)視自己的代碼以檢測是否自己正在被調(diào)試。為做到這一點,惡意軟件可以檢查自己代碼是否被設(shè)置了斷點,或者直接通過系統(tǒng)調(diào)用來檢測調(diào)試器。
1.斷點
為了檢測其代碼是否被設(shè)置斷點,惡意軟件可以查找指令操作碼0xcc(調(diào)試器會使用該指令在斷點處取得惡意軟件的控制權(quán)),它會引起一個SIGTRAP。如果惡意軟件代碼本身建立了一個單獨的處理程序的話,惡意軟件也可以設(shè)置偽斷點。用這種方法惡意軟件可以在被設(shè)置斷點的情況下繼續(xù)執(zhí)行其指令。
惡意軟件也可以設(shè)法覆蓋斷點,例如有的病毒采用了反向解密循環(huán)來覆蓋病毒中的斷點。相反,還有的病毒則使用漢明碼自我糾正自身的代碼。漢明碼使得程序可以檢測并修改錯誤,但是在這里卻使病毒能夠檢測并清除在它的代碼中的斷點。
2.計算校驗和
惡意軟件也可以計算自身的校驗和,如果校驗和發(fā)生變化,那么病毒會假定它正在被調(diào)試,并且其代碼內(nèi)部已被放置斷點。VAMPiRE是一款抗反調(diào)試工具,可用來逃避斷點的檢測。VaMPiRE通過在內(nèi)存中維護一張斷點表來達到目的,該表記錄已被設(shè)置的所有斷點。該程序由一個頁故障處理程序(PFH),一個通用保護故障處理程序(GPFH),一個單步處理程序和一個框架API組成。當一個斷點被觸發(fā)的時候,控制權(quán)要么傳給PFH(處理設(shè)置在代碼、數(shù)據(jù)或者內(nèi)存映射I/O中的斷點),要么傳給GPFH(處理遺留的I/O斷點)。單步處理程序用于存放斷點,使斷點可以多次使用。
3.檢測調(diào)試器
在Linux系統(tǒng)上檢測調(diào)試器有一個簡單的方法,只要調(diào)用Ptrace即可,因為對于一個特定的進程而言無法連續(xù)地調(diào)用Ptrace兩次以上。在Windows中,如果程序目前處于被調(diào)試狀態(tài)的話,系統(tǒng)調(diào)用isDebuggerPresent將返回1,否則返回0。這個系統(tǒng)調(diào)用簡單檢查一個標志位,當調(diào)試器正在運行時該標志位被置1。直接通過進程環(huán)境塊的第二個字節(jié)就可以完成這項檢查,以下代碼為大家展示的就是這種技術(shù):
mov eax, fs:[30h]
move eax, byte [eax+2]
test eax, eax
jne @DdebuggerDetected
在上面的代碼中,eax被設(shè)置為PEB(進程環(huán)境塊),然后訪問PEB的第二個字節(jié),并將該字節(jié)的內(nèi)容移入eax。通過查看eax是否為零,即可完成這項檢測。如果為零,則不存在調(diào)試器;否則,說明存在一個調(diào)試器。
如果某個進程為提前運行的調(diào)試器所創(chuàng)建的,那么系統(tǒng)就會給ntdll.dll中的堆操作例程設(shè)置某些標志,這些標志分別是FLG_HEAP_ENABLE_TAIL_CHECK、FLG_HEAP_ENABLE_FREE_CHECK和FLG_HEAP_VALIDATE_PARAMETERS。我們可以通過下列代碼來檢查這些標志:
mov eax, fs:[30h]
mov eax, [eax+68h]
and eax, 0x70
test eax, eax
jne @DebuggerDetected
在上面的代碼中,我們還是訪問PEB,然后通過將PEB的地址加上偏移量68h到達堆操作例程所使用的這些標志的起始位置,通過檢查這些標志就能知道是否存在調(diào)試器。
檢查堆頭部內(nèi)諸如ForceFlags之類的標志也能檢測是否有調(diào)試器在運行,如下所示:
mov eax, fs:[30h]
mov eax, [eax+18h] ;process heap
mov eax, [eax+10h] ;heap flags
test eax, eax
jne @DebuggerDetected
上面的代碼向我們展示了如何通過PEB的偏移量來訪問進程的堆及堆標志,通過檢查這些內(nèi)容,我們就能知道Force標志是否已經(jīng)被當前運行的調(diào)試器提前設(shè)置為1了。
另一種檢測調(diào)試器的方法是,使用NtQueryInformationProcess這個系統(tǒng)調(diào)用。我們可以將ProcessInformationClass設(shè)為7來調(diào)用該函數(shù),這樣會引用ProcessDebugPort,如果該進程正在被調(diào)試的話,該函數(shù)將返回-1。示例代碼如下所示。
push 0push 4push offset isdebuggedpush 7 ;ProcessDebugPortpush -1call NtQueryInformationProcesstest eax, eaxjne @ExitErrorcmp isdebugged, 0jne @DebuggerDetected
在本例中,首先把NtQueryInformationProcess的參數(shù)壓入堆棧。這些參數(shù)介紹如下:第一個是句柄(在本例中是0),第二個是進程信息的長度(在本例中為4字節(jié)),接下來是進程信息類別(在本例中是7,表示ProcessDebugPort),下一個是一個變量,用于返回是否存在調(diào)試器的信息。如果該值為非零值,那么說明該進程正運行在一個調(diào)試器下;否則,說明一切正常。最后一個參數(shù)是返回長度。使用這些參數(shù)調(diào)用NtQueryInformationProcess后的返回值位于isdebugged中。隨后測試該返回值是否為0即可。
另外,還有其他一些檢測調(diào)試器的方法,如檢查設(shè)備列表是否含有調(diào)試器的名稱,檢查是否存在用于調(diào)試器的注冊表鍵,以及通過掃描內(nèi)存以檢查其中是否含有調(diào)試器的代碼等。
另一種非常類似于EPO的方法是,通知PE加載器通過PE頭部中的線程局部存儲器(TLS)表項來引用程序的入口點。這會導致首先執(zhí)行TLS中的代碼,而不是先去讀取程序的入口點。因此,TLS在程序啟動就可以完成反調(diào)試所需檢測。從TLS啟動時,使得病毒得以能夠在調(diào)試器啟動之前就開始運行,因為一些調(diào)試器是在程序的主入口點處切入的。
4.探測單步執(zhí)行
惡意軟件還能夠通過檢查單步執(zhí)行來檢測調(diào)試器。要想檢測單步執(zhí)行的話,我們可以把一個值放進堆棧指針,然后看看這個值是否還在那里。如果該值在那里,這意味著,代碼正在被單步執(zhí)行。當調(diào)試器單步執(zhí)行一個進程時,當其取得控制時需要把某些指令壓入棧,并在執(zhí)行下一個指令之前將其出棧。所以,如果該值仍然在那里,就意味著其它正在運行的進程已經(jīng)在使用堆棧。下面的示例代碼展示了惡意軟件是如何通過堆棧狀態(tài)來檢測單步執(zhí)行的:
Mov bp,sp;選擇堆棧指針
Push ax ;將ax壓入堆棧
Pop ax ;從堆棧中選擇該值
Cmp word ptr [bp -2],ax ;跟堆棧中的值進行比較
Jne debug ;如果不同,說明發(fā)現(xiàn)了調(diào)試器。
如上面的注釋所述,一個值被壓入堆棧然后又被彈出。如果存在調(diào)試器,那么堆棧指針–2位置上的值就會跟剛才彈出堆棧的值有所不同,這時就可以采取適當?shù)男袆印?br>
5.在運行時中檢測速度衰減
通過觀察程序在運行時是否減速,惡意代碼也可以檢測出調(diào)試器。如果程序在運行時速度顯著放緩,那就很可能意味著代碼正在單步執(zhí)行。因此如果兩次調(diào)用的時間戳相差甚遠,那么惡意軟件就需要采取相應(yīng)的行動了。Linux跟蹤工具包LTTng/LTTV通過觀察減速問題來跟蹤病毒。當LTTng/LTTV追蹤程序時,它不需要在程序運行時添加斷點或者從事任何分析。此外,它還是用了一種無鎖的重入機制,這意味著它不會鎖定任何Linux內(nèi)核代碼,即使這些內(nèi)核代碼是被跟蹤的程序需要使用的部分也是如此,所以它不會導致被跟蹤的程序的減速和等待。
6.指令預(yù)取
如果惡意代碼篡改了指令序列中的下一條指令并且該新指令被執(zhí)行了的話,那么說明一個調(diào)試器正在運行。這是指令預(yù)取所致:如果該新指令被預(yù)取,就意味著進程的執(zhí)行過程中有其他程序的切入。否則,被預(yù)取和執(zhí)行的應(yīng)該是原來的指令。
7.自修改代碼
惡意軟件也可以讓其他代碼自行修改(自行修改其他代碼),這樣的一個例子是HDSpoof。這個惡意軟件首先啟動了一些異常處理例程,然后在運行過程中將其消除。這樣一來,如果發(fā)生任何故障的話,運行中的進程會拋出一個異常,這時病毒將終止運行。此外,它在運行期間有時還會通過清除或者添加異常處理例程來篡改異常處理例程。在下面是HDSpoof清除全部異常處理例程(默認異常處理例程除外)的代碼。
exception handlers before:
0x77f79bb8 ntdll.dll:executehandler2@20 + 0x003a0x0041adc9 hdspoof.exe+0x0001adc90x77e94809 __except_handler3
exception handlers after:
0x77e94809 __except_handler3
0x41b770: 8b44240c mov eax,dword ptr [esp+0xc]0x41b774: 33c9 xor ecx,ecx 0x41b776: 334804 xor ecx,dword ptr [eax+0x4]0x41b779: 334808 xor ecx,dword ptr [eax+0x8]0x41b77c: 33480c xor ecx,dword ptr [eax+0xc]0x41b77f: 334810 xor ecx,dword ptr [eax+0x10]0x41b782: 8b642408 mov esp,dword ptr [esp+0x8]0x41b786: 648f0500000000 pop dword ptr fs:[0x0]
下面是HDSpoof創(chuàng)建一個新的異常處理程序的代碼。
0x41f52b: add dword ptr [esp],0x9ca
0x41f532: push dword ptr [dword ptr fs:[0x0]
0x41f539: mov dword ptr fs:[0x0],esp
8.覆蓋調(diào)試程序信息
一些惡意軟件使用各種技術(shù)來覆蓋調(diào)試信息,這會導致調(diào)試器或者病毒本身的功能失常。通過鉤住中斷INT 1和INT 3(INT 3是調(diào)試器使用的操作碼0xCC),惡意軟件還可能致使調(diào)試器丟失其上下文。這對正常運行中的病毒來說毫無妨礙。另一種選擇是鉤住各種中斷,并調(diào)用另外的中斷來間接運行病毒代碼。
下面是Tequila 病毒用來鉤住INT 1的代碼:
new_interrupt_one:
push bp
mov bp,sp
cs cmp b[0a],1 ;masm mod. needed
je 0506 ;masm mod. needed
cmp w[bp+4],09b4
ja 050b ;masm mod. needed
push ax
push es
les ax,[bp+2]
cs mov w[09a0],ax ;masm mod. needed
cs mov w[09a2],es ;masm mod. needed
cs mov b[0a],1
pop es
pop ax
and w[bp+6],0feff
pop bp
iret
一般情況下,當沒有安裝調(diào)試器的時候,鉤子例程被設(shè)置為IRET。V2Px使用鉤子來解密帶有INT 1和INT 3的病毒體。在代碼運行期間,會不斷地用到INT 1和INT 3向量,有關(guān)計算是通過中斷向量表來完成的。