本機(jī)函數(shù)和匯編代碼調(diào)用
對(duì)于逆向工程師來(lái)說(shuō),直接從分析的二進(jìn)制代碼中調(diào)用函數(shù)的能力是一種捷徑,可以省去很多麻煩。雖然在某些情況下,理解函數(shù)邏輯并在高級(jí)語(yǔ)言中重新實(shí)現(xiàn)它是可能的,但這并不總是可行的,而且原始函數(shù)的邏輯越脆弱和復(fù)雜,這種方法就越不可行。在處理自定義哈希和加密時(shí),這是一個(gè)特別棘手的問(wèn)題,如果計(jì)算中的某個(gè)地方出現(xiàn)一個(gè)錯(cuò)誤,就會(huì)導(dǎo)致最終輸出完全不同,而且調(diào)試起來(lái)非常麻煩。
我們將在本文中,介紹3 種實(shí)現(xiàn)此“捷徑”的不同方法,并直接從匯編中調(diào)用函數(shù)。我們首先介紹IDA Pro原生支持的IDA Appcall功能,可以直接通過(guò)idpython使用。然后我們演示如何使用Dumpulator實(shí)現(xiàn)同樣的行為,最后,我們將展示如何使用Unicorn Engine模擬得到該結(jié)果。本文中使用的實(shí)際示例基于MiniDuke惡意軟件示例實(shí)現(xiàn)的“經(jīng)過(guò)調(diào)整的”SHA1哈希算法。
MiniDuke 實(shí)現(xiàn)的改進(jìn)的 SHA1 Hashing 算法
MiniDuke示例中經(jīng)過(guò)修改的SHA1算法用于為惡意軟件配置創(chuàng)建每個(gè)系統(tǒng)的加密密鑰。要哈希的緩沖區(qū)包含與所有接口描述的 DWORD 連接的當(dāng)前計(jì)算機(jī)名稱,例如 'DESKTOP-ROAC4IJ\x00MicrWAN WAN MicrWAN MicrWAN InteWAN InteWAN Inte'。此函數(shù) (SHA1Hash) 在初始摘要和中間階段使用與原始 SHA1 相同的常量,但產(chǎn)生不同的輸出。
MiniDuke SHA1Hash 函數(shù)常量
由于在原始和修改后的 SHA1 中使用的常量都相同,因此差異必須出現(xiàn)在函數(shù)的 1241 條匯編指令中。我們不能說(shuō)這種調(diào)整是否是故意引入的,但事實(shí)仍然是惡意軟件開(kāi)發(fā)者越來(lái)越喜歡插入這樣的“驚喜”,而處理它們的責(zé)任就落在了分析師身上。為此,我們必須首先了解函數(shù)期望其輸入并產(chǎn)生其輸出的形式。
事實(shí)證明,Duke-SHA1匯編使用自定義調(diào)用約定,其中要哈希的緩沖區(qū)長(zhǎng)度在ecx寄存器中傳遞,而緩沖區(qū)本身的地址在edi中傳遞。從技術(shù)上講,在 eax 中也傳遞了一個(gè)值,但是每當(dāng)可執(zhí)行文件調(diào)用該函數(shù)時(shí),該值都是相同的 0xffffffff,因此我們可以將其視為常量。有趣的是,惡意軟件還在每次調(diào)用該函數(shù)時(shí)將緩沖區(qū)長(zhǎng)度(ecx)設(shè)置為0x40,僅對(duì)緩沖區(qū)的前 0x40 個(gè)字節(jié)進(jìn)行有效地哈希處理。
SHA1Hash 函數(shù)參數(shù)
由此產(chǎn)生的160位SHA1哈希值在寄存器中以5個(gè)dword的形式返回(從高到低: eax, edx, ebx, ecx, esi)。例如,緩沖DESKTOP-ROAC4IJ\x00MicrWAN WAN MicrWAN MicrWAN InteWAN InteWAN Inte的Duke-SHA1值為1851fff77f0957d1d690a32f31df2c32a1a84af7,返回為EAX:0x1851fff7 EDX:0x7f0957d1 EBX:0xd690a32f ECX:0x31df2c32 ESI:0xa1a84af7。
生成的SHA1緩沖區(qū)哈希示例
如上所述,查找SHA1和Duke-SHA1的邏輯發(fā)生分歧的確切位置,然后在Python中重新實(shí)現(xiàn)Duke-SHA1,不過(guò)這個(gè)方法非常浪費(fèi)時(shí)間。接下來(lái),我們將使用幾種方法來(lái)“插入”函數(shù)的調(diào)用約定并直接調(diào)用它。
IDA–Appcall
Appcall是IDA Pro的一個(gè)功能,它允許IDA Python腳本在調(diào)試程序中調(diào)用函數(shù),就像它們是內(nèi)置函數(shù)一樣。這是非常方便的,但它也不是通用的,即當(dāng)用例變得有些不尋?;驈?fù)雜時(shí),應(yīng)用難度會(huì)急劇上升。雖然在 ecx 中傳遞緩沖區(qū)長(zhǎng)度和在 edi 中傳遞緩沖區(qū)是正常的,但在5個(gè)寄存器中分割160位的返回值并不是典型的函數(shù)輸出形式,Appcall 需要一些創(chuàng)意來(lái)解決這個(gè)問(wèn)題。
接下來(lái),我們創(chuàng)建了一個(gè)自定義結(jié)構(gòu)struc_SHA1HASH,它保存了5個(gè)寄存器的值,并用作函數(shù)原型的返回類型:
IDA 結(jié)構(gòu)窗口——“struc_SHA1HASH”
現(xiàn)在有了結(jié)構(gòu)定義, Appcall 就可以與這個(gè)函數(shù)原型交互,如下面的 PROTO 值所示。
由于IDA Appcall依賴于調(diào)試器,為了調(diào)用這個(gè)邏輯,我們首先需要編寫(xiě)一個(gè)腳本來(lái)啟動(dòng)調(diào)試器,對(duì)堆棧進(jìn)行必要的調(diào)整,并執(zhí)行其他必要的管理工作。
IDA視圖——堆棧調(diào)整
使用Appcall是最后一步,有幾種方法可以利用它來(lái)調(diào)用函數(shù)。我們可以在不指定原型的情況下直接調(diào)用函數(shù),但這高度依賴于 IDA 的 IDB 中正確類型的函數(shù)。第二種方法是根據(jù)函數(shù)名和定義的原型創(chuàng)建一個(gè)可調(diào)用對(duì)象。通過(guò)這種方式,我們可以調(diào)用帶有特定原型的函數(shù),無(wú)論在IDB中設(shè)置了什么類型,如下所示:
使用 Appcall 調(diào)用 Duke-SHA1 的完整腳本如下所示。
還有一些示例輸出:
腳本執(zhí)行——“IDA Appcall”產(chǎn)生與 MiniDuke 樣本相同的 SHA1 哈希值
如果我們只是想將被調(diào)用的函數(shù)用作黑盒,那么上面的方法是可以的,但有時(shí)我們可能希望在執(zhí)行的特定狀態(tài)下訪問(wèn)注冊(cè)表值,并且像上面那樣指定原型是一件繁瑣的事情。令人高興的是,這兩個(gè)缺點(diǎn)都可以被優(yōu)化。
由于 IDA Appcall 依賴于調(diào)試器并且可以直接從 IDAPython 調(diào)用,因此我們可以從調(diào)試器調(diào)用 Appcall 并對(duì)其執(zhí)行進(jìn)行更精細(xì)的控制。例如,我們可以通過(guò)為 Appcall 設(shè)置一個(gè)特殊選項(xiàng)——APPCALL_MANUAL 來(lái)讓 Appcall 在執(zhí)行過(guò)程中將控制權(quán)交還給調(diào)試器。
通過(guò)這種方式,我們可以使用Appcall來(lái)準(zhǔn)備參數(shù),分配一個(gè)緩沖區(qū),然后恢復(fù)之前的執(zhí)行上下文。我們也可以避免為返回值指定結(jié)構(gòu)類型(將其輸入為void),因?yàn)檫@將由調(diào)試器處理。有更多的方法來(lái)獲取函數(shù)的返回值,因此當(dāng)控制調(diào)試器,就可以使用條件斷點(diǎn)在特定的執(zhí)行狀態(tài)(例如在返回時(shí))打印所需的值。
我們可以通過(guò)調(diào)用cleanup_appcall()在任何需要的執(zhí)行時(shí)刻恢復(fù)之前的狀態(tài)(在 Appcall 調(diào)用之前)。在我們的例子中,正好在遇到條件斷點(diǎn)之后。
完整的腳本如下:
Dumpulator
Dumpulator是一個(gè)python庫(kù),它幫助在minidump文件中進(jìn)行代碼模擬。dumator的核心模擬引擎基于Unicorn engine,但在同類工具中有一個(gè)比較獨(dú)特的特點(diǎn),那就是可以獲得整個(gè)過(guò)程的內(nèi)存。這帶來(lái)了性能改進(jìn)(在不離開(kāi) Unicorn 的情況下模擬大部分已分析的二進(jìn)制文件),如果我們可以在調(diào)用函數(shù)所需的程序上下文(堆棧等)已經(jīng)就位的時(shí)候計(jì)算內(nèi)存轉(zhuǎn)儲(chǔ)的時(shí)間,那么就更方便了。此外,只有模擬系統(tǒng)調(diào)用才能提供真實(shí)的Windows環(huán)境(因?yàn)閷?shí)際上一切都是合法的進(jìn)程環(huán)境)。
可以使用許多工具(x64dbg - MiniDumpPlugin, process Explorer, process Hacker, Task Manager)或Windows API (MiniDumpWriteDump)捕獲所需進(jìn)程的一個(gè)minidump。我們可以使用x64dbg - MiniDumpPlugin在幾乎所有進(jìn)程都已經(jīng)設(shè)置為SHA1哈希創(chuàng)建的狀態(tài)下創(chuàng)建一個(gè)minidump,就在SHA1Hash函數(shù)調(diào)用之前。注意,沒(méi)有必要以這種方式對(duì)轉(zhuǎn)儲(chǔ)進(jìn)行計(jì)時(shí),因?yàn)樵谶M(jìn)行轉(zhuǎn)儲(chǔ)后,可以在轉(zhuǎn)儲(chǔ)器中手動(dòng)設(shè)置環(huán)境,這只是為了方便而已。
使用“x64dbg - MiniDumpPlugin”創(chuàng)建minidump
Dumpulator不僅可以訪問(wèn)整個(gè)轉(zhuǎn)儲(chǔ)的進(jìn)程內(nèi)存,還可以分配額外的內(nèi)存、讀取內(nèi)存、寫(xiě)入內(nèi)存、讀取注冊(cè)表值和寫(xiě)入注冊(cè)表值。換句話說(shuō),模擬器可以做的任何事情。也有可能實(shí)現(xiàn)系統(tǒng)調(diào)用,因此可以模擬使用它們的代碼。
要通過(guò)Dumpulator調(diào)用Duke-SHA1,我們需要指定將在minidump中調(diào)用的函數(shù)的地址及其參數(shù)。在本例中,SHA1Hash的地址為0x407108。
在 IDA 中打開(kāi)生成的minidump
因?yàn)槲覀儾幌M趍inidump的當(dāng)前狀態(tài)中使用已經(jīng)設(shè)置的值,所以我們?yōu)楹瘮?shù)定義自己的參數(shù)值。我們甚至可以分配一個(gè)新的緩沖區(qū),用作哈希的緩沖區(qū)。完成此任務(wù)的代碼如下所示。
執(zhí)行此腳本將生成正確的Duke-SHA1值
腳本執(zhí)行——“Dumpulator”產(chǎn)生與 MiniDuke 樣本相同的 SHA1 哈希值
Emulation–Unicorn Engine
對(duì)于模擬方法,我們可以使用任何類型的CPU模擬器(例如Qiling、Speakeasy等),它們能夠模擬x86匯編,并具有針對(duì)Python語(yǔ)言的綁定。因?yàn)槲覀儾恍枰魏胃叩某橄蠹?jí)別(系統(tǒng)調(diào)用,API函數(shù)),我們可以使用其他大多數(shù)引擎的基礎(chǔ)設(shè)施——Unicorn Engine。
Unicorn是一個(gè)輕量級(jí)、多平臺(tái)、多體系結(jié)構(gòu)的CPU模擬器框架,基于QEMU,它是用純C語(yǔ)言實(shí)現(xiàn)的,并綁定了許多其他語(yǔ)言。我們將使用Python綁定。我們的目標(biāo)是創(chuàng)建一個(gè)獨(dú)立的函數(shù)SHA1Hash,它可以像Python中的任何其他普通函數(shù)一樣被調(diào)用,產(chǎn)生與MiniDuke中原始函數(shù)相同的SHA1哈希。我們使用的實(shí)現(xiàn)背后的想法非常簡(jiǎn)單——我們只需提取函數(shù)的操作碼字節(jié)并通過(guò) CPU 模擬使用它們。
提取原始函數(shù)操作碼的所有字節(jié)可以簡(jiǎn)單地通過(guò)idpython或使用IDA→Edit→Export Data來(lái)完成。
使用IDA“Export data”對(duì)話框?qū)С鯯HA1Hash函數(shù)的操作碼字節(jié)
與前面的方法一樣,我們需要設(shè)置執(zhí)行上下文。在本文示例中,這意味著為函數(shù)準(zhǔn)備參數(shù),并為提取的操作碼和輸入緩沖區(qū)設(shè)置地址。
請(qǐng)注意,應(yīng)從提取的操作碼列表中刪除最后一條 retn 指令,以免將執(zhí)行轉(zhuǎn)移回堆棧上的返回地址,并且應(yīng)通過(guò)指定 ebp 和 esp 的值手動(dòng)設(shè)置堆棧幀。所有這些都顯示在下面的最終 Python 腳本中。
腳本輸出如下所示:
腳本執(zhí)行——“Unicorn Engine”產(chǎn)生與MiniDuke示例相同的SHA1哈希值
總結(jié)
上述所有直接調(diào)用匯編的方法都各有優(yōu)缺點(diǎn)。給我們留下特別深刻印象的是簡(jiǎn)易的Dumpulator,它是免費(fèi)的,執(zhí)行起來(lái)很快,而且非常有效。它非常適合編寫(xiě)通用字符串解密器、配置提取器和其他上下文,在這些上下文中,必須依次調(diào)用許多不同的邏輯片段,同時(shí)保留難以設(shè)置的上下文。
當(dāng)我們希望直接使用調(diào)用特定函數(shù)產(chǎn)生的結(jié)果來(lái)豐富IDA數(shù)據(jù)庫(kù)時(shí),IDA Appcall功能是最好的解決方案之一。系統(tǒng)調(diào)用可以是Appcall在實(shí)際執(zhí)行環(huán)境中使用的函數(shù)的一部分——使用調(diào)試器。Appcall最大的優(yōu)點(diǎn)之一就是快速而簡(jiǎn)單的上下文恢復(fù)。由于Appcall依賴調(diào)試器,可以與idpython腳本一起使用,理論上它甚至可以作為模糊器的基礎(chǔ),向函數(shù)提供隨機(jī)輸入以發(fā)現(xiàn)意外行為(即錯(cuò)誤),但這種方法的消耗太大。
通過(guò) Unicorn Engine 使用純仿真是獨(dú)立實(shí)現(xiàn)特定功能的通用解決方案。使用這種方法,可以按原樣獲取部分代碼并在不連接到原始示例的情況下使用它。此方法不依賴于可運(yùn)行的示例,并且僅適用于部分代碼重新實(shí)現(xiàn)功能。對(duì)于不是連續(xù)的、易于轉(zhuǎn)儲(chǔ)的代碼塊的函數(shù),這種方法可能更難實(shí)現(xiàn)。對(duì)于發(fā)生 API 或系統(tǒng)調(diào)用的部分代碼,或者難以設(shè)置執(zhí)行上下文的代碼,前面提到的方法通常是更好的選擇。
本文翻譯自:https://research.checkpoint.com/2022/native-function-and-assembly-code-invocation/如若轉(zhuǎn)載,請(qǐng)注明原文地址