實戰案例:如何使用Speakeasy輕松模擬惡意Shellcode
一、概述
為了能夠大規模模擬惡意軟件樣本,研究人員近期開發了Speakeasy模擬框架。這一框架能夠幫助非惡意軟件分析人員的用戶能輕松自動獲取分類報告,同時可以幫助逆向工程師為難以分類的惡意軟件系列編寫自定義插件。
Speakeasy最初是為了模擬Windows內核模式的惡意軟件而創建的,現在也開始支持用戶模式樣本。這一項目的主要目標是針對x86和amd64平臺進行動態惡意軟件分析的Windows操作系統的高度模擬。我們知道,目前也有類似的模擬框架可以實現對用戶模式二進制文件進行模擬,但Speakeasy的特點包括:
1、專為模擬Windows惡意軟件而設計;
2、支持內核模式二進制文件的模擬,借此可以分析難以分類的rootkit;
3、結合當前惡意軟件趨勢進行模擬和API支持,提供了無需額外工具就可以提取威脅指標的方法;
4、提供了無需其他代碼就完全可配置的模擬環境。
該項目目前支持內核模式驅動程序、用戶模式Windows DLL和可執行文件、Shellcode。可以自動模擬惡意軟件樣本,并生成報告,以讓分析人員進行后期處理。我們的下一個目標是繼續增加對新惡意軟件家族或常見惡意軟件家族的支持。
在這篇文章中,我們將展示一個案例,說明Speakeasy是如何有效地從在線惡意軟件集獲取的Cobalt Strike Beacon樣本中自動提取威脅指標的。
二、背景
Windows惡意軟件的動態分析一直是惡意軟件分析過程中的一個關鍵步驟。要想評估惡意軟件對網絡產生的實際影響,了解惡意軟件如何與Windows API交互,并提取有價值的主機、網絡威脅指標(IoC)就變得至關重要。通常情況下,研究人員都使用自動化或有針對性的方式進行動態分析。例如將惡意軟件按順序在沙箱中運行以監視其功能,或者手動進行調試以確認沙箱運行期間沒能執行的代碼路徑。
在以前,代碼模擬一直被用于測試、驗證甚至是惡意軟件分析。如果能夠模擬惡意軟件代碼,無論對于人工分析還是自動分析來說,都是很有收益的。通過對CPU指令的模擬,可以對二進制代碼進行全面檢測,在這一過程中,可以讓控制流最大程度地覆蓋代碼。在模擬過程中,可以監控并記錄所有功能,以便迅速提取威脅指標,或者其他有用的情報。
與在虛擬機沙箱中執行相比,模擬具有一些優勢,其中一個關鍵優勢就是減少環境中的噪音(誤報情況)。在模擬過程中,只有由惡意軟件作者編寫的活動才會被記錄下來,或者是在二進制文件中靜態編譯的。在虛擬機中的API Hooking(特別是在內核模式中),可能很難被歸因于惡意軟件本身。舉例來說,沙箱解決方案經常會Hook堆分配器API調用,但它們通常無法判斷惡意軟件作者是否打算分配內存,還是在較低級別API負責內存分配。
但是,模擬的方式也有缺點。由于我們在分析的階段就脫離了操作系統,因此模擬器需要負責提供API調用和模擬期間所發生的內存訪問的預期輸入和輸出。為了成功模擬在正常Windows系統上運行的惡意軟件樣本,這需要大量的精力。
三、Shellcode攻擊方式分析
通常,Shellcode是讓攻擊者在受感染系統上保持隱身的絕佳選擇。Shellcode在可執行的內存中運行,不需要依賴于磁盤上的任何文件。這就使得攻擊者的代碼可以輕松隱藏在大多數形式的傳統取證都無法識別的內存之中。必須首先確定加載Shellcode的原始二進制文件,或者必須從內存中轉儲Shellcode本身。為了逃避檢測,攻擊者可以將Shellcode隱藏在合法的加載程序中,然后注入到另一個用戶模式進程之中。
在文章的第一部分,我們將展示在應急響應過程中遇到的一個較為常見的Shellcode惡意軟件樣本,并分析如何對其進行模擬。Cobalt Strike是一個商業的滲透測試框架,通常使用分階段的程序來執行其他代碼。其中的一個階段,往往是通過HTTP請求下載其他代碼,并執行HTTP響應的數據內容。在這種情況下,這個響應的數據就成為了Shellcode,通常要對內容進行解碼,然后得到有效的PE,PE中包含反射加載其自身的代碼。在Cobalt Strike框架中,這種情況的Payload通常是被稱為Beacon的植入工具。Beacon被設計為駐留在內存中的后門,用于在受感染的Windows系統上維持命令與控制(C2)。由于它是使用Cobalt Strike框架構建的,所以無需進行任何代碼層面上的修改,就能夠輕松調整其核心功能和命令與控制信息。
上述這些特點,促使攻擊者能夠在受感染的網絡中快速構建和部署新的Beacon注入工具變種。因此,我們就需要使用一種工具來快速提取Beacon的變種組件,并且在理想情況下,最好不要占用惡意軟件分析人員的過多寶貴時間。
四、Speakeasy原理分析
Speakeasy當前使用基于QEMU的模擬器引擎Unicorn來模擬x86和amd64體系結構的CPU指令。Speakeasy未來希望通過抽象層支持任意模擬引擎,但目前還是依賴于Unicorn。
如果要借助沙箱分析所有樣本,可能需要使用完整的操作系統沙箱來分析全量樣本,這種情況下就很難模擬所有版本的Windows系統。沙箱可能難以按照需要去擴展,并且運行樣本需要花費大量的時間。但是,如果能夠確定特定的惡意軟件家族(例如案例中的Beacon),我們可以大幅減少需要進行逆向工程的變種的數量。在對惡意軟件的變種進行分析的過程中,最好是可以以自動化的方式來生成一個高級的分類報告。這樣一來,就會讓惡意軟件分析人員有更多的時間專注于可能需要更深入分析的樣本。
使用Speakeasy時,Shellcode或Windows PE被加載到模擬地址空間中。在嘗試模擬惡意軟件之前,首先創建了Windows內核模式和用戶模式要進行基本模擬所需的Windows數據結構。進程、驅動程序、設備和用戶模式庫都是偽造的,這樣可以為惡意軟件提供一個近乎真實的執行環境。惡意軟件可以與模擬的文件系統、網絡、注冊表繼續你功能交互。所有這些模擬的子系統都可以使用獨立的配置文件進行配置。
Windows API由Python API處理程序進行處理。這些處理程序將嘗試模擬這些API的預期輸出,以便惡意軟件樣本繼續預期的執行路徑。在定義API處理程序時,我們所需要的就是API的名稱、API期望的參數數量以及可選的調用約定規范。如果沒有提供任何調用約定,則假設為stdcall。在目前版本中,如果嘗試了不支持的API調用,Speakeasy將記錄不支持的API,并繼續進行下一個入口點。下圖展示了由kernel32.dll導出的Windows HeapAlloc函數的示例處理程序。
默認情況下,會模擬所有入口點。例如,對于DLL來說,會模擬所有導出;對于驅動程序,會分別模擬IRP主要函數。此外,還會跟蹤在運行時發現的動態入口點。其中一些動態入口點包括創建的線程或注冊的回調。在嘗試確定惡意軟件的影響時,將活動與特定的入口點關聯起來對于全局來說至關重要。
五、導出報告
當前,模擬工具捕獲的所有事件都會記錄在JSON格式的報告中,以便后續處理。報告中包含在模擬過程中記錄的關鍵事件。與大多數模擬工具一樣,所有Windows API調用及其參數都會被記錄。所有的入口點都會被模擬,并且標記相應的API列表。除了API跟蹤外,還會記錄其他特定事件,包括文件、注冊表和網絡訪問。所有解碼后或駐留在內存中的字符串都會被轉儲并展現在報告中,從而揭示出通過靜態字符串分析無法找到的關鍵信息。下圖展示了Speakeasy的JSON報告中記錄的文件讀取事件的示例:
六、運行速度
由于該框架是使用Python編寫的,所以速度是一個明顯需要關心的維度。Unicorn和QEMU是使用C編寫的,可以提供非常快的模擬速度。但是,我們編寫的API和事件處理程序使用了Python。本地代碼和Python之間的轉換過程會花費一定時間,應該盡量減少。所以,我們的目標就是僅在絕對必要時再執行Python代碼。默認情況下,我們用Python處理的唯一事件就是內存訪問異常或Windows API調用。為了捕獲Windows API調用并在Python中進行模擬,導入表中加入了無效的內存地址,因此我們僅在訪問導入表時才會切換到Python。當Shellcode訪問惡意軟件的模擬地址空間中加載的DLL導出表時,也會使用類似的技術。通過執行盡可能少的Python代碼,我們可以控制該框架在一個合理的速度,同時我們允許用戶開發該框架的功能。
七、內存管理
Speakeasy在模擬工具引擎的內存管理方面實現了輕量級。跟蹤并標記由惡意軟件分配的每個內存塊,就可以獲取到有意義的內存轉儲。對于分析人員來說,能夠將惡意活動與特定的內存塊進行關聯會非常有幫助。通過記錄對敏感數據結構的內存讀寫,就可以揭示出API調用記錄可能無法體現的惡意軟件真實意圖,這對于rootkit樣本來說特別有用。
Speakeasy提供了可選的“內存跟蹤”功能,該功能將記錄樣本涉及到的所有內存訪問。會記錄所有讀取、寫入和執行的內存。由于模擬工具標記了所有分配的內存塊,因此可能會從該數據中收集到更多上下文。如果惡意軟件對關鍵數據結構進行了Hook,或者將執行轉移到動態映射的內存中,那么通過這一過程就能有效發現,對于調試或分析來說很有幫助。但是,這一功能的資源消耗較高,默認情況下是關閉的。
提供給惡意軟件的模擬環境中包括常見的數據結構,Shellcode可以在其中查找并執行導出的Windows系統功能。為了調用Win32 API,必須解析導出的函數,這樣才能對目標系統產生有意義的影響。在大多數情況下(包括Beacon),這些函數是通過遍歷進程環境塊(Process Environment Block,PEB)來定位的。Shellcode可以從PEB訪問進程的虛擬地址空間中所有已加載模塊的列表。
下圖展示了通過模擬Beacon Shellcode樣本生成的內存報告。在這里,我們可以看到惡意軟件遍歷PEB,以查找kernel32.dll的地址。然后,惡意軟件會手動解析并調用VirtualAlloc API的函數指針,然后繼續進行解碼,將其自身復制到新緩沖區中,以控制執行。
內存跟蹤報告:
八、配置
Speakeasy是高度可配置的,允許用戶創建自己的配置文件。其中,可以指定不同的分析程度,以優化各個用例。最終目標是讓用戶在不調整代碼的情況下輕松切換配置選項。當前,配置文件的結構是JSON文件。如果用戶沒有提供配置文件,則使用框架的默認配置。哥哥字段都記錄在Speakeasy的項目文檔中。
下圖展示了網絡模擬工具配置部分的代碼片段。在這里,用戶可以指定在進行DNS查找時返回哪些IP地址,或者在某些Beacon樣本的案例中,指定在TXT記錄查詢期間返回哪些二進制數據。同樣,也可以自定義HTTP響應。
網絡配置:
許多惡意軟件在HTTP階段都會使用HTTP GET請求來檢索Web資源。比如Cobalt Strike或Metasploit在這一階段,會立即執行響應的內容,以繼續下一階段。可以使用Speakeasy的配置文件對響應的內容進行輕松配置。在上圖的配置中,除非被覆蓋,否則框架會返回default.bin文件中包含的數據進行響應。該文件當前包含調試中斷指令(int3),因此,如果惡意軟件嘗試執行數據,就會退出,并記錄在報告中。使用這個工具,我們可以輕松的將惡意軟件分類為“可下載其他代碼”的下載工具。類似地,還可以配置文件和注冊表路徑,將返回數據變為希望在Windows系統上運行的樣本。
九、限制
如前所述,模擬的方式還帶來了一些挑戰。如何與被模擬的系統保持一致的功能,這對我們來說是一場持續的戰斗。但是,這也提供了控制惡意軟件和包含更多功能的獨特機會。
在模擬沒有完全完成的情況下,仍然可以生成模擬報告和內存轉儲,以收集盡可能多的數據。例如,后門可能會成功安裝其持久性機制,但無法連接到C2服務器。在這種情況下,仍然能夠記錄下來主機層面的指標,為分析人員提供價值。
同時,可以輕松地將缺少的API處理程序添加到模擬工具中,以應對這些情況。對于許多API處理程序來說,僅返回成功代碼就足以使惡意軟件繼續執行。盡管不可能對每個惡意軟件進行完全模擬,但針對特定惡意軟件家族的功能可以極大地減少逆向工程相同家族變種的需求。
十、使用方法
Speakeasy目前已經在GitHub上發布。可以使用項目中的Python安裝程序腳本安裝,也可以使用提供的Dockerfile安裝在Docker容器中。該框架不受限于平臺,可以在Windows、Linux或macOS中模擬Windows惡意軟件。有關更多信息,請參見項目的README文件。
安裝后,Speakeasy可以作為獨立的庫,也可以使用提供的run_speakeasy.py腳本直接調用。在這篇文章中,我們將演示如何從命令行模擬惡意軟件樣本。關于如何將Speakeasy作為庫使用的信息,請參考項目的README文件。
其中包含的腳本用于模擬單個樣本,并根據記錄的事件生成JSON報告。下圖展示了run_speakeasy.py的命令行參數。
Speakeasy還提供了豐富的開發和Hooking接口,用于編寫自定義插件。
十一、實戰:Beacon注入工具的模擬過程
對于這一樣本,我們將模擬Shellcode,該Shellcode解碼后執行Beacon植入程序變種,這個變種的SHA-256哈希值為7f6ce8a8c2093eaf6fea1b6f1ef68a957c1a06166d20023ee5b637b5f7838918。我們首先要驗證樣本的文本格式。該樣本應該由加載程序啟動,或者作為漏洞利用Payload的一部分。
惡意軟件的Hex轉儲示例:
從上圖,我們可以清楚地看到該文件不是PE文件格式。有Shellcode樣本分析經驗的分析人員可能會注意到前兩個字節——0xfc 0xe8。這些字節可以反匯編為Intel的匯編指令cld和call。cld指令通常是獨立Shellcode的前奏,因為它將清除方向標記,使惡意軟件可以輕松地分析系統DLL導出表中的字符串數據。Shellcode通常使用后面的call指令,通過在其后跟隨pop指令來獲取當前程序計數器。這樣,惡意軟件就可以從內存中獲取其執行位置。
由于我們確定該樣本是Shellcode,那么我們就使用命令行調用Speakeasy,如下圖所示。
使用命令行模擬惡意軟件樣本:
該命令將指示Speakeasy將偏移量為0的樣本模擬為x86 Shellcode。請注意,即使我們正在模擬代碼,并沒有實際執行代碼,但它們仍然是攻擊者生成的二進制文件。因此,如果要使用本地CPU模擬引擎,最好還是在虛擬機中模擬惡意代碼。
在模擬后,將生成一個名為report.json的報告。此外,還會將模擬環境的完整內存轉儲壓縮后寫入memory_dump.zip。該惡意軟件將被加載到偽造的容器進程內部的模擬內存中,以模擬Shellcode期望的真實執行環境。一旦開始模擬,模擬的API調用將會連同其參數和返回值一起記錄到屏幕上。下圖展示了Beacon示例,該示例分配了一個新的內存緩沖區,將在其中復制自身。然后,惡意軟件開始手動解析它需要執行的導出。
網絡配置:
經過額外的解碼和設置后,惡意軟件嘗試連接到其C2服務器。在下圖中,我們可以看到惡意軟件使用Wininet庫,通過HTTP連接并讀取了C2服務器中的數據。
用于連接到C2的Wininet API調用:
惡意軟件將無限次循環,直到從C2服務器接收到期望的數據為止。Speakeasy將在預定時間后超時,并生成JSON報告。
網絡C2事件:
在生成報告的“network_events”和“traffic”部分中,匯總了網絡指標。在上圖中,我們可以看到IP地址、端口號,還可以看到與惡意軟件建立的連接相關的HTTP標頭。
對于這個樣本來說,當我們模擬樣本時,我們指示Speakeasy創建模擬地址空間的內存轉儲。這會為每個內存分配及周圍的上下文創建一個ZIP壓縮包,其中包括基址、大小、由模擬工具分配的標簽(用于標識內存分配對應的內容)。下圖展示了在模擬過程中創建的內存轉儲文件的摘要。文件名包含與每個內存分配關聯的標簽和基址。
通過模擬獲得的單個內存塊:
如果僅在這些內存轉儲上運行字符串,就可以快速找到值得關注的字符串和Beacon配置數據,如下圖所示。
惡意軟件的配置字符串數據:
在分類的分析中,我們可能僅僅關心已知家族的惡意軟件變種的威脅指標。但是,如果需要對樣本進行完整的逆向工程,我們還可以以DLL形式恢復Beacon惡意軟件的解碼后版本。通過簡單地對MZ魔術字節執行原始的grep,我們唯一得到的結果就是與原始樣本分配相關的內存轉儲,以及惡意軟件將自身復制到的虛擬分配緩沖區。
包含已解碼惡意軟件的內存轉儲:
如果我們查看原始Shellcode緩沖區中的字節,就可以看到它在復制之前已經被解碼,并且位于內存中,準備從偏移量0x48進行轉儲。現在,我們可以成功地將解碼后的Beacon DLL加載到IDA Pro中進行全面分析。
已解碼的惡意軟件成功加載到IDA Pro中:
十二、總結
在這篇文章中,我們展示了如何使用Speakeasy模擬框架自動分類Beacon惡意軟件樣本。我們用它來發現有價值的網絡指標,從內存中提取配置信息,并得到了解碼后的Beacon DLL,以進行進一步分析。