來源:Malware Data Science Attack Detection and Attribution
要徹底理解惡意程序,我們通常需要超越對其節(jié)插掂、字符串、導(dǎo)入和圖像的基本靜態(tài)分析。這涉及到對程序的匯編代碼進(jìn)行逆向工程辅甥。事實(shí)上酝润,反匯編和逆向工程是惡意軟件樣本深度靜態(tài)分析的核心。
由于逆向工程是一門藝術(shù)肆氓、技術(shù)工藝和科學(xué)袍祖,所以徹底的探索超出了本章的范圍。我在這里的目標(biāo)是向您介紹逆向工程谢揪,以便您可以將其應(yīng)用于惡意軟件數(shù)據(jù)科學(xué)蕉陋。理解這種方法對于成功地將機(jī)器學(xué)習(xí)和數(shù)據(jù)分析應(yīng)用于惡意軟件是至關(guān)重要的。
在本章中拨扶,我將從理解x86反匯編所需的概念開始凳鬓。在本章的后面,我將展示惡意軟件作者如何試圖繞過反匯編患民,并討論如何減輕這些反分析和反檢測操作缩举。但首先,讓我們回顧一些常見的反匯編方法以及x86匯編語言的基礎(chǔ)知識匹颤。
一仅孩、反匯編方法
反匯編是將惡意軟件的二進(jìn)制代碼轉(zhuǎn)換成有效的x86匯編語言的過程。惡意軟件作者通常用高級語言(如C或c++)編寫惡意軟件程序印蓖,然后使用編譯器將源代碼編譯成x86二進(jìn)制代碼辽慕。匯編語言是這種二進(jìn)制代碼的可讀表示形式。因此赦肃,將惡意軟件程序分解成匯編語言是了解其核心行為的必要條件溅蛉。
不幸的是,反匯編不是一件容易的事情他宛,因?yàn)閻阂廛浖髡呓?jīng)常使用一些技巧來阻止?jié)撛诘哪嫦蚬こ處煷唷J聦?shí)上,在故意混淆的情況下厅各,完全反匯編是計(jì)算機(jī)科學(xué)中一個(gè)尚未解決的問題镜撩。目前,僅存在近似的讯检、容易出錯(cuò)的方法來反匯編這些程序琐鲁。
例如,考慮自修改代碼的情況人灼,或者在執(zhí)行時(shí)修改自身的二進(jìn)制代碼围段。正確分解這段代碼的唯一方法是理解代碼修改自身的程序邏輯,但這可能非常復(fù)雜投放。
由于完全反匯編目前是不可能的奈泪,我們必須用不完善的方法來完成這項(xiàng)任務(wù)。我們將使用的方法是線性反匯編,這涉及到在可移植可執(zhí)行(PE)文件中識別與x86程序代碼相對應(yīng)的連續(xù)字節(jié)序列涝桅,然后解碼這些字節(jié)拜姿。這種方法的主要限制是,它忽略了CPU在程序執(zhí)行過程中如何解碼指令的細(xì)微差別冯遂。此外蕊肥,它也沒有解釋惡意軟件作者有時(shí)使用的各種混淆,使他們的程序更難分析蛤肌。
逆向工程的其他方法,我們在這里不討論展东,是工業(yè)級反匯編器(如IDA Pro)使用的更復(fù)雜的反匯編方法炒俱。這些更高級的方法實(shí)際上模擬或推理程序執(zhí)行,以發(fā)現(xiàn)程序可能通過一系列條件分支達(dá)到哪些匯編指令权悟。
盡管這種類型的反匯編比線性反匯編更精確,但它比線性反匯編方法占用的CPU資源要多得多峦阁,這使得它不太適合數(shù)據(jù)科學(xué)的目的处硬,因?yàn)閿?shù)據(jù)科學(xué)的重點(diǎn)是反匯編數(shù)千甚至數(shù)百萬個(gè)程序。
然而拇派,在開始使用線性反匯編進(jìn)行分析之前,您需要回顧匯編語言的基本組件凿跳。
二件豌、x86匯編語言基礎(chǔ)
匯編語言是針對給定體系結(jié)構(gòu)的人類可讀的最低級編程語言,它緊密地映射到特定CPU體系結(jié)構(gòu)的二進(jìn)制指令格式控嗜。匯編語言的一行幾乎總是等同于一條CPU指令茧彤。因?yàn)槌绦蚣募墑e很低,所以您常辰福可以通過使用正確的工具輕松地從惡意軟件二進(jìn)制文件中檢索它曾掂。
獲得基本的熟練閱讀反匯編的惡意軟件x86代碼比你想象的要容易。這是因?yàn)榇蠖鄶?shù)惡意軟件匯編代碼花費(fèi)了大部分時(shí)間通過Windows操作系統(tǒng)的動(dòng)態(tài)鏈接庫(dll)調(diào)用操作系統(tǒng)壁顶,dll在運(yùn)行時(shí)加載到程序內(nèi)存中珠洗。惡意軟件程序使用dll來完成大部分實(shí)際工作,比如修改系統(tǒng)注冊表若专、移動(dòng)和復(fù)制文件许蓖、建立網(wǎng)絡(luò)連接以及通過網(wǎng)絡(luò)協(xié)議進(jìn)行通信等等。因此,跟蹤惡意程序匯編代碼通常需要了解函數(shù)調(diào)用從匯編中生成的方式膊爪,以及了解各種DLL調(diào)用的功能自阱。當(dāng)然,事情可能會(huì)變得復(fù)雜得多米酬,但了解這么多可以揭示很多關(guān)于惡意軟件的信息沛豌。
在接下來的幾節(jié)中,我將介紹一些重要的匯編語言概念赃额。我還解釋了一些抽象的概念加派,如控制流和控制流圖。最后爬早,我們對ircbot.exe程序進(jìn)行了分解醉旦,并探討了它的組裝和控制流程如何使我們了解它的用途车胡。
x86匯編有兩種主要的語法:Intel和AT&T匈棘。在本書中,我使用的是Intel語法簇搅,它可以從所有主要的反匯編器中獲得瘩将,并且是x86 CPU的官方Intel文檔中使用的語法。
讓我們從查看CPU寄存器開始肖抱。
2.1CPU寄存器
寄存器是x86 cpu執(zhí)行計(jì)算的小型數(shù)據(jù)存儲(chǔ)單元熊经。由于寄存器位于CPU本身镐依,寄存器訪問比內(nèi)存訪問快幾個(gè)數(shù)量級然低。這就是為什么核心計(jì)算操作雳攘,如算術(shù)和條件測試指令吨灭,都是目標(biāo)寄存器。這也是為什么CPU使用寄存器來存儲(chǔ)有關(guān)運(yùn)行程序狀態(tài)的信息吠冤。雖然許多寄存器對于經(jīng)驗(yàn)豐富的x86匯編程序員是可用的拯辙,但是我們在這里只關(guān)注幾個(gè)重要的寄存器。
1遭赂、通用寄存器
通用寄存器對于程序集程序員來說就像一個(gè)空白空間。在32位系統(tǒng)上狈蚤,每個(gè)寄存器包含32位脆侮、16位或8位空間靖避,我們可以對這些空間執(zhí)行算術(shù)操作盆犁、位操作谐岁、字節(jié)順序交換操作等等伊佃。
在常見的計(jì)算工作流中航揉,程序?qū)?shù)據(jù)從內(nèi)存或外部硬件設(shè)備轉(zhuǎn)移到寄存器中帅涂,對這些數(shù)據(jù)執(zhí)行一些操作漠秋,然后將數(shù)據(jù)移回內(nèi)存中進(jìn)行存儲(chǔ)。例如搂抒,要對長列表進(jìn)行排序求晶,程序通常從內(nèi)存中的數(shù)組中提取列表項(xiàng)芳杏,在寄存器中比較它們衔峰,然后將比較結(jié)果寫回內(nèi)存。
要了解Intel 32位體系結(jié)構(gòu)中通用寄存器模型的一些細(xì)微差別秕铛,請看圖2-1但两。
縱軸顯示通用寄存器的布局镜遣,橫軸顯示EAX谎僻、EBX艘绍、ECX和EDX是如何細(xì)分的诱鞠。EAX航夺、EBX、ECX和EDX是32位寄存器缭保,其中包含更小的16位寄存器:AX艺骂、BX钳恕、CX和DX忧额。如圖所示轴脐,這些16位寄存器可以細(xì)分為上下8位寄存器:AH、AL、BH丑搔、BL啤月、CH、CL郑诺、DH和DL辙诞。雖然有時(shí)處理EAX、EBX封拧、ECX和EDX中的子分類很有用,但是您將主要看到對EAX缰趋、EBX味抖、ECX和EDX的直接引用灰粮。
2仔涩、堆棧和控制流寄存器
堆棧管理寄存器存儲(chǔ)有關(guān)程序堆棧的關(guān)鍵信息,程序堆棧負(fù)責(zé)存儲(chǔ)函數(shù)的局部變量粘舟、傳遞給函數(shù)的參數(shù)以及與程序控制流相關(guān)的控制信息熔脂。我們來復(fù)習(xí)一下這些寄存器佩研。
簡單地說,ESP寄存器指向當(dāng)前執(zhí)行函數(shù)堆棧的頂部霞揉,而EBP寄存器指向當(dāng)前執(zhí)行函數(shù)堆棧的底部旬薯。這對于現(xiàn)代程序來說是至關(guān)重要的信息绊序,因?yàn)檫@意味著通過引用相對于堆棧的數(shù)據(jù)而不是使用它的絕對地址,過程性和面向?qū)ο蟮拇a可以更優(yōu)雅、更有效地訪問本地變量。
盡管您不會(huì)在x86匯編代碼中看到對EIP寄存器的直接引用,但它在安全分析中非常重要垢粮,特別是在漏洞研究和緩沖區(qū)溢出利用開發(fā)的上下文中。這是因?yàn)镋IP包含當(dāng)前執(zhí)行指令的內(nèi)存地址。攻擊者可以使用緩沖區(qū)溢出漏洞間接破壞EIP寄存器的值洛姑,并控制程序的執(zhí)行择同。
除了它在開發(fā)中的作用紧武,EIP在分析惡意軟件部署的惡意代碼方面也很重要滥酥。使用調(diào)試器,我們可以隨時(shí)檢查EIP的值弦讽,這有助于我們了解在任何特定時(shí)間執(zhí)行的代碼惡意軟件。
EFLAGS是一個(gè)包含CPU標(biāo)志的狀態(tài)寄存器酱酬,CPU標(biāo)志是存儲(chǔ)當(dāng)前執(zhí)行程序狀態(tài)信息的位巡揍。EFLAGS寄存器是在x86程序中創(chuàng)建條件分支的過程的核心啤斗,或者是由于if/then風(fēng)格的程序邏輯的結(jié)果而導(dǎo)致的執(zhí)行流的更改盯桦。具體來說,當(dāng)一個(gè)x86匯編程序檢查一個(gè)值是否大于或小于零,然后跳到一個(gè)函數(shù)基于這個(gè)測試的結(jié)果,EFLAGS寄存器起到了支持作用,更詳細(xì)地描述“基本塊和控制流圖”在19頁喊递。
3殉疼、算術(shù)指令
指令在通用寄存器上操作眠砾。您可以使用算術(shù)指令使用通用寄存器執(zhí)行簡單的計(jì)算谷丸。例如币狠,add宝踪、sub、inc巷挥、dec和mul是在惡意軟件逆向工程中經(jīng)常遇到的算術(shù)指令桩卵。表2-1列出了一些基本指令及其語法示例。
add指令添加兩個(gè)整數(shù)倍宾,并將結(jié)果存儲(chǔ)在指定的第一個(gè)操作數(shù)中雏节,無論這個(gè)操作數(shù)是內(nèi)存位置還是寄存器(根據(jù)以下語法)。記住高职,只有一個(gè)參數(shù)可以是內(nèi)存位置钩乍。子指令類似于加法,只是它減去整數(shù)怔锌。inc指令遞增寄存器或內(nèi)存位置的整數(shù)值寥粹,而dec遞減寄存器或內(nèi)存位置的整數(shù)值变过。
4、數(shù)據(jù)轉(zhuǎn)移指令
x86處理器提供了一組健壯的指令涝涤,用于在寄存器和內(nèi)存之間移動(dòng)數(shù)據(jù)媚狰。這些指令提供了允許我們操作數(shù)據(jù)的底層機(jī)制。主體內(nèi)存運(yùn)動(dòng)指令是mov指令阔拳。表2-2顯示了如何使用mov指令移動(dòng)數(shù)據(jù)崭孤。
與mov指令相關(guān),lea指令將指定的絕對內(nèi)存地址加載到寄存器中糊肠,用于獲得指向內(nèi)存位置的指針辨宠。例如,lea edx [ESP -4]從ESP中的值中減去4并將結(jié)果值加載到edx中货裹。
5嗤形、棧指令
x86匯編中的堆棧是一種數(shù)據(jù)結(jié)構(gòu)弧圆,允許您將值推入和彈出到堆棧中赋兵。這類似于在一堆板的頂部或頂部添加和刪除板拓轻。
因?yàn)榭刂屏魍峭ㄟ^c風(fēng)格的表達(dá)在x86匯編函數(shù)調(diào)用,因?yàn)檫@些函數(shù)調(diào)用使用堆棧來傳遞參數(shù),分配本地變量,并且記住什么該計(jì)劃的一部分返回一個(gè)函數(shù)執(zhí)行完畢后,棧需要理解和控制流在一起枣氧。
當(dāng)程序員希望將寄存器值保存到堆棧中時(shí)覆糟,push指令將值推送到程序堆棧中,pop指令將從堆棧中刪除值并將它們放入指定的寄存器中。
push指令使用以下語法執(zhí)行操作:
push 1
在本例中,程序?qū)⒍褩V羔?寄存器ESP)指向一個(gè)新的內(nèi)存地址处坪,從而為值(1)騰出空間根资,該值現(xiàn)在存儲(chǔ)在堆棧的頂部位置。然后它將值從參數(shù)復(fù)制到CPU剛剛為堆棧頂部騰出空間的內(nèi)存位置同窘。
讓我們將其與pop進(jìn)行對比:
pop eax
該程序使用pop將堆棧頂部的值彈出并將其移動(dòng)到指定的寄存器中玄帕。在本例中,pop eax從堆棧中彈出頂部值并將其移動(dòng)到eax中想邦。
關(guān)于x86程序堆棧裤纹,需要理解的一個(gè)不直觀但重要的細(xì)節(jié)是,它在內(nèi)存中向下增長丧没,因此堆棧上的最大值實(shí)際上存儲(chǔ)在堆棧內(nèi)存中的最低地址鹰椒。當(dāng)您分析引用存儲(chǔ)在堆棧上的數(shù)據(jù)的匯編代碼時(shí),記住這一點(diǎn)非常重要呕童,因?yàn)槌悄蓝褩5膬?nèi)存布局漆际,否則很快就會(huì)混淆。
因?yàn)閤86堆棧增長下行在內(nèi)存中,當(dāng)推指令分配空間程序堆棧的一個(gè)新值,它將ESP的價(jià)值,讓它指向一個(gè)較低的位置在內(nèi)存中,然后從目標(biāo)寄存器值拷貝到內(nèi)存位置,堆棧的頂部開始地址和成長夺饲。相反奸汇,pop指令實(shí)際上復(fù)制堆棧的頂部值,然后增加ESP的值往声,使其指向更高的內(nèi)存位置茫蛹。
6、控制流指令
x86程序的控制流定義了程序可能執(zhí)行的指令執(zhí)行序列的網(wǎng)絡(luò)烁挟,具體取決于程序可能接收到的數(shù)據(jù)婴洼、設(shè)備交互和其他輸入『成ぃ控制流指令定義程序的控制流柬采。它們比堆棧指令更復(fù)雜欢唾,但仍然非常直觀。由于控制流在x86匯編中通常是通過c風(fēng)格的函數(shù)調(diào)用來表示的粉捻,所以堆棧和控制流是緊密相關(guān)的礁遣。它們也是相關(guān)的,因?yàn)檫@些函數(shù)調(diào)用使用堆棧傳遞參數(shù)肩刃、分配局部變量祟霍,并記住函數(shù)執(zhí)行完后返回程序的哪個(gè)部分。
調(diào)用和ret控制流指令對于程序在x86匯編中如何調(diào)用函數(shù)以及程序在執(zhí)行這些函數(shù)之后如何從函數(shù)返回是最重要的盈包。
調(diào)用指令調(diào)用一個(gè)函數(shù)沸呐。可以把它看作是一個(gè)函數(shù)呢燥,您可以用C之類的高級語言編寫它崭添,以便在調(diào)用調(diào)用指令并完成函數(shù)的執(zhí)行之后,程序返回到該指令叛氨。您可以使用以下語法調(diào)用調(diào)用指令呼渣,其中address表示調(diào)用指令調(diào)用函數(shù)的內(nèi)存∧海可以把它看作是一個(gè)函數(shù)屁置,您可以用C之類的高級語言編寫它,以便在調(diào)用調(diào)用指令并完成函數(shù)的執(zhí)行之后仁连,程序返回到該指令蓝角。您可以使用以下語法調(diào)用調(diào)用指令,其中address表示內(nèi)存:
call?address
調(diào)用指令做兩件事怖糊。首先帅容,它將函數(shù)調(diào)用返回后將執(zhí)行的指令的地址推到堆棧的頂部颇象,以便程序知道被調(diào)用的函數(shù)執(zhí)行完后返回什么地址伍伤。其次,call用地址操作數(shù)指定的值替換EIP的當(dāng)前值遣钳。然后扰魂,CPU在EIP指向的新內(nèi)存位置開始執(zhí)行。
就像調(diào)用開始一個(gè)函數(shù)調(diào)用一樣蕴茴,ret指令完成它劝评。你可以單獨(dú)使用ret指令,不需要任何參數(shù)倦淀,如下圖所示:
ret
當(dāng)被調(diào)用時(shí)蒋畜,ret將從堆棧中彈出頂部值,我們希望該值是調(diào)用指令被調(diào)用時(shí)被推入堆棧的保存程序計(jì)數(shù)器值(EIP)撞叽。然后它將彈出的程序計(jì)數(shù)器值放回EIP并繼續(xù)執(zhí)行姻成。
jmp指令是另一個(gè)重要的控制流結(jié)構(gòu)插龄,它的操作比調(diào)用簡單。jmp不需要擔(dān)心保存EIP科展,只需告訴CPU移動(dòng)到作為其參數(shù)指定的內(nèi)存地址均牢,并在那里開始執(zhí)行。例如才睹,jmp 0x12345678告訴CPU在下一條指令上開始執(zhí)行存儲(chǔ)在內(nèi)存位置0x12345678的程序代碼徘跪。
您可能想知道如何使jmp和調(diào)用指令以一種有條件的方式執(zhí)行,比如“如果程序收到了一個(gè)網(wǎng)絡(luò)包琅攘,執(zhí)行以下函數(shù)”垮庐。答案是x86程序集沒有高級構(gòu)造,比如if乎澄、then突硝、else、else if等等置济。相反解恰,在程序代碼中分支到一個(gè)地址通常需要兩條指令:一條cmp指令,它根據(jù)某個(gè)測試值檢查某個(gè)寄存器中的值浙于,并將該測試的結(jié)果存儲(chǔ)在EFLAGS寄存器中;另一條是條件分支指令护盈。
大多數(shù)條件分支指令都以j開頭,它允許程序跳轉(zhuǎn)到內(nèi)存地址羞酗,并使用表示測試條件的字母進(jìn)行后置腐宋。例如,jge告訴程序在大于或等于時(shí)跳轉(zhuǎn)檀轨。這意味著要測試的寄存器中的值必須大于或等于測試值胸竞。
cmp指令使用以下語法:
cmp?register, memory location, or literal, register, memory location, or
literal
如前所述,cmp將指定的通用寄存器中的值與值進(jìn)行比較参萄,然后將比較的結(jié)果存儲(chǔ)在EFLAGS寄存器中卫枝。
然后調(diào)用各種條件jmp指令如下:
j* address
正如您所看到的,我們可以將j前綴為任意數(shù)量的條件測試指令讹挎。例如校赤,只有當(dāng)被測試的值大于或等于寄存器中的值時(shí),才要跳轉(zhuǎn)筒溃,請使用以下指令:
jge address
注意马篮,與調(diào)用和ret指令的情況不同,jmp指令家族從不涉及程序堆棧怜奖。實(shí)際上浑测,在jmp指令家族中,x86程序負(fù)責(zé)跟蹤自己的執(zhí)行流歪玲,并可能保存或刪除關(guān)于它訪問過哪些地址的信息迁央,以及在執(zhí)行了特定的指令序列之后應(yīng)該返回到哪里的信息怎顾。
7、基本塊和控制流程圖
雖然當(dāng)我們在文本編輯器中滾動(dòng)x86程序的代碼時(shí)漱贱,它們看起來是順序的槐雾,但實(shí)際上它們有循環(huán)、條件分支和無條件分支(控制流)幅狮。所有這些都為每個(gè)x86程序提供了一個(gè)網(wǎng)絡(luò)結(jié)構(gòu)募强。讓我們使用清單2-1中的簡單玩具組裝程序來看看這是如何工作的。
正如您可以看到的,這個(gè)程序初始化計(jì)數(shù)器的值10,存儲(chǔ)在寄存器EAX?崇摄。接下來,它的循環(huán)價(jià)值EAX由每個(gè)迭代1?遞減擎值。最后,一旦EAX已經(jīng)到了一個(gè)值0?,程序循環(huán)的爆發(fā)。
在控制流程圖分析語言中逐抑,我們可以把這些指令看作是由三個(gè)基本塊組成的鸠儿。基本塊是一系列指令厕氨,我們知道這些指令總是連續(xù)執(zhí)行的进每。換句話說,基本塊總是以分支指令或分支目標(biāo)指令結(jié)束命斧,并且總是以程序的第一條指令(稱為程序的入口點(diǎn))或分支目標(biāo)開始田晚。
在清單2-1中,您可以看到我們的簡單程序的基本塊從哪里開始和結(jié)束国葬。第一個(gè)基本塊由指令mov eax, 10在setup:下組成贤徒。第二個(gè)基本塊由以下幾行組成:從子eax開始,1到在loopstart:下面的jne $loopstart汇四,第三個(gè)基本塊從mov eax開始接奈,1在loopend:下面。我們可以使用圖2-2中的圖可視化基本塊之間的關(guān)系通孽。(我們使用術(shù)語圖與術(shù)語網(wǎng)絡(luò)同義;在計(jì)算機(jī)科學(xué)中序宦,這些術(shù)語是可以互換的。
如果一個(gè)基本塊可以流到另一個(gè)基本塊中利虫,我們將它連接起來挨厚,如圖2-2所示堡僻。如圖所示糠惫,setup基本塊指向loopstart基本塊,該基本塊在轉(zhuǎn)換到loopend基本塊之前重復(fù)10次《ひ撸現(xiàn)實(shí)世界中的程序有這樣的控制流程圖硼讽,但它們要復(fù)雜得多,有數(shù)千個(gè)基本塊和數(shù)千個(gè)互連牲阁。
8固阁、使用pefile和capstone反匯編ircbot.exe
現(xiàn)在您已經(jīng)很好地理解了匯編語言的基礎(chǔ)知識壤躲,讓我們來分解ircbot的前100個(gè)字節(jié)。使用線性反匯編的exe匯編代碼备燃。為此碉克,我們將使用開放源碼Python庫pefile(在第1章中介紹)和capstone,這是一個(gè)可以反匯編32位x86二進(jìn)制代碼的開放源碼反匯編庫并齐。您可以使用以下命令使用pip安裝這兩個(gè)庫:
pip install pefile
pip install capstone
安裝了這兩個(gè)庫之后漏麦,我們可以使用清單2-2中的代碼利用它們來反編譯ircbot.exe。
#!/usr/bin/python
import pefile
from capstone import *
# load the target PE file
pe = pefile.PE("ircbot.exe")
# get the address of the program entry point from the program header
entrypoint = pe.OPTIONAL_HEADER.AddressOfEntryPoint
# compute memory address where the entry code will be loaded into memory
entrypoint_address = entrypoint+pe.OPTIONAL_HEADER.ImageBase
# get the binary code from the PE file object
binary_code = pe.get_memory_mapped_image()[entrypoint:entrypoint+100]
# initialize disassembler to disassemble 32 bit x86 binary code
disassembler = Cs(CS_ARCH_X86, CS_MODE_32)
# disassemble the code
for instruction in disassembler.disasm(binary_code, entrypoint_address):
print "%s\t%s" %(instruction.mnemonic, instruction.op_str)
這將產(chǎn)生以下輸出:
?push????ebp
mov?????ebp, esp
push????-1
push????0x437588
push????0x41982c
?mov?????eax, dword ptr fs:[0]
push????eax
mov?????dword ptr fs:[0], esp
?add?????esp, -0x5c
push????ebx
push????esi
push????edi
mov?????dword ptr [ebp - 0x18], esp
?call????dword ptr [0x496308]
--snip--
不要擔(dān)心理解反匯編輸出中的所有指令:這將涉及到超出本書范圍的對匯編的理解况褪。
但是撕贞,您應(yīng)該對輸出中的許多指令感到滿意,并對它們的功能有一定的了解测垛。例如,惡意軟件推動(dòng)EBP寄存器中的值壓入堆棧?,保存它的價(jià)值捏膨。然后將ESP中的值移動(dòng)到EBP中,并將一些數(shù)值推入堆棧食侮。程序會(huì)將一些數(shù)據(jù)在內(nèi)存中移動(dòng)到EAX寄存器?,和它添加值0 x5c?ESP寄存器中的值号涯。最后,程序使用的調(diào)用指令調(diào)用一個(gè)函數(shù)存儲(chǔ)在內(nèi)存地址0 x496308?。
因?yàn)檫@不是一本關(guān)于逆向工程的書锯七,所以我在這里不再深入討論代碼的含義诚隙。我所介紹的是理解匯編語言如何工作的一個(gè)開端。有關(guān)匯編語言的更多信息起胰,我推薦使用Intel程序員手冊久又,網(wǎng)址是http://www.intel.com/content/www/us/en/processors/architectures-software-developer-manuals.html。
9效五、限制靜態(tài)分析的因素
在本章和第1章中地消,您了解了各種使用靜態(tài)分析技術(shù)來闡明新發(fā)現(xiàn)的惡意二進(jìn)制文件的目的和方法的方法。不幸的是畏妖,靜態(tài)分析有一些限制脉执,使得它在某些情況下用處不大。例如戒劫,惡意軟件作者可以使用某些攻擊策略半夷,這些策略的實(shí)現(xiàn)要比防御容易得多。讓我們來看看這些進(jìn)攻性的戰(zhàn)術(shù)迅细,看看如何防御它們巫橄。
(1)包裝
惡意軟件打包是惡意軟件作者壓縮、加密或以其他方式破壞大部分惡意程序的過程茵典,從而使惡意軟件分析人員無法理解這些程序湘换。當(dāng)惡意軟件運(yùn)行時(shí),它會(huì)解包,然后開始執(zhí)行彩倚。繞過惡意軟件打包最明顯的方法是在安全的環(huán)境中實(shí)際運(yùn)行惡意軟件筹我,這是我將在第3章介紹的動(dòng)態(tài)分析技術(shù)。
注意:出于合法的原因帆离,軟件安裝程序也會(huì)使用軟件打包蔬蕊。良性軟件作者使用打包來交付他們的代碼,因?yàn)樗试S他們壓縮程序資源來減少軟件安裝程序下載的大小哥谷。它還幫助它們阻止業(yè)務(wù)競爭對手的逆向工程嘗試袁串,并提供了一種方便的方法來將許多程序資源捆綁到一個(gè)安裝程序文件中。
(2)資源混淆
惡意軟件作者使用的另一種反檢測呼巷、反分析技術(shù)是資源混淆囱修。它們混淆了程序資源(如字符串和圖形圖像)存儲(chǔ)在磁盤上的方式,然后在運(yùn)行時(shí)對它們進(jìn)行反混淆王悍,以便惡意程序可以使用它們破镰。例如,一個(gè)簡單的混淆操作是在PE resources部分中存儲(chǔ)的圖像和字符串中的所有字節(jié)中添加一個(gè)值1压储,然后在運(yùn)行時(shí)從所有這些數(shù)據(jù)中減去1鲜漩。當(dāng)然,這里可能存在許多混淆集惋,所有這些都使惡意軟件分析人員難以使用靜態(tài)分析來理解惡意軟件二進(jìn)制文件孕似。
與打包一樣,繞過資源混淆的一種方法是在安全的環(huán)境中運(yùn)行惡意軟件刮刑。當(dāng)這不是一個(gè)選項(xiàng)時(shí)喉祭,唯一能減輕資源混淆的方法就是找出惡意軟件混淆其資源的方式,并手動(dòng)消除它們的混淆雷绢,這是專業(yè)惡意軟件分析師經(jīng)常做的事情泛烙。
(3)Anti-disassembly技術(shù)
惡意軟件作者使用的第三組反檢測、反分析技術(shù)是反匯編技術(shù)翘紊。這些技術(shù)旨在利用最先進(jìn)的反匯編技術(shù)的固有限制蔽氨,向惡意軟件分析人員隱藏代碼,或使惡意軟件分析人員認(rèn)為存儲(chǔ)在磁盤上的代碼塊包含與實(shí)際不同的指令鹉究。
反匯編技術(shù)的一個(gè)例子是將分支轉(zhuǎn)移到一個(gè)內(nèi)存位置,惡意軟件作者的反匯編程序?qū)堰@個(gè)內(nèi)存位置解釋為另一條指令踪宠,本質(zhì)上是向逆向工程師隱藏惡意軟件的真實(shí)指令自赔。反匯編技術(shù)有巨大的潛力,沒有完美的方法來抵御它們殴蓬。在實(shí)踐中匿级,針對這些技術(shù)的兩種主要防御方法是在動(dòng)態(tài)環(huán)境中運(yùn)行惡意軟件樣本,并手動(dòng)找出惡意軟件樣本中反匯編策略的表現(xiàn)染厅,以及如何繞過它們痘绎。
(4)動(dòng)態(tài)下載數(shù)據(jù)
惡意軟件作者使用的最后一類反分析技術(shù)包括從外部獲取數(shù)據(jù)和代碼。例如肖粮,惡意軟件示例可能在惡意軟件啟動(dòng)時(shí)從外部服務(wù)器動(dòng)態(tài)加載代碼孤页。如果是這種情況,靜態(tài)分析對于這樣的代碼將是無用的涩馆。類似地行施,惡意軟件可能在啟動(dòng)時(shí)從外部服務(wù)器獲取解密密鑰,然后使用這些密鑰解密將在惡意軟件執(zhí)行過程中使用的數(shù)據(jù)或代碼魂那。
顯然蛾号,如果惡意軟件使用工業(yè)級加密算法,靜態(tài)分析將不足以恢復(fù)加密的數(shù)據(jù)和代碼涯雅。這種反分析和反檢測技術(shù)非常強(qiáng)大鲜结,解決它們的唯一方法是通過某種方式獲取外部服務(wù)器上的代碼、數(shù)據(jù)或私鑰活逆,然后使用它們來分析所涉及的惡意軟件精刷。
總結(jié)
本章介紹了x86匯編代碼分析,并演示了如何使用開放源碼Python工具在ircbot.exe上執(zhí)行基于解體的靜態(tài)分析蔗候。雖然這并不是x86程序集的完整入門怒允,但是您現(xiàn)在應(yīng)該已經(jīng)有了一個(gè)了解給定惡意程序集轉(zhuǎn)儲(chǔ)中發(fā)生的事情的起點(diǎn)。最后锈遥,您了解了惡意軟件作者如何防范反匯編和其他靜態(tài)分析技術(shù)纫事,以及如何減輕這些反分析和反檢測策略。在第3章中所灸,您將學(xué)習(xí)如何進(jìn)行動(dòng)態(tài)惡意軟件分析儿礼,這彌補(bǔ)了靜態(tài)惡意軟件分析的許多弱點(diǎn)。