反病毒引擎设计全解(三)
2.虛擬機(jī)查毒
2.1虛擬機(jī)概論
近些年,虛擬機(jī),在反病毒界也被稱為通用解密器,已經(jīng)成為反病毒軟件中最引人注目的部分,盡管反病毒者對(duì)于它的運(yùn)用還遠(yuǎn)沒有達(dá)到一個(gè)完美的程度,但虛擬機(jī)以其諸如"病毒指令碼模擬器"和"Stryker"等多變的名稱為反病毒產(chǎn)品的市場(chǎng)銷售帶來了光明的前景。以下的討論將把我們帶入一個(gè)精彩的虛擬技術(shù)的世界中。
首先要談及的是虛擬機(jī)的概念和它與諸如Vmware(美國(guó)VMWARE公司生產(chǎn)的一款虛擬機(jī),它支持在WINNT/2000環(huán)境下運(yùn)行如Linux等其它操作系統(tǒng))和WIN9X下的VDM(DOS虛擬機(jī),它用來在32位保護(hù)模式環(huán)境中運(yùn)行16實(shí)模式代碼)的區(qū)別。其實(shí)這些虛擬機(jī)的設(shè)計(jì)思想是有淵源可尋的,早在上個(gè)世紀(jì)60年代IBM就開發(fā)了一套名為VM/370的操作系統(tǒng)。VM/370在不同的程序之間提供搶先式多任務(wù),作法是在單一實(shí)際的硬件上模式出多部虛擬機(jī)器。典型的VM/370會(huì)話,使用者坐在電纜連接的遠(yuǎn)程終端前,經(jīng)由控制程序的一個(gè)IPL命令,模擬真實(shí)機(jī)器的初始化程序裝載操作,于是 一套完整的操作系統(tǒng)被載入虛擬機(jī)器中,并開始為使用者著手創(chuàng)建一個(gè)會(huì)話。這套模擬系統(tǒng)是如此的完備,系統(tǒng)程序員甚至可以運(yùn)行它的一個(gè)虛擬副本,來對(duì)新版本進(jìn)行除錯(cuò)。Vmware與此非常相似,它作為原操作系統(tǒng)下的一個(gè)應(yīng)用程序可以為運(yùn)行于其上的目標(biāo)操作系統(tǒng)創(chuàng)建出一部虛擬的機(jī)器,目標(biāo)操作系統(tǒng)就象運(yùn)行在單獨(dú)一臺(tái)真正機(jī)器上,絲毫察覺不到自己處于Vmware的控制之下。當(dāng)在Vmware中按下電源鍵(Power On)時(shí),窗口里出現(xiàn)了機(jī)器自檢畫面,接著是操作系統(tǒng)的載入,一切都和真的一樣。而WIN9X為了讓多個(gè)程序共享CPU和其它硬件資源決定使用VMs(所有Win32應(yīng)用程序運(yùn)行在一部系統(tǒng)虛擬機(jī)上;而每個(gè)16位DOS程序擁有一部DOS虛擬機(jī))。VM是一個(gè)完全由軟件虛構(gòu)出來的東西,以和真實(shí)電腦完全相同的方式來回應(yīng)應(yīng)用程序所提出的需求。從某種角度來看,你可以將一部標(biāo)準(zhǔn)的PC的結(jié)構(gòu)視為一套API。這套API的元素包括硬件I/O系統(tǒng),和以中斷為基礎(chǔ)的BIOS和MS-DOS。WIN9X常常以它自己的軟件來代理這些傳統(tǒng)的API元素,以便能夠?qū)φ滟F的硬件多重發(fā)訊。在VM上運(yùn)行的應(yīng)用程序認(rèn)為自己獨(dú)占整個(gè)機(jī)器,它們相信自己是從真正的鍵盤和鼠標(biāo)獲得輸入,并從真正的屏幕上輸出。稍被加一點(diǎn)限制,它們甚至可以認(rèn)為自己完全擁有CPU和全部?jī)?nèi)存。實(shí)現(xiàn)虛擬技術(shù)關(guān)鍵在于軟件虛擬化和硬件虛擬化,下面簡(jiǎn)要介紹WIN9X下的DOS虛擬機(jī)的實(shí)現(xiàn)。
當(dāng)Windows移往保護(hù)模式后,保護(hù)模式程序無法直接調(diào)用實(shí)模式的MS-DOS處理例程,也不能直接調(diào)用實(shí)模式的BIOS。軟件虛擬化就是用來描述保護(hù)模式Windows部件是如何能夠和實(shí)模式MS-DOS和BIOS彼此互動(dòng)。軟件虛擬化要求操作系統(tǒng)能夠攔截企圖跨越保護(hù)模式和實(shí)模式邊界的調(diào)用,并且調(diào)整適當(dāng)?shù)膮?shù)寄存器后,改變CPU模式。WIN9X使用虛擬設(shè)備驅(qū)動(dòng)(VXD)攔截來自保護(hù)模式的中斷,通過實(shí)模式中斷向量表(IVT),將之轉(zhuǎn)換為實(shí)模式中斷調(diào)用。做為轉(zhuǎn)換的一部分,VXD必須使用置于保護(hù)模式擴(kuò)展內(nèi)存中的參數(shù),生成出適當(dāng)?shù)膮?shù),并將之放在實(shí)模式(V86)操作系統(tǒng)可以存取的地方。服務(wù)結(jié)束后,VXD在把結(jié)果交給擴(kuò)展內(nèi)存中保護(hù)模式調(diào)用端。16位DOS程序中大量的21H和13H中斷調(diào)用就此解決,但其中還存在不少直接端口I/O操作,這就需要引入硬件虛擬化來解決。虛擬硬件的出現(xiàn)是為了在硬件中斷請(qǐng)求線上產(chǎn)生中斷請(qǐng)求,為了回應(yīng)IN和OUT指令,改變特殊內(nèi)存映射位置等原因。硬件虛擬化依賴于Intel 80386+的幾個(gè)特性。其中一個(gè)是I/O許可掩碼,使操作系統(tǒng)可能誘捕(Trap)對(duì)任何一個(gè)端口的所有IN/OUT指令。另一個(gè)特性是:由硬件輔助的分頁(yè)機(jī)制,使操作系統(tǒng)能夠提供虛擬內(nèi)存,并攔截對(duì)內(nèi)存地址的存取操作,將Video RAM虛擬化是此很好的例證。最后一個(gè)必要的特性是CPU的虛擬8086(V86)模式 ,讓DOS程序象在實(shí)模式中那樣地執(zhí)行。
我們下面討論用于查毒的虛擬機(jī)并不是象某些人想象的:如Vmware一樣為待查可執(zhí)行程序創(chuàng)建一個(gè)虛擬的執(zhí)行環(huán)境,提供它可能用到的一切元素,包括硬盤,端口等,讓它在其上自由發(fā)揮,最后根據(jù)其行為來判定是否為病毒。當(dāng)然這是個(gè)不錯(cuò)的構(gòu)想,但考慮到其設(shè)計(jì)難度過大(需模擬元素過多且行為分析要借助人工智能理論),因而只能作為以后發(fā)展的方向。我設(shè)計(jì)的虛擬機(jī)嚴(yán)格的說不能稱之為虛擬機(jī)器,而叫做虛擬CPU,通用解密器等更為合適一些,但由于反病毒界習(xí)慣稱之為虛擬機(jī),所以在下面的討論中我還將延續(xù)這個(gè)名稱。查毒的虛擬機(jī)是一個(gè)軟件模擬的CPU,它可以象真正CPU一樣取指,譯碼,執(zhí)行,它可以模擬一段代碼在真正CPU上運(yùn)行得到的結(jié)果。給定一組機(jī)器碼序列,虛擬機(jī)會(huì)自動(dòng)從中取出第一條指令操作碼部分,判斷操作碼類型和尋址方式以確定該指令長(zhǎng)度,然后在相應(yīng)的函數(shù)中執(zhí)行該指令,并根據(jù)執(zhí)行后的結(jié)果確定下條指令的位置,如此循環(huán)反復(fù)直到某個(gè)特定情況發(fā)生以結(jié)束工作,這就是虛擬機(jī)的基本工作原理和簡(jiǎn)單流程。設(shè)計(jì)虛擬機(jī)查毒的目的是為了對(duì)付加密變形病毒,虛擬機(jī)首先從文件中確定并讀取病毒入口處代碼,然后以上述工作步驟解釋執(zhí)行病毒頭部的解密段(decryptor),最后在執(zhí)行完的結(jié)果(解密后的病毒體明文)中查找病毒的特征碼。這里所謂的“虛擬”,并非是創(chuàng)建了什么虛擬環(huán)境,而是指染毒文件并沒有實(shí)際執(zhí)行,只不過是虛擬機(jī)模擬了其真實(shí)執(zhí)行時(shí)的效果。這就是虛擬機(jī)查毒基本原理,具體介紹請(qǐng)參看后面的相關(guān)章節(jié)。
當(dāng)然,虛擬執(zhí)行技術(shù)使用范圍遠(yuǎn)不止自動(dòng)脫殼(虛擬機(jī)查毒實(shí)際上是自動(dòng)跟蹤病毒入口的解密子將加密的病毒體按其解密算法進(jìn)行解密),它還可以應(yīng)用在跨平臺(tái)高級(jí)語言解釋器,惡意代碼分析,調(diào)試器。如劉濤濤設(shè)計(jì)的國(guó)產(chǎn)調(diào)試器Trdos就是完全利用虛擬技術(shù)解釋執(zhí)行被調(diào)試程序的每條指令,這種調(diào)試器比較起傳統(tǒng)的斷點(diǎn)式調(diào)試器(Debug,Softice等)具有諸多優(yōu)勢(shì),如不易被被調(diào)試者察覺,斷點(diǎn)個(gè)數(shù)沒有限制等。
?
2.2加密變形病毒
前面提到過設(shè)計(jì)虛擬機(jī)查毒的目的是為了對(duì)付加密變形病毒。這一章就重點(diǎn)介紹加密變形技術(shù)。早期病毒沒有使用任何復(fù)雜的反檢測(cè)技術(shù),如果拿反匯編工具打開病毒體代碼看到的將是真正的機(jī)器碼。因而可以由病毒體內(nèi)某處一段機(jī)器代碼和此處距離病毒入口(注意不是文件頭)偏移值來唯一確定一種病毒。查毒時(shí)只需簡(jiǎn)單的確定病毒入口并在指定偏移處掃描特定代碼串。這種靜態(tài)掃描技術(shù)對(duì)付普通病毒是萬無一失的。
隨著病毒技術(shù)的發(fā)展,出現(xiàn)了一類加密病毒。這類病毒的特點(diǎn)是:其入口處具有解密子(decryptor),而病毒主體代碼被加了密。運(yùn)行時(shí)首先得到控制權(quán)的解密代碼將對(duì)病毒主體進(jìn)行循環(huán)解密,完成后將控制交給病毒主體運(yùn)行,病毒主體感染文件時(shí)會(huì)將解密子,用隨機(jī)密鑰加密過的病毒主體,和保存在病毒體內(nèi)或嵌入解密子中的密鑰一同寫入被感染文件。由于同一種病毒的不同傳染實(shí)例的病毒主體是用不同的密鑰進(jìn)行加密,因而不可能在其中找到唯一的一段代碼串和偏移來代表此病毒的特征,似乎靜態(tài)掃描技術(shù)對(duì)此即將失效。但仔細(xì)想想,不同傳染實(shí)例的解密子仍保持不變機(jī)器碼明文(從理論上講任何加密程序中都存在未加密的機(jī)器碼,否則程序無法執(zhí)行),所以將特征碼選于此處雖然會(huì)冒一定的誤報(bào)風(fēng)險(xiǎn)(解密子中代碼缺少病毒特性,同樣的特征碼也會(huì)出現(xiàn)在正常程序中),但仍不失為一種有效的方法。由于加密病毒還沒有能夠完全逃脫靜態(tài)特征碼掃描,所以病毒寫作者在加密病毒的基礎(chǔ)之上進(jìn)行改進(jìn),使解密子的代碼對(duì)不同傳染實(shí)例呈現(xiàn)出多樣性,這就出現(xiàn)了加密變形病毒。它和加密病毒非常類似,唯一的改進(jìn)在于病毒主體在感染不同文件會(huì)構(gòu)造出一個(gè)功能相同但代碼不同的解密子,也就是不同傳染實(shí)例的解密子具有相同的解密功能但代碼卻截然不同。比如原本一條指令完全可以拆成幾條來完成,中間可能會(huì)被插入無用的垃圾代碼。這樣,由于無法找到不變的特征碼,靜態(tài)掃描技術(shù)就徹底失效了。下面先舉兩個(gè)例子說明加密變形病毒解密子構(gòu)造,然后再討論怎樣用虛擬執(zhí)行技術(shù)檢測(cè)加密變形病毒。
著名多形病毒Marburg的變形解密子:
00401020: movsx edi,si ;病毒入口
00401023: movsx edx,bp
00401026: jmp 00408a99
......
00407400: ;病毒體入口
加密的病毒主體
00408a94: ;解密指針初始值
......
00408a99: mov dl,f7
00408a9b: movsx edx,bx
00408a9e: mov ecx,cf4b9b4f
00408aa3: call 00408ac4
......
00408ac4: pop ebx
00408ac5: jmp 00408ade
......
00408ade: mov cx,di
00408ae1: add ebx,9fdbd22d
00408ae7: jmp 00408b08
......
00408b08: add ecx,80c1fbc1
00408b0e: mov ebp,7fcdeff3 ;循環(huán)解密記數(shù)器初值
00408b13: sub cl,39
00408b16: movsx esi,si
00408b19: add dword ptr[ebx+60242dbf],9ef42073 ;解密語句,9ef42073是密鑰
00408b23: mov edx,6fd1d4cf
00408b28: mov di,dx
00408b2b: inc ebp
00408b2c: xor dl,a3
00408b2f: mov cx,si
00408b32: sub ebx,00000004 ;移動(dòng)解密偏移指針,逆向解密
00408b38: mov ecx,86425df9
00408b3d: cmp ebp,7fcdf599 ;判斷解密結(jié)束與否
00408b43: jnz 00408b16
00408b49: jmp 00408b62
......
00408b62: mov di,bp
00408b65: jmp 00407400 ;將控制權(quán)交給解密后的病毒體入口
?
著名多形病毒Hps的變形解密子:
005365b8: ;解密指針初始值和病毒體入口
加密的病毒主體
......
005379cd: call 005379e2
......
005379e2: pop ebx
005379e3: sub ebx,0000141a;設(shè)置解密指針初值
005379e9: ret
......
005379f0: dec edx ;減少循環(huán)記數(shù)值
005379f1: ret
......
00537a00: xor dword ptr[ebx],10e7ed59 ;解密語句,10e7ed59是密鑰
00537a06: ret
......
00537a1a: sub ebx,ffffffff
00537a20: sub ebx,fffffffd ;移動(dòng)解密指針,正向解密
00537a26: ret
......
00537a30: mov edx,74d9cb97 ;設(shè)置循環(huán)記數(shù)初值
00537a35: ret
......
00537a3f: call 005379cd ;病毒入口
00537a44: call 00537a30
00537a49: call 00537a00
00537a4e: call 00537a1a
00537a53: call 005379f0
00537a58: mov esi,edx
00537a5a: cmp esi,74d9c696 ;判斷解密結(jié)束與否
00537a60: jnz 00537a49
00537a66: jmp 005365b8 ;將控制權(quán)交給解密后的病毒體入口
以上的代碼看上去絕對(duì)不會(huì)是用編譯器編譯出來,或是編程者手工寫出來的,因?yàn)槠渲谐涑饬舜罅康膩y數(shù)和垃圾。代碼中沒有注釋部分均可認(rèn)為是垃圾代碼,有用部分完成的功能僅是循環(huán)向加密過的病毒體的每個(gè)雙字加上或異或一個(gè)固定值。這只是變形病毒傳染實(shí)例的其中一個(gè),別的實(shí)例的解密子和病毒體將不會(huì)如此,極度變形以至讓人無法辯識(shí)。至于變形病毒的實(shí)現(xiàn)技術(shù)由于涉及復(fù)雜的算法和控制,因此不在我們討論范圍內(nèi)。
這種加密變形病毒的檢測(cè)用傳統(tǒng)的靜態(tài)特征碼掃描技術(shù)顯然已經(jīng)不行了。為此我們采取的方法是動(dòng)態(tài)特征碼掃描技術(shù),所謂“動(dòng)態(tài)特征碼掃描”指先在虛擬機(jī)的配合下對(duì)病毒進(jìn)行解密,接著在解密后病毒體明文中尋找特征碼。我們知道解密后病毒體明文是穩(wěn)定不變的,只要能夠得到解密后的病毒體就可以使用特征碼掃描了。要得到病毒體明文首先必須利用虛擬機(jī)對(duì)病毒的解密子進(jìn)行解釋執(zhí)行,當(dāng)跟蹤并確定其循環(huán)解密完成或達(dá)到規(guī)定次數(shù)后,整個(gè)病毒體明文或部分已被保存到一個(gè)內(nèi)部緩沖區(qū)中了。虛擬機(jī)之所以又被稱為通用解密器在于它不用事先知道病毒體的加密算法,而是通過跟蹤病毒自身的解密過程來對(duì)其進(jìn)行解密。至于虛擬機(jī)怎樣解釋指令執(zhí)行,怎樣確定可執(zhí)行代碼有無循環(huán)解密段等細(xì)節(jié)將在下一節(jié)中介紹。
?
2.3虛擬機(jī)實(shí)現(xiàn)技術(shù)詳解
有了前面關(guān)于加密變形病毒的介紹,現(xiàn)在我們知道動(dòng)態(tài)特征碼掃描技術(shù)的關(guān)鍵就在于必須得到病毒體解密后的明文,而得到明文產(chǎn)生的時(shí)機(jī)就是病毒自身解密代碼解密的完畢。目前有兩種方法可以跟蹤控制病毒的每一步執(zhí)行,并能夠在病毒循環(huán)解密結(jié)束后從內(nèi)存中讀出病毒體明文。一種是單步和斷點(diǎn)跟蹤法,和目前一些程序調(diào)試器相類似;另一種方法當(dāng)然就是虛擬執(zhí)行法。下面分別分析單步和斷點(diǎn)跟蹤法和虛擬執(zhí)行法的技術(shù)細(xì)節(jié)。單步跟蹤和斷點(diǎn)是實(shí)現(xiàn)傳統(tǒng)調(diào)試器的最根本技術(shù)。單步的工作原理很簡(jiǎn)單:當(dāng)CPU在執(zhí)行一條指令之前會(huì)先檢查標(biāo)志寄存器,如果發(fā)現(xiàn)其中的陷阱標(biāo)志被設(shè)置則會(huì)在指令執(zhí)行結(jié)束后引發(fā)一個(gè)單步陷阱INT1H。至于斷點(diǎn)的設(shè)置有軟硬之分,軟件斷點(diǎn)是指調(diào)試器用一個(gè)通常是單字節(jié)的斷點(diǎn)指令(CC,即INT3H)替換掉欲觸發(fā)指令的首字節(jié),當(dāng)程序執(zhí)行至斷點(diǎn)指令處,默認(rèn)的調(diào)試異常處理代碼將被調(diào)用,此時(shí)保存在棧中的段/偏移地址就是斷點(diǎn)指令后一字節(jié)的地址;而硬件斷點(diǎn)的設(shè)置則利用了處理器本身的調(diào)試支持,在調(diào)試寄存器(DR0--DR4)中設(shè)置觸發(fā)指令的線形地址并設(shè)置調(diào)試控制寄存器(DR7)中相關(guān)的控制位,CPU會(huì)在預(yù)設(shè)指令執(zhí)行時(shí)自動(dòng)引發(fā)調(diào)試異常。而Windows本身又提供了一套調(diào)試API,使得調(diào)試跟蹤一個(gè)程序變得非常簡(jiǎn)單:調(diào)試器本身不用接掛默認(rèn)的調(diào)試異常處理代碼,而只須調(diào)用WaitForDebugEvent等待系統(tǒng)發(fā)來的調(diào)試事件;調(diào)試器可利用GetThreadContext掛起被調(diào)試線程獲取其上下文,并設(shè)置上下文中的標(biāo)志寄存器中的陷阱標(biāo)志位,最后通過SetThreadContext使設(shè)置生效來進(jìn)行單步調(diào)試;調(diào)試器還可通過調(diào)用兩個(gè)功能強(qiáng)大的調(diào)試API--ReadProcessMemory和WriteProcessMemory來向被調(diào)試線程的地址空間中注入斷點(diǎn)指令。根據(jù)我逆向后的分析結(jié)果,VC++的調(diào)試器就是直接利用這套調(diào)試API寫成的。使用以上的調(diào)試技術(shù)既然可以寫出像VC++那樣功能齊全的調(diào)試器,那么沒有理由不能將之運(yùn)用于病毒代碼的自動(dòng)解密上。最簡(jiǎn)單的做法:創(chuàng)建待查可執(zhí)行文件為調(diào)試器的調(diào)試子進(jìn)程,然后用上述方法對(duì)其進(jìn)行單步跟蹤,每當(dāng)收到具有EXCEPTION_SINGLE_STEP異常代碼的事件時(shí)就可以分析該條以單步模式執(zhí)行的指令,最后當(dāng)判斷病毒的整個(gè)解密過程結(jié)束后即可調(diào)用ReadProcessMemory讀出病毒體明文。用單步和斷點(diǎn)跟蹤法的唯一一點(diǎn)好處就在于它不用處理每條指令的執(zhí)行--這意味著它無需編寫大量的特定指令處理函數(shù),因?yàn)樗械慕饷艽a都交由CPU去執(zhí)行,調(diào)試器不過是在代碼被單步中斷的間隙得到控制權(quán)而已。但這種方法的缺點(diǎn)也是相當(dāng)明顯的:其一容易被病毒覺察到,病毒只須進(jìn)行簡(jiǎn)單的堆棧檢查,或直接調(diào)用IsDebugerPresent就可確定自己正處于被調(diào)試狀態(tài);其二由于沒有相應(yīng)的機(jī)器碼分析模塊,指令的譯碼,執(zhí)行完全依賴于CPU,所以將導(dǎo)致無法準(zhǔn)確地獲取指令執(zhí)行細(xì)節(jié)并對(duì)其進(jìn)行有效的控制。;其三單步和斷點(diǎn)跟蹤法要求待查可執(zhí)行文件真實(shí)執(zhí)行,即其將做為系統(tǒng)中一個(gè)真實(shí)的進(jìn)程在自己的地址空間中運(yùn)行,這當(dāng)然是病毒掃描所不能允許的。很顯然,單步和斷點(diǎn)跟蹤法可以應(yīng)用在調(diào)試器,自動(dòng)脫殼等方面,但對(duì)于查毒卻是不合適的。而使用虛擬執(zhí)行法的唯一一點(diǎn)缺點(diǎn)就在于它必須在內(nèi)部處理所有指令的執(zhí)行--這意味著它需要編寫大量的特定指令處理函數(shù)來模擬每種指令的執(zhí)行效果,這里根本不存在何時(shí)得到控制權(quán)的問題,因?yàn)榭刂茩?quán)將永遠(yuǎn)掌握在虛擬機(jī)手中。用軟件方法模擬CPU并非易事,需要對(duì)其機(jī)制有足夠的了解,否則模擬效果將與真實(shí)執(zhí)行相去甚遠(yuǎn)。舉兩個(gè)例子:一個(gè)是病毒常用的乘法后ASCII調(diào)整指令AAM,這條指令因?yàn)榇嬖谖垂_的行為從而常常被病毒用來考驗(yàn)虛擬機(jī)設(shè)計(jì)的優(yōu)劣。通常情況下AAM是雙字節(jié)指令,操作碼為D4 0A(其實(shí)0A隱含代表了操作數(shù)10);但也可作為單字節(jié)指令明確地指定第二字節(jié)除數(shù)為任意8位立即數(shù),此時(shí)操作碼僅為D4。虛擬機(jī)必需考慮到后一種指定除數(shù)的情況來保證模擬結(jié)果的正確性;還有一個(gè)例子是關(guān)于處理器響應(yīng)中斷的方式,即CPU在剛打開中斷后將不會(huì)馬上響應(yīng)中斷,而必須隔一個(gè)指令周期。如果虛擬機(jī)沒有考慮到該機(jī)制則很可能虛擬執(zhí)行流程會(huì)與真實(shí)情況不符。但虛擬執(zhí)行的優(yōu)點(diǎn)也是很明顯的,同時(shí)它正好填補(bǔ)了單步和斷點(diǎn)跟蹤法所力不能及的方面:首先是不可能被病毒覺察到,因?yàn)樘摂M機(jī)將在其內(nèi)部緩沖區(qū)中為被虛擬執(zhí)行代碼設(shè)立專用的堆棧,所以堆棧檢查結(jié)果與實(shí)際執(zhí)行無二(不會(huì)向堆棧中壓入單步和斷點(diǎn)中斷時(shí)的返回地址);其次由于虛擬機(jī)自身完成指令的解碼和地址的計(jì)算,所以能夠獲取每條指令的執(zhí)行細(xì)節(jié)并加以控制;最后,最為關(guān)鍵的一條在于虛擬執(zhí)行確實(shí)做到了“虛擬”執(zhí)行,系統(tǒng)中不會(huì)產(chǎn)生代表被執(zhí)行者的進(jìn)程,因?yàn)楸粓?zhí)行者的寄存器組和堆棧等執(zhí)行要素均在虛擬機(jī)內(nèi)部實(shí)現(xiàn),因而可以認(rèn)為它在虛擬機(jī)地址空間中執(zhí)行。鑒于虛擬執(zhí)行法諸多的優(yōu)點(diǎn),所以將其運(yùn)用于通用病毒體解密上是再好不過的了。
通常,虛擬機(jī)的設(shè)計(jì)方案可以采取以下三種之一:自含代碼虛擬機(jī)(SCCE),緩沖代碼虛擬機(jī)(BCE),有限代碼虛擬機(jī)(LCE)。
自含代碼虛擬機(jī)工作起來象一個(gè)真正的CPU。一條指令取自內(nèi)存,由SCCE解碼,并被傳送到相應(yīng)的模擬這條指令的例程,下一條指令則繼續(xù)這個(gè)循環(huán)。虛擬機(jī)會(huì)包含一個(gè)例程來對(duì)內(nèi)存/寄存器尋址操作數(shù)進(jìn)行解碼,然后還會(huì)包括一個(gè)用于模擬每個(gè)可能在CPU上執(zhí)行的指令的例程集。正如你所想到的,SCCE的代碼會(huì)變的無比的巨大而且速度也會(huì)很慢。然而SCCE對(duì)于一個(gè)先進(jìn)的反病毒軟件是很有用的。所有指令都在內(nèi)部被處理,虛擬機(jī)可以對(duì)每條指令的動(dòng)作做出非常詳細(xì)的報(bào)告,這些報(bào)告和啟發(fā)式數(shù)據(jù)以及通用清除模塊將相互參照形成一個(gè)有效的反毒系統(tǒng)。同時(shí),反病毒程序能夠最精確地控制內(nèi)存和端口的訪問,因?yàn)樗约禾幚淼刂返慕獯a和計(jì)算。
緩沖代碼虛擬機(jī)是SCCE的一個(gè)縮略版,因?yàn)橄鄬?duì)于SCCE它具有較小的尺寸和更快的執(zhí)行速度。在BCE中,一條指令是從內(nèi)存中取得的,并和一個(gè)特殊指令表相比較。如果不是特殊指令,則它被進(jìn)行簡(jiǎn)單的解碼以求得指令的長(zhǎng)度,隨后所有這樣的指令會(huì)被導(dǎo)入到一個(gè)可以通用地模擬所有非特殊指令的小過程中。而特殊指令,只占整個(gè)指令集的一小部分,則在特定的小處理程序中進(jìn)行模擬。BCE通過將所有非特殊指令用一個(gè)小的通用的處理程序模擬來減少它必須特殊處理的指令條數(shù),這樣一來它削減了自身的大小并提高了執(zhí)行速度。但這意味著它將不能真正限制對(duì)某個(gè)內(nèi)存區(qū)域,端口或其他類似東西的訪問,同時(shí)它也不可能生成如SCCE提供的同樣全面的報(bào)告。
有限代碼虛擬機(jī)有點(diǎn)象用于通用解密的虛擬系統(tǒng)所處的級(jí)別。LCE實(shí)際上并非一個(gè)虛擬機(jī),因?yàn)樗⒉徽嬲哪M指令,它只簡(jiǎn)單地跟蹤一段代碼的寄存器內(nèi)容,也許會(huì)提供一個(gè)小的被改動(dòng)的內(nèi)存地址表,或是調(diào)用過的中斷之類的東西。選擇使用LCE而非更大更復(fù)雜的系統(tǒng)的原因,在于即使只對(duì)極少數(shù)指令的支持便可以在解密原始加密病毒的路上走很遠(yuǎn),因?yàn)椴《緝H僅使用了INTEL指令集的一小部分來加密其主體。使用LCE,原本處理整個(gè)INTEL指令集時(shí)的大量花費(fèi)沒有了,帶來的是速度的巨大增長(zhǎng)。當(dāng)然,這是以不能處理復(fù)雜解密程序段為代價(jià)的。當(dāng)需要進(jìn)行快速文件掃描時(shí)LCE就變的有用起來,因?yàn)橐粋€(gè)小型但象樣的LCE可以用來快速檢查執(zhí)行文件的可疑行為,反之對(duì)每個(gè)文件都使用SCCE算法將會(huì)導(dǎo)致無法忍受的緩慢。當(dāng)然,如果一個(gè)文件看起來可疑,LCE還可以啟動(dòng)某個(gè)SCCE代碼對(duì)文件進(jìn)行全面檢查。
下面開始介紹32位自含代碼虛擬機(jī)w32encode(w32encode.cpp,Tw32asm.h,Tw32asm.cpp做為查毒引擎的一部分和其它搜索清除模塊聯(lián)編為Rsengine.dll)的程序結(jié)構(gòu)和流程。由于這是一個(gè)設(shè)計(jì)完備且復(fù)雜的大型商用虛擬機(jī),其中不可避免地包含了對(duì)某些特定病毒的特定處理,為了使虛擬機(jī)模型的結(jié)構(gòu)清晰脈絡(luò)分明,分析時(shí)我將做適當(dāng)?shù)暮?jiǎn)化。w32encode的工作原理很簡(jiǎn)單:它首先設(shè)置模擬寄存器組(用一個(gè)DWORD全局變量模擬真實(shí)CPU內(nèi)部的一個(gè)寄存器,如ENEAX)的初始值,初始化執(zhí)行堆棧指針(虛擬機(jī)用內(nèi)部的一個(gè)數(shù)組static int STACK[0x20]來模擬堆棧)。然后進(jìn)入一個(gè)循環(huán),解釋執(zhí)行指令緩沖區(qū)ProgBuffer中的頭256條指令,如果循環(huán)退出時(shí)仍未發(fā)現(xiàn)病毒的解密循環(huán)則可由此判定非加密變形病毒,若發(fā)現(xiàn)了解密循環(huán)則調(diào)用EncodeInst函數(shù)重復(fù)執(zhí)行循環(huán)解密過程,將病毒體明文解密到DataSeg1或DataSeg2中。相關(guān)部分代碼如下:
W32Encode0中總體流程控制部分代碼:
for (i=0;i<0x100;i++) //首先虛擬執(zhí)行256條指令試圖發(fā)現(xiàn)病毒循環(huán)解密子
{
if (InstLoc>=0x280) return(0);
if (InstLoc+ProgSeeKOFf>=ProgEndOff)
????????????? return(0); //以上兩條判斷語句檢查指令位置的合法性
saveinstloc(); //存儲(chǔ)當(dāng)前指令在指令緩沖區(qū)中的偏移
HasAddNewInst=0;
if (!(j=parse())) //虛擬執(zhí)行指令緩沖區(qū)中的一條指令
????????????? return(0); //遇到不認(rèn)識(shí)的指令時(shí)退出循環(huán)
if (j==2) //返回值為2說明發(fā)現(xiàn)了解密循環(huán)
???????? break;
}
if (i==0x100) //執(zhí)行過256條指令后仍未發(fā)現(xiàn)循環(huán)則退出
return(0);
PreParse=0;
ProcessInst();
if (!EncodeInst()) //調(diào)用解密函數(shù)重復(fù)執(zhí)行循環(huán)解密過程
return(0);
?
jmp中判定循環(huán)出現(xiàn)部分代碼:
if ((loc>=0)&&(loc<InstLoc)) //若轉(zhuǎn)移后指令指針小于當(dāng)前指令指針則可能出現(xiàn)循環(huán)
if (!isinstloc(loc)) //在保存的指令指針數(shù)組InstLocArray中查找轉(zhuǎn)移后指
...... //令指針值,如發(fā)現(xiàn)則可判定循環(huán)出現(xiàn)
Else
{
......
return(2); //返回值2代表發(fā)現(xiàn)了解密循環(huán)
}
parse中虛擬執(zhí)行每條指令的過程較復(fù)雜一些:通常parse會(huì)從取得指令緩沖區(qū)ProgBuffer中取得當(dāng)前指令的頭兩個(gè)字節(jié)(包括了全部操作碼)并根據(jù)它們的值調(diào)用相應(yīng)的指令處理函數(shù)。例如當(dāng)?shù)谝粋€(gè)字節(jié)等于0F并且第二個(gè)字節(jié)位與BE后等于BE時(shí),可判定此指令為movszx并同時(shí)調(diào)用movszx進(jìn)行處理。當(dāng)執(zhí)行進(jìn)入特定指令的處理函數(shù)中時(shí),首先要通過判斷尋址方式(調(diào)用modregrm或modregrm1)確定指令長(zhǎng)度并將控制權(quán)交給saveinst函數(shù)。saveinst在保存該指令的相關(guān)信息后會(huì)調(diào)用真正指令執(zhí)行函數(shù)W32ExecuteInst。這個(gè)函數(shù)和parse非常相似,它從SaveInstBuf1中取得當(dāng)前指令的頭兩個(gè)字節(jié)并根據(jù)它們的值調(diào)用相應(yīng)的指令模擬函數(shù)以完成一條指令的執(zhí)行。相關(guān)部分代碼如下:
W32ExecuteInst中指令分遣部分代碼:
if ((c&0xf0)==0x50)
{
if (ExecutePushPop1) //模擬push和pop
return(gotonext());
return(0);
}
if (c==0x9c)
{
if (ExecutePushf()) //模擬pushf
return(gotonext());
return(0);
}
if (c==(char)0x9d)
{
if (ExecutePopf()) //模擬popf
return(gotonext());
return(0);
}
if ((c==0xf)&&((c2&0xbe)==0xbe))
{
if (i=ExecuteMovszx(0)) //模擬movszx
return(gotonext());
return(0);
}
?
2.4虛擬機(jī)代碼剖析
總體流程控制和分遣部分的相關(guān)代碼,在上一章中都已分析過了。下面分析具體的特定指令模擬函數(shù),這才是虛擬機(jī)的精華之所在。我將指令分成不依賴標(biāo)志寄存器和依賴標(biāo)志寄存器兩大類分別介紹:
?
2.4.1不依賴標(biāo)志寄存器指令模擬函數(shù)的分析
push和pop指令的模擬:
static int ExecutePushPop1(int c)
{
if (c<=0x57)
{
if (StackP<0) //入棧前檢查堆棧緩沖指針的合法性
return(0);
???? }
else if (StackP>=0x40) //出棧前檢查堆棧緩沖指針的合法性
????? return(0);
if (c<=0x57)
{
????? StackP--;
????? ENESP-=4; //如果是入棧指令則在入棧前減少堆棧指針
}
Switch
{
????? case 0x50:STACK[StackP]=ENEAX; //模擬push eax
????? break;
????? ......
????? case 0x5f:ENEDI=STACK[StackP]; //模擬push edi
????? break;
}
if (c>=0x58)
{
????? StackP++;
????? ENESP+=4; //如果是出棧指令則在出棧后增加堆棧指針
}
return(1);
}
?
2.4.2依賴標(biāo)志寄存器指令模擬函數(shù)的分析
CW32Asm類中cmp指令的模擬:
void CW32Asm:: cmpw(int c1,int c2)
{
char FlgReg;
__asm
{
mov eax,c1 //取得第一個(gè)操作數(shù)
mov ecx,c2 //取得第二個(gè)操作數(shù)
cmp eax,ecx //比較
lahf //將比較后的標(biāo)志結(jié)果裝入ah
mov FlgReg,ah //保存結(jié)果在局部變量FlgReg中
}
FlagReg=FlgReg; //保存結(jié)果在全局變量FlagReg中
}
CW32Asm類中jnz指令的模擬:
int CW32Asm::JNE()
{
int i;
char FlgReg=FlagReg; //用保存的FlagReg初始化局部變量FlgReg
__asm
{
mov ah,FlgReg //設(shè)置ah為保存的模擬標(biāo)志寄存器值
pushf //保存虛擬機(jī)自身當(dāng)前標(biāo)志寄存器
sahf //將模擬標(biāo)志寄存器值裝入真實(shí)標(biāo)志寄存器中
mov eax,1
jne l //執(zhí)行jnz
popf //恢復(fù)虛擬機(jī)自身標(biāo)志寄存器
xor eax,eax
l:
popf //恢復(fù)虛擬機(jī)自身標(biāo)志寄存器
mov i,eax
}
return; //返回值為1代表需要跳轉(zhuǎn)
}
?
2.5反虛擬機(jī)技術(shù)
任何一個(gè)事物都不是盡善盡美,無懈可擊的,虛擬機(jī)也不例外。由于反虛擬執(zhí)行技術(shù)的出現(xiàn),使得虛擬機(jī)查毒受到了一定的挑戰(zhàn)。這里介紹幾個(gè)比較典型的反虛擬執(zhí)行技術(shù):
首先是插入特殊指令技術(shù),即在病毒的解密代碼部分人為插入諸如浮點(diǎn),3DNOW,MMX等特殊指令以達(dá)到反虛擬執(zhí)行的目的。盡管虛擬機(jī)使用軟件技術(shù)模擬真正CPU的工作過程,它畢竟不是真正的CPU,由于精力有限,虛擬機(jī)的編碼者可能實(shí)現(xiàn)對(duì)整個(gè)Intel指令集的支持,因而當(dāng)虛擬機(jī)遇到其不認(rèn)識(shí)的指令時(shí)將會(huì)立刻停止工作。但通過對(duì)這類病毒代碼的分析和統(tǒng)計(jì),我們發(fā)現(xiàn)通常這些特殊指令對(duì)于病毒的解密本身沒有發(fā)生任何影響,它們的插入僅僅是為了干擾虛擬機(jī)的工作,換句話說就是病毒根本不會(huì)利用這條隨機(jī)的垃圾指令的運(yùn)算結(jié)果。這樣一來,我們可以僅構(gòu)造一張所有特殊指令對(duì)應(yīng)于不同尋址方式的指令長(zhǎng)度表,而不必為每個(gè)特殊指令編寫一個(gè)專用的模擬函數(shù)。有了這張表后,當(dāng)虛擬機(jī)遇到不認(rèn)識(shí)的指令時(shí)可以用指令的操作碼索引表格以求得指令的長(zhǎng)度,然后將當(dāng)前模擬的指令指針(EIP)加上指令長(zhǎng)度來跳過這條垃圾指令。當(dāng)然,還有一個(gè)更為保險(xiǎn)的辦法那就是:得到指令長(zhǎng)度后,可以將這條我們不認(rèn)識(shí)的指令放到一個(gè)充滿空操作指令(NOP)的緩沖區(qū)中,接著我們將跳到緩沖區(qū)中去執(zhí)行,這等于讓真正的CPU幫我們來執(zhí)行這條指令,最后一步當(dāng)然是將執(zhí)行后真實(shí)寄存器中的結(jié)果放回我們的模擬寄存器中。這虛擬執(zhí)行和真實(shí)執(zhí)行參半方法的好處在于:即便在特殊指令對(duì)于病毒是有意義的,即病毒依賴其返回結(jié)果的情況下,虛擬機(jī)仍可保證虛擬執(zhí)行結(jié)果的正確。其次是結(jié)構(gòu)化異常處理技術(shù),即病毒的解密代碼首先設(shè)置自己的異常處理函數(shù),然后故意引發(fā)一個(gè)異常而使程序流程轉(zhuǎn)向預(yù)先設(shè)立的異常處理函數(shù)。這種流程轉(zhuǎn)移是CPU和操作系統(tǒng)相互配合的結(jié)果,并且在很大程度上,操作系統(tǒng)在其中起了很大的作用。由于目前的虛擬機(jī)僅僅模擬了沒有保護(hù)檢查的CPU的工作過程,而對(duì)于系統(tǒng)機(jī)制沒有進(jìn)行處理。所以面對(duì)引發(fā)異常的指令會(huì)有兩種結(jié)果:其一是某些設(shè)計(jì)有缺陷的虛擬機(jī)無法判斷被模擬指令的合法性,所以模擬這樣的指令將使虛擬機(jī)自身執(zhí)行非法操作而退出;其二虛擬機(jī)判斷出被模擬指令屬于非法指令,如試圖向只讀頁(yè)面寫入的指令,則立刻停止虛擬執(zhí)行。通常病毒使用該技術(shù)的目的在于將真正循環(huán)解密代碼放到異常處理函數(shù)后,如此虛擬機(jī)將在進(jìn)入異常處理函數(shù)前就停止了工作,從而使解密子有機(jī)會(huì)逃避虛擬執(zhí)行。因而一個(gè)好的虛擬機(jī)應(yīng)該具備發(fā)現(xiàn)和記錄病毒安裝異常過濾函數(shù)的操作并在其引發(fā)異常時(shí)自動(dòng)將控制轉(zhuǎn)向異常處理函數(shù)的能力。再次是入口點(diǎn)模糊(EPO)技術(shù),即病毒在不修改宿主原入口點(diǎn)的前提下,通過在宿主代碼體內(nèi)某處插入跳轉(zhuǎn)指令來使病毒獲得控制權(quán)。通過前面的分析,我們知道虛擬機(jī)掃描病毒時(shí)出于效率考慮不可能虛擬執(zhí)行待查文件的所有代碼,通常的做法是:掃描待查文件代碼入口,假如在規(guī)定步數(shù)中沒有發(fā)現(xiàn)解密循環(huán),則由此判定該文件沒有攜帶加密變形病毒。這種技術(shù)之所以能起到反虛擬執(zhí)行的作用在于它正好利用了虛擬機(jī)的這個(gè)假設(shè):由于病毒是從宿主執(zhí)行到一半時(shí)獲得控制權(quán)的,所以虛擬機(jī)首先解釋執(zhí)行的是宿主入口的正常程序,當(dāng)然在規(guī)定步數(shù)中不可能發(fā)現(xiàn)解密循環(huán),因而產(chǎn)生了漏報(bào)。如果虛擬機(jī)能增加規(guī)定步數(shù)的大小,則很有可能隨著病毒插入的跳轉(zhuǎn)指令跟蹤進(jìn)入病毒的解密子,但確定規(guī)定步數(shù)大小實(shí)在是件難事:太大則將無謂增加正常程序的檢測(cè)時(shí)間;太小則容易產(chǎn)生漏報(bào)。但我們對(duì)此也不必過于擔(dān)心,這類病毒由于其編寫技術(shù)難度較大所以為數(shù)不多。在沒有反匯編和虛擬執(zhí)行引擎的幫助下,病毒很難在宿主體內(nèi)定位一條完整指令的開始處來插入跳轉(zhuǎn),同時(shí)很難保證插入的跳轉(zhuǎn)指令的深度大于虛擬機(jī)的規(guī)定步數(shù),并且沒有把握插入的跳轉(zhuǎn)指令一定會(huì)被執(zhí)行到。
另外還有多線程技術(shù),即病毒在解密部分入口主線程中又啟動(dòng)了額外的工作線程,并且將真正的循環(huán)解密代碼放置于工作線程中運(yùn)行。由于多線程間切換調(diào)度由操作系統(tǒng)負(fù)責(zé)管理,所以我們的虛擬機(jī)只能在假定被執(zhí)行線程獨(dú)占處理器時(shí)間,即保證永遠(yuǎn)不被搶先,的前提下進(jìn)行。如此一來,虛擬機(jī)對(duì)于模擬啟用多線程工作的代碼將很難做到與真實(shí)效果一致。多線程和結(jié)構(gòu)化異常處理兩種技術(shù)都利用了特定的操作系統(tǒng)機(jī)制來達(dá)到反虛擬執(zhí)行的目的,所以在虛擬CPU中加入對(duì)特定操作系統(tǒng)機(jī)制的支持將是我們今后改進(jìn)的目標(biāo)。
最后是元多形技術(shù)(MetaPolymorphy),即病毒中并非是多形的解密子加加密的病毒體結(jié)構(gòu),而整體均采用變形技術(shù)。這種病毒整體都在變,沒有所謂“病毒體明文”。當(dāng)然,其編寫難度是很大的。如果說前幾種反虛擬機(jī)技術(shù)是利用了虛擬機(jī)設(shè)計(jì)上的缺陷,可以通過代碼改進(jìn)來彌補(bǔ)的話,那么這種元多形技術(shù)卻使虛擬機(jī)配合的動(dòng)態(tài)特征碼掃描法徹底失效了,我們必須尋求如行為分析等更先進(jìn)的方法來解決。? 與50位技術(shù)專家面對(duì)面20年技術(shù)見證,附贈(zèng)技術(shù)全景圖總結(jié)
以上是生活随笔為你收集整理的反病毒引擎设计全解(三)的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 反病毒引擎设计全解(二)
- 下一篇: 梳理商业模式