對於逆向工程人員來說,他們會經常使用模擬技術來對抗此示例中的函數調用混淆和字符串加密。我們將使用flare-emu 框架實現一個IDAPython 腳本,以使IDA Pro 中的反彙編更具可讀性。這將對樣品的靜態分析有很大幫助。
以Pandora為例在這篇文章中,我將討論在Pandora 中看到的惡意程序研發人員使用的兩種特定的反逆向工程技術:
1.使用不透明謂詞進行函數調用混淆;
2.加密字符串;
使用不透明謂詞的函數調用混淆下圖顯示了Pandora 勒索軟件中一個簡單的函數調用在解壓後的樣子。
Pandora 中的標準函數調用
我們可以看到正在調用的函數的地址是在運行時計算的。 cs:qword_7FF6B6FF9AB8 似乎是某種函數地址表的基地址。然後我們使用硬編碼值在該表中找到正確的函數指針,這就是我們在調用它之前加載到rax 中的內容。不透明謂詞通常表示程序中的表達式,其結果為程序員所知,但仍需要在運行時進行評估。它以許多不同的方式用作混淆和反分析技術。在這種情況下,進入rax 的值是固定的,但是因為它仍然必須在運行時計算,它會破壞靜態分析工具。
如果我們以上圖為例,rax 中的地址是這樣計算的:
或十進制:
此類問題的簡單解決方案是在調試器中運行惡意軟件並從那裡獲取地址。但是在這個示例中,所有函數調用都是這樣的,靜態鏈接庫中的函數調用除外。這意味著我們需要在調試器中的每個函數調用處中斷,以便對惡意軟件中發生的事情進行自動化處理。
加密字符串這個特定勒索軟件樣本的另一個挑戰是所有有趣的字符串都被加密了。二進製文件中有很多純文本字符串(如下圖所示),但它們大多是Windows API 函數名稱和嵌入式庫中的字符串。沒有任何可以幫助我們了解惡意軟件正在做什麼的字符串以純文本形式提供。這在現代惡意軟件中非常常見,因此該挑戰的解決方案可用於對抗各種惡意軟件。
Pandora 示例中的字符串
通常當遇到帶有加密字符串的惡意軟件時,有兩種方法:
1.使用動態方法,例如調試或模擬,並使用惡意軟件自己的字符串解密函數來完成工作。
2.如此詳細地了解解密功能,以至於可以在一個簡單的腳本中重新實現它。當加密是簡單的單字節異或時,這通常是最簡單的途徑。
就Pandora 而言,至少有14 個不同的字符串解密函數,因此重新實現解密算法可能並不總是可行的。
模擬模擬允許我們假裝代碼運行在CPU 上,但模擬軟件運行的不是真正的CPU,而是運行代碼。與實際執行相比,模擬通常非常慢。但是,它允許我們完全控制我們想要運行的內容以及與模擬代碼的高度交互。例如,使用模擬器,我們可以只模擬惡意軟件的一個功能,甚至只是幾行代碼,並在每條指令處評估程序的狀態。在這種情況下,模擬的一大優勢是我們可以直接在IDA Pro 中進行。
flare-emuflare-emu 是由Mandiant 的FLARE 團隊創建的模擬框架。它建立在著名的模擬引擎Unicorn Engine 和IDAPython 之上。可以直接使用Unicorn 引擎,但flare-emu 隱藏了它的一些複雜性。本質上,人們可以定義想要模擬的內容,並為特定的掛鉤定義回調函數,當模擬到達該掛鉤時將調用這些函數。一個很好的例子是callHook 參數,它接受一個回調函數,每次將要模擬CALL 指令時調用該函數。在這個回調函數中,我們可以實現在那種情況下我們想做的任何事情,即轉儲寄存器、更改數據、跳過調用等。 flare-emu 變得非常簡單且相對易於使用。
解決挑戰我們可以編寫一些代碼來使用IDAPython 腳本解決這些挑戰。
函數調用混淆下圖再次顯示了我們首先要解決的問題。這是Pandora 代碼解包部分中main() 函數中的第一個函數調用。我們可以相當確定,如果我們模擬main() 函數並在調用之前檢查rax 的值,那麼我們會得到正確的結果。我們也可以讀出函數調用的參數,並將所有這些信息作為IDA Pro 中的註釋添加到彙編代碼中。
函數調用混淆
讓我們開始整理我們的IDAPython 腳本。下圖顯示了我們如何初始化模擬。當我們啟動腳本時,它應該模擬IDA 中光標當前所在的函數(由get_screen_ea() 返回)。
初始化模擬
要初始化flare-emu,我們只需要實例化一個EmuHelper。 Flare-emu 提供了不同的方式來運行我們的模擬。我們使用emulateRange() 函數,它用於指定我們想要模擬的內存範圍。我們將起始地址設置為函數的開頭,結束地址可以省略(python 中為None),這意味著模擬將一直運行,直到到達返回類型指令。請注意,iterateAllPaths() 而不是emulateRange() 也應該可以工作,但是由於Pandora 中的另一種混淆技術導致了問題,這不在本文的討論範圍內。但在不太複雜的惡意軟件中iterateAllPaths() 可能是更好的選擇。
當調用flare-emu 的一個模擬函數(在這種情況下為emulateRange())時,模擬開始。該框架允許我們為模擬提供額外的細節,例如帶有寄存器和堆棧的處理器狀態,或回調函數的數據,但我們現在不需要這些。
emulateRange() 允許我們為不同的掛鉤定義回調函數:
1.指令掛鉤:在模擬每條指令之前調用。我用它為IDA 中模擬的每條指令塗色,以可視化模擬的覆蓋範圍。
2.調用掛鉤:每當模擬CALL 類型的指令時調用。請注意,默認情況下不會模擬被調用的函數。
3.內存訪問掛鉤:每當訪問內存以進行讀取或寫入時調用。
對於我們當前的任務,我們只需要callHook。如上圖中的第9 行所示,我們已經將call_hook 函數名稱作為callHook 參數傳遞。接下來,我們需要定義callHook 函數,如下圖所示。
call_hook() 的第一個實現
我們創建了call_hook() 函數,每次在模擬CALL 指令之前,模擬器都會調用該函數。在其當前狀態下,該函數將記錄它已被執行,然後使用analysisHelper 檢查當前CALL 指令中的操作數是否為寄存器。如果沒有,那麼我們可以返回,因為只有註冊案例對我們來說是有趣的。然後我們恢復寄存器的名稱(operand_name) 及其值(operand_name) 並暫時記錄它們。如果我們針對main 函數運行腳本,那麼我們會得到下圖中的結果。請注意,由於Pandora 代碼中存在許多其他惡意混淆,這個簡單的腳本將無法模擬整個函數。但這可以通過擴展腳本來完成。
第一次測試的結果
通過模擬找到了三個CALL 指令並打印了操作數寄存器的值。仔細想想,我們基本上解決了函數調用混淆的問題,因為我們現在知道不同的CALL 指令調用了哪些地址。現在我們只需要將它添加到IDA 中的反彙編中。這些是每當我們解析CALL 指令時我們想要在IDA 中做的事情:
1.添加一個帶有被調用函數地址的註釋。
2.為該函數調用添加帶有參數的註釋。
在IDA 中為調用的函數添加交叉引用
更新後的代碼如下圖所示。
添加評論和交叉引用
在創建評論時,我們使用了來自flare-emu 的一個功能。它允許我們以獨立於架構的方式獲取函數參數。這個惡意軟件是x86_64,所以我們可以只使用rcx、rdx、r8、r9 和堆棧。調用掛鉤獲取的參數之一是參數變量,它將包含flare-emu認為是此函數調用的參數的值。當然,如果不分析被調用的函數,我們將不知道需要多少參數,所以我們只打印所有參數。
最後(第23 行)我們添加了一個IDA 交叉引用,這將對我們的分析有很大幫助。如果我們在main 函數上再次運行此代碼,我們會得到下圖中的結果。
函數調用解析的結果
加密字符串現在我們已經解決了第一個問題,並且有了一個可以使用的模擬框架,我們可以繼續我們的第二個挑戰,解密字符串。為了能夠知道要模擬哪個函數來解密字符串,我們唯一的要求是我們需要知道哪些函數是解密函數。與往常一樣,逆向工程是一個迭代過程。一旦我們運行我們在main 函數上編寫的腳本,那麼我們就可以開始分析調用的函數了。那麼我們如何判斷一個函數是否是一個解密函數呢?
1.我們在IDA 中看到了這一點。無需深入研究0x7ff6b6f971e0 處的函數,我們可以在圖形視圖中看到它相當簡單並且有一些循環。
0x7ff6b6f971e0 處函數的圖形視圖
如果我們滾動瀏覽代碼,我們會在下圖中找到基本塊,我們可以看到它迭代了某個值並對其進行異或。這表明它可能是基於XOR 的編碼/加密。
XOR 表示解碼/解密
2.我們在調試器中看到它。在進行靜態分析的同時,我們當然也可以調試惡意軟件(在安全的環境中)。在調試器中,當我們看到一個函數獲取一些地址作為輸入並返回一個字符串時,這可能意味著它是一個解密函數。下圖顯示了0x7ff6b6f971e0 處的函數何時返回,並且確實在rcx 中返回了字符串“ThisIsMutexa”。
解密後的字符串出現在rcx 中
一旦我們知道一個函數是一個解密函數,我們就可以相應地重命名它(我們使用了mw_decrypt_str())。有趣的是,Pandora 使用了多個解密函數,隨著我研究深入,我們慢慢發現了這些函數。最後,我們確定了14 個不同的解密函數,但其中大多數看起來與圖9 非常相似,這使我們能夠快速查看一個函數是否只是另一個解密函數。
一旦我們知道了(一些)解密函數,我們就可以改進我們的idpython腳本,以便在看到解密函數被調用時模擬函數調用。這實際上非常類似於flare-emu文檔中的一個示例,該文檔展示了此類代碼通常可以很好地重用。
下圖顯示了更新後的call_hook() 函數。從第23 行開始,我們首先檢查我們正在調用的地址處的函數是否具有包含字符串mw_decrypt_str 的名稱。這就是我們判斷被調用函數是否為解密函數的方式。
向call_hook() 添加解密
如果它是一個解密函數,那麼我們在腳本中調用decrypt()函數。這將返回解密後的純文本字符串。然後,我們創建一個註釋,其中也將包含解密後的字符串。
解密過程如下圖所示。我們創建一個新的EmuHelper 實例,在啟動emulateRange 時,我們使用函數名稱(fname) 來獲取函數的地址作為起始地址。我們還將argv 數組的前四個元素作為參數寄存器傳遞。最後我們返回argv[0]中的值。
模擬解密進程
在IDA 中運行腳本後,結果如圖12 所示。解密後的字符串是ThisIsMutexa,它被添加到註釋中並記錄在輸出中。
字符串解密成功
現在我們可以自動解密字符串了。隨著我們對代碼的分析和更多解密函數的發現,我們可以在調用這些解密函數的函數上重新運行腳本來恢復純文本字符串。
結論Pandora 勒索軟件包含混淆和反逆向工程技術。在這篇文章中,我們研究了其中兩個:函數調用混淆和字符串加密。我們使用flare-emu 模擬框架編寫了一個IDAPython 腳本來解析函數調用的地址和參數,並模擬解密函數以將字符串恢復為純文本。最終腳本可以進一步開發,以應對Pandora 勒索軟件深入分析中討論的其他反逆向工程挑戰。
Recommended Comments