徐曉亭
(四川大學網(wǎng)絡空間安全學院,成都 610207)
鑒于當今惡意代碼爆炸性增長趨勢,國外安全廠商如卡巴斯基、VirusTotal等,和國內(nèi)安全廠商如騰訊、微步等均推出了自動化判別未知惡意代碼系統(tǒng)。API調(diào)用序列無疑是判別未知程序有無惡意行為的關鍵信息,其記錄的完整與否直接影響判定結果。
Windows API可分為用戶層和內(nèi)核層,一個用戶層API的調(diào)用將需要多個內(nèi)核層API的配合才能完成,因而用戶層API可以提供更加抽象的語義信息,相應的動態(tài)檢測誤報率和漏報率會更低。為了繞過動態(tài)檢測,攻擊者使用了API混淆技術來干擾用戶層行為信息收集過程。
API混淆技術指在進行靜態(tài)或動態(tài)分析惡意代碼時,獲取API地址對應的名稱失敗,從而可以隱藏它的真實意圖,增加分析難度。Kawakoya等人[1]提出最新的 API混淆方案 Stealth Loader是該方向的最新成果,可擊敗大多數(shù)靜態(tài)和動態(tài)反混淆技術。但本身也存在多種缺陷,如ntdll模塊初始化、多個DLL之間局部堆共享、消息回調(diào)函數(shù)注冊失敗等。程斌林等人[2]提出的BinUnpack屬于自動化脫殼方向的最新成果,可以檢測到Stealth Loader,跟蹤記錄加殼后的程序調(diào)用的API,實現(xiàn)反混淆。
本文聚焦堆共享問題,依靠API Hook的原理解決此問題,使得其支持更加廣泛的內(nèi)存管理函數(shù)。將基于空閑鏈表的內(nèi)存管理系統(tǒng)嵌入Stealth Loader中,刪除加殼后程序的IAT,成功逃脫Bi?nUnpack的檢測。
根據(jù)上述對API混淆的定義,與之相對的反混淆就是將API地址與名稱重新建立連接?,F(xiàn)有的反混淆技術大多都是基于以下兩個條件:
(1)IAT中的API地址可由DLL基址和DLL的EAT(Export Address Table,導出地址表)得出。
(2)DLL基址可通過Windows操作系統(tǒng)特定的數(shù)據(jù)結構獲得。
基于以上兩個條件,獲取API名稱的過程如算法1所示。
算法1獲取API名稱算法
其中A代表API地址,可由DLL基址與EAT獲得;N代表API名稱,可由DLL基址與ENT(Export Name Table,導出名稱表)獲得。dj代表一個API地址與其名稱構成的元組(Aj,Nj)。Di代表由組成的集合,象征一個DLL中所有API函數(shù)。D代表由Di組成的集合,代表當前進程所有DLL。
Kawakoya等人提出Stealth Loader混淆技術,實現(xiàn)一套獨立DLL管理系統(tǒng),可繞過現(xiàn)有的大多數(shù)API反混淆技術。
它主要由exPEB、sLdrLoadDll、sLdrGetProc Address和BootStrap四部分組成。exPEB負責管理由sLdrLoadDll加載的DLL。sLdrLoadDll負責將DLL以Reflective Loading方式映射到內(nèi)存中,并遞歸調(diào)用該函數(shù)與sLdrGetProcAddress函數(shù)修復DLL的IAT。sLdrGetProcAddress以API名稱為參數(shù),返回API地址。BootStrap負責解析程序所依賴的DLL,并調(diào)用sLdrLoadDll和sLdrGetProcAd?dress函數(shù)填充IAT。Stealth Loader還刪除了DLL映射到內(nèi)存中的DOS頭、INT表、ENT表和調(diào)試節(jié)等信息,防止反混淆工具以這些信息為線索識別加載的模塊。
程斌林等人[2]基于脫殼前后程序IAT地址不同的特性,提出Windows通用脫殼技術BinUn?pack,是自動化脫殼方向的最新研究成果。Bi?nUnpack利用Stealth Loader的API調(diào)用序列特征進行檢測,即首先調(diào)用CreateFile而后調(diào)用內(nèi)存分配API。但這種檢測方法也不是無法繞過的,本文將基于空閑鏈表的內(nèi)存管理系統(tǒng)與Stealth Loader相結合,并刪除加殼后程序的IAT,達到繞過Bi?nUnpack檢測的目的。
堆在系統(tǒng)安全領域是一種用于存儲動態(tài)數(shù)據(jù)的內(nèi)存空間。在Windows操作系統(tǒng)中,堆可分為私有堆(private heap)和局部堆(local heap)或稱默認堆(default heap)。局部堆在Windows加載可執(zhí)行程序時被創(chuàng)建,PE頭結構中的SizeOfHeapRe?serve和SizeOfHeapCommit字段分別指明了需要保留和提交的局部堆空間大小。
Windows中對局部堆的使用主要有兩種方式:一種是以Heap為前綴的通用堆管理函數(shù),另一種是專用的局部堆管理函數(shù),如LocalAlloc、Local?ReAlloc等。kernelbase動態(tài)鏈接庫中的全局變量BHHT(base heap handle table)負責管理由專用局部堆函數(shù)分配的堆。
Stealth Loader先將三個必要模塊重新加載到進程中,隨后加載程序依賴的其他模塊,修復模塊之間的依賴關系。但出現(xiàn)kernelbase模塊與其他模塊的BHHT變量指針不一致的問題,即ker?nelbase中的BHHT指針指向自身,其他模塊中的卻指向Windows加載的kernelbase。隨之而來的就是兩kernelbase中的BHHT變量內(nèi)容不相同,導致調(diào)用專用的局部堆管理函數(shù)時出現(xiàn)異常結果,如:LocalSize結果總是為0、GlobalReAlloc未成功擴展內(nèi)存大小和GlobalLock鎖定內(nèi)存失敗等,存在運行Crash的隱患。
API Hook是一種通過劫持原有API控制流,改變執(zhí)行結果的技術。雖然該技術實現(xiàn)方式存在很大差異,但原理都是修改控制流,使其經(jīng)過回調(diào)函數(shù)。
通過上文對該問題的分析,可知該問題的根源是由于兩kernelbase中的BHHT變量內(nèi)容不一致造成的。借助EAT Hook技術,替換寫入BHHT變量的導出API函數(shù)地址為回調(diào)函數(shù)地址。在回調(diào)函數(shù)中,先執(zhí)行原有的API函數(shù),隨后同步兩kernelbase中的BHHT變量的內(nèi)容。慶幸的是,與局部堆相關的Win32 API數(shù)量不是很多,且僅存在于kernelbase和kernel32兩模塊。借助靜態(tài)分析工具IDA逆向分析kernelbase和kernel32,得到引用BHHT全局變量相關的API,如表1所示。
表1 兩模塊中讀寫B(tài)HHT API
本小節(jié)將詳細闡述Stealth Loader的設計實現(xiàn)過程。由于Kawakoya并沒有公布相關源代碼和具體實現(xiàn)細節(jié),本文按照[1]中的框架重新實現(xiàn)。為了規(guī)避BinUnpack程序的檢測,采用了以下兩種方式:①實現(xiàn)基于空閑鏈表的內(nèi)存管理系統(tǒng),繞過其在API調(diào)用特征上的檢測。②根據(jù)[2]中7.1節(jié)所述,無法作用于無IAT的程序。因此,將加殼后程序的IAT置空,致使BinUnpack脫殼失敗。
3.1.1 修改PE頭內(nèi)存屬性
從PEB中獲取kernel32中基地址,進而依據(jù)PE格式得到VirtualProtect和VirtualAlloc兩個導出函數(shù)地址。VirtualProtect修改PE頭部的內(nèi)存屬性為可寫,利用PE文件格式磁盤對齊與內(nèi)存對齊的大小不同,可在PE頭尾部0x200處存放一些重要信息,如字符串、exPEB的地址和一些必要的Windows系統(tǒng)API地址等。
3.1.2 內(nèi)存分配
使用VirtualAlloc函數(shù)分配10 MB內(nèi)存,用于存放程序所需要的外部模塊和exPEB信息,并在此基礎上使用空閑鏈表對內(nèi)存進行管理。exPEB是由一個個Module結構體組成的,代表程序所依賴的外部模塊,結構體的成員如表2所示。圖1(a)是內(nèi)存管理頭部結構體,主要用于記錄內(nèi)存塊的各種信息。內(nèi)存分配算法如圖1(b)所示,搜索空閑鏈表中的內(nèi)存塊,返回給用戶。如果空閑塊大于所需,需要進行拆分,并將剩余的內(nèi)存鏈接至空閑鏈表中。內(nèi)存回收算法如圖1(c)所示,釋放內(nèi)存時為避免內(nèi)存碎片過多,會進行適當?shù)暮喜⒉僮鳌?/p>
圖1 內(nèi)存管理算法
表2 Module結構體
3.1.3 解析外部模塊
Windows系統(tǒng)下的每個進程都需要依賴ntdll、kernelbase和kernel32模塊,因此首先將這些模塊加載到內(nèi)存中。保存表1中需要被Hook的原函數(shù)地址至內(nèi)存鏡像PE頭0x200處,修改這些函數(shù)在Module結構體function數(shù)組中的值為回調(diào)函數(shù)地址。本文將function數(shù)組作為模塊的EAT,替換其中的函數(shù)地址。對于kernelbase的回調(diào)函數(shù)來說,先執(zhí)行對應的原函數(shù),后將BHHT同步至Win?dows系統(tǒng)下的BHHT。對于kernel32的回調(diào)函數(shù)來說,也是先調(diào)用原函數(shù),不同的是從Windows系統(tǒng)同步BHHT至Stealth Loader下。
根據(jù)PE文件格式,逐步尋找程序所依賴的外部函數(shù)和模塊。調(diào)用sLdrLoadDll函數(shù)加載這些模塊,sLdrGetProcAddress獲取依賴函數(shù)的地址填充至模塊的IAT中。
上述步驟已經(jīng)將程序所有依賴外部函數(shù)填充至程序IAT中,最后跳轉原始程序OEP(original entry point),執(zhí)行真正的代碼。
3.1.4 sLdrGetProcAddress和sLdrLoadDll函數(shù)
sLdrLoadDll函數(shù)按照Windows模塊加載的順序來進行搜索,以Reflective Loading[3]方式將這些模塊加載到內(nèi)存中,并注冊至exPEB中。根據(jù)當前模塊IAT,遞歸調(diào)用sLdrLoadDll和sLdrGetProc Address修復模塊間依賴。
sLdrGetProcAddress的參數(shù)為模塊基址B和函數(shù)名N或序號O。首先查找模塊,比較B和exPEB內(nèi)存中的Module的dst_base或src_base成員。然后定位函數(shù)名下標,如果第二個參數(shù)為函數(shù)名N,對N使用ror13函數(shù)得到N_hash,在模塊的name_ror13數(shù)組中搜索,得到數(shù)組的下標I;如果第二個參數(shù)為O,下標I為O與ordinal_base的差。最后,返回函數(shù)地址functions[I]。
3.1.5 其他
一些分析工具依照PE頭特征來識別加載的模塊,在修復模塊之間的依賴之后,刪除模塊的PE頭是很有必要的。
BinUnpack檢測程序最初的IAT是否包含正在調(diào)用的API地址,來判斷是否新IAT以產(chǎn)生。如果未包含,則暗示新的IAT已產(chǎn)生,可進行脫殼操作。將IAT表從加殼后的程序中刪除后,引起B(yǎng)inUnpack判斷新IAT產(chǎn)生時機失誤,脫殼失敗。
Stealth Loader支持包括:ntdll、kernel32、ker?nelbase、gdi32、user32、shell32、shlwapi、ws2_32、wininet、winsock、crypt32 和 msvcrt,共 12 個 模塊。這些模塊中的API函數(shù)已經(jīng)覆蓋了惡意代碼所需的各個方面。實驗內(nèi)容與[1]相同,分為兩項:①API混淆功能測試。主要驗證Stealth Loader是否可以擊敗各種動態(tài)和靜態(tài)反混淆工具。②惡意代碼測試。將二進制惡意代碼作為輸入,Stealth Loader輸出具有功能相同且具有API反混淆能力惡意代碼,在相關惡意軟件分析平臺檢測其危險系數(shù)是否降低。
3.2.1 API混淆功能實驗
選取相同的實驗程序,包括:calc.exe、win?mine.exe、 notepad.exe、 cmd.exe、 regedt32.exe、tasklist.exe、taskmgr.exe、xcopy.exe和 ftp.exe。將它們作為Stealth Loader的輸入,對輸出的程序分別使用靜態(tài)分析和動態(tài)分析驗證是否達到API混淆的目的。
動靜態(tài)分析。使用靜態(tài)分析工具,包括IDA、Scylla、impscan和ldrmodules,動態(tài)分析工具,包括 Cuckoo沙箱[4]、traceapi[5]、min_apitracer[6],并增加對BinUnpack測試。在使用Cuckoo沙箱分析原始程序與加殼后的程序過程中,出現(xiàn)加殼后的程序無法在Cuckoo沙箱中執(zhí)行的現(xiàn)象,造成前后分析結果相差較大。經(jīng)過進一步的實驗,加殼后的程序可以在真機或虛擬機環(huán)境中正常執(zhí)行,這也可以證明Cuckoo沙箱反混淆失敗。
實驗結果如表4所示,可見重寫的Stealth Loader達到了[1]中的效果。
表4 惡意代碼實驗結果
3.2.2 惡意代碼實驗
在3.2.1節(jié)的實驗中,加殼后的程序無法在Cuckoo沙箱中運行,為了更加真實的測試重寫Stealth Loader的效果,選擇VirusTotal和微步兩平臺。
重復與[1]相同的實驗,對從VirusShare平臺收集到的283個原始樣本加殼。首先對加殼后的程序逆向分析,確保原有功能保持不變。然后從中挑選10個不同類型的樣本,上傳至VirusTotal和微步兩個惡意軟件分析平臺進行檢測,結果如表5。
表3 API混淆功能實驗結果
表5 函數(shù)分組測試
從表中可以得出以下結論:
(1)對于VirusTotal平臺,Stealth Loader使得檢測到威脅的反病毒引擎數(shù)量大大降低。
(2)除了Lokibot和CoinMiner兩實驗,其余實驗中檢測到威脅反病毒引擎數(shù)量均減少了一倍以上,且沙箱中運行的進程均Crash。
(3)兩平臺對惡意程序的分類出現(xiàn)偏差。
kernelbase和kernel32模塊中的專用局部堆管理函數(shù)按其功能可被分為兩組,如表6所示。組一是測試內(nèi)存管理函數(shù)是否可以正常使用,組二測試內(nèi)存鎖函數(shù)是否可以正常使用。
表6 內(nèi)存管理函數(shù)實驗結果
3.3.1 內(nèi)存管理函數(shù)測試
使用下方的實驗偽代碼進行測試,包括內(nèi)存分配、大小調(diào)整和釋放三方面。
首先,前兩行代碼測試了Hook GlobalAlloc(kernelbase)的效果。若沒有同步BHHT變量至Windows下 kernelbase,則 GlobalReAlloc(kernel32)將返回空。第四行代碼測試Hook GlobalFree(kernel32)的效果,若沒有同步變量內(nèi)容至Stealth Loader下kernelbase中,result不為零。實驗結果表7驗證了EAT Hook技術已成功解決內(nèi)存管理函數(shù)的使用問題。
表7 內(nèi)存鎖函數(shù)實驗結果
3.3.2 內(nèi)存鎖函數(shù)測試
使用下方偽代碼堆內(nèi)存鎖函數(shù)進行測試,包括加鎖、解鎖和獲取鎖的數(shù)量。
前兩行偽碼測試Hook LocalAlloc(kernelbase)函數(shù)的效果,分配0x40字節(jié)大小內(nèi)存,并為其加鎖。若沒有同步BHHT至Windows下kernelbase,則 LocalFlags(kernel32)為 LMEM_INVALID_HANDLE,否則返回為 1(lock_count_first)和 0(lock_count_second)。實驗結果表8驗證了EAT Hook技術已成功解決內(nèi)存鎖函數(shù)使用問題。
本文在重新實現(xiàn)Stealth Loader的基礎上,結合了基于空閑鏈表的內(nèi)存管理系統(tǒng),并刪除加殼后的IAT,以規(guī)避BinUnpack的檢測。以EAT Hook技術為原型進行改進,修復了Stealth Loader的模塊間局部堆問題。在3.2.1節(jié)中,實驗對象出現(xiàn)了在沙箱環(huán)境下運行失敗的情況,該情況并未在[1]中出現(xiàn)。未來將進一步修復Stealth Loader其他問題,并研究加殼后程序出現(xiàn)的反沙箱特性。