使用Speakeasy Emulation Framework快速分析惡意程序
Andrew Davis最近發布了他的新Windows模擬框架,名為Speakeasy。本文將強調該框架的另一個強大用途:自動大規模分析惡意程序。我將通過代碼示例演示如何通過編程方式使用Speakeasy:
- 繞過不受支持的Windows API以繼續模擬和分析;
- 使用API掛鉤保存動態分配的代碼的虛擬地址;
- 使用代碼掛鉤以外科方式直接執行代碼的關鍵區域;
- 從模擬器內存中轉儲分析的PE并修復其節頭;
- 通過查詢Speakeasy以獲得符號信息來幫助重建導入表;
初始設置
與Speakeasy交互的一種方法是創建Speakeasy類的子類。圖1顯示了Python代碼段,它設置了一個類,這個類將在后面的示例中擴展。
創建一個Speakeasy子類
圖1中的代碼接受一個Speakeasy配置字典,該字典可用于覆蓋默認配置。 Speakeasy附帶了幾個配置文件。 Speakeasy類是基礎模擬器類的壓縮器類。根據二進制文件的PE標頭加載二進制文件或將其指定為shellcode時,會自動選擇模擬器類。子類化Speakeasy使訪問,擴展或修改接口變得容易,它還有助于在模擬期間讀取和寫入狀態數據。
模擬二進制
圖2顯示了如何將二進制文件加載到Speakeasy模擬器中。
將二進制文件加載到模擬器中
load_module函數為磁盤上提供的二進制文件返回PeFile對象。它是在speakeasy/windows/common.py中定義的PeFile類的實例,該類是pefile的PE類的子類,或者,你可以使用data參數而不是指定文件名來提供二進制字節。圖3顯示了如何模擬已加載的二進制文件。
啟動模擬
API掛鉤
Speakeasy框架提供了對數百個Windows API的支持,并經常添加更多的API。這是通過定義在speakeasy/winenv/ API目錄的適當文件中的Python API處理程序來實現的。當在模擬過程中調用特定的API時,可以安裝API掛鉤以執行自己的代碼。可以為任何API安裝它們,而不管是否存在處理程序。API掛鉤可用于覆蓋現有處理程序,并且可以選擇從你的掛鉤中調用該處理程序。 Speakeasy中的API掛鉤機制提供了靈活性和對模擬的控制。讓我們研究一下在模擬分析代碼以檢索分析的有效載荷的上下文中API掛鉤的幾種用法。
繞過不受支持的API
當Speakeasy遇到不受支持的Windows API調用時,它將停止模擬并提供不受支持的API函數的名稱。如果所討論的API函數對于分析二進制文件不是至關重要的,則可以添加一個API掛鉤,該掛鉤僅返回允許執行繼續的值。例如,最近的樣本的分析代碼包含對分析過程沒有影響的API調用。這樣的API調用之一就是GetSysColor,為了繞過此調用并允許執行繼續,可以添加一個API掛鉤,如圖4所示。
添加的API掛鉤
根據MSDN,此函數采用1參數,并返回表示為DWORD的RGB顏色值。如果要掛接的API函數的調用約定不是stdcall,則可以在可選的call_conv參數中指定調用約定。調用約定常量在speakeasy/common/arch.py文件中定義。因為GetSysColor返回值不會影響分析過程,所以我們可以簡單地返回0。圖5顯示了圖4中指定的getsyscolor_hook函數的定義。
GetSysColor掛鉤返回0
如果API函數需要更精細的處理,則可以實現更具體,更有意義的掛鉤,以滿足你的需要。如果你的掛鉤實現足夠穩定,則可以考慮將其作為API處理程序添加到Speakeasy項目!
添加API處理程序
在speakeasy / winenv / api目錄中,你將找到usermode和kernelmode子目錄,這些子目錄包含對應二進制模塊的Python文件。這些文件包含每個模塊的API處理程序。在usermode / kernel32.py中,我們看到為SetEnvironmentVariable定義的處理程序,如圖6所示。
SetEnvironmentVariable的API處理程序
處理程序以函數修飾符(第1行)開頭,該修飾符定義API的名稱及其接受的參數數量。在處理程序開始時,最好的做法是將MSDN記錄在案的原型作為注釋(第3-8行)。
處理程序的代碼首先將argv參數的元素存儲在以相應API參數(第9行)命名的變量中,處理程序的ctx參數是一個字典,其中包含有關API調用的上下文信息。對于以“A”或“W”結尾的API函數(例如,CreateFileA),可以通過將ctx參數傳遞給get_char_width函數(第10行)來檢索字符寬度。然后可以將此寬度值傳遞給read_mem_string之類的調用(第12和13行),該調用在給定地址讀取模擬器的內存并返回一個字符串。
最好的作法是用相應的字符串值(第14和15行)覆蓋argv參數中的字符串指針值。這使Speakeasy能夠在其API日志中顯示字符串值而不是指針值。為了說明更新argv值的影響,請檢查圖7中所示的Speakeasy輸出。在VirtualAlloc條目中,符號常量字符串PAGE_EXECUTE_READWRITE替換值0x40。在GetModuleFileNameA和CreateFileA條目中,指針值被替換為文件路徑。
Speakeasy API日志
保存分析的代碼地址
壓縮樣本通常使用VirtualAlloc之類的函數來分配用于存儲分析樣本的內存。捕獲分析后的代碼的位置和大小的有效方法是首先掛接分析存根使用的內存分配函數。圖8顯示了掛鉤VirtualAlloc以捕獲虛擬地址和API調用分配的內存量的示例。
VirtualAlloc掛鉤可以保存內存轉儲信息
圖8中的掛鉤在第12行調用Speakeasy的VirtualAlloc的API處理程序,以分配內存。 API處理程序返回的虛擬地址將保存到名為rv的變量。由于VirtualAlloc可用于分配與分析過程無關的內存,因此在第13行使用附加檢查以確認截獲的VirtualAlloc調用是分析代碼中使用的調用。根據先前的分析,我們正在尋找一個VirtualAlloc調用,該調用將接收lpAddress值0和flProtect值PAGE_EXECUTE_READWRITE(0x40)。如果存在這些自變量,則虛擬地址和指定的大小將存儲在第15行和第16行,因此在分析代碼完成后,可以將它們用于從內存中提取分析的有效載荷。最后,在第17行,該掛鉤返回VirtualAlloc處理程序的返回值。
使用API和代碼掛鉤的外科式代碼模擬
Speakeasy是一個強大的模擬框架,但是,你可能會遇到包含大量有問題代碼的二進制文件。例如,一個示例可能會調用許多不受支持的API,或者只是花費太長時間而無法進行模擬。在以下情形中描述了克服這兩個挑戰的示例。
取消隱藏在MFC項目中的存根
一種用于掩蓋惡意有效載荷的流行技術涉及將它們隱藏在大型的開源MFC項目中。 MFC是Microsoft Foundation Class的縮寫,它是用于構建Windows桌面應用程序的流行庫。這些MFC項目通常是從流行的網站(例如Code Project)中任意選擇的,盡管MFC庫使創建桌面應用程序變得容易,但是MFC應用程序由于其大小和復雜性而難以進行反向工程。由于它們調用許多不同的Windows API的大型初始化例程,因此特別難以模擬。以下是對我使用Speakeasy編寫Python腳本以自動分析自定義壓縮程序的經驗的描述,該自定義壓縮程序將其分析存根隱藏在MFC項目中。
對壓縮程序進行反向工程后發現,在CWinApp對象的初始化過程中最終會調用分析存根,該過程在C運行時和MFC初始化之后發生。嘗試繞過不受支持的API之后,我意識到,即使成功,模擬也將花費太長時間而無法實現。我考慮過完全跳過初始化代碼,然后直接跳轉到分析存根。不幸的是,為了成功模擬分析存根,需要執行C運行時初始化代碼。
我的解決方案是在代碼中確定在C運行時初始化后落在MFC初始化例程中的早期位置,檢查完圖9所示的Speakeasy API日志后,很容易發現該位置。與圖形相關的API函數GetDeviceCaps在MFC初始化例程的早期被調用。這是基于以下事實推論得出的:1.MFC是與圖形相關的框架,2.在C運行時初始化期間不太可能調用GetDeviceCaps。
在Speakeasy API日志中標識MFC代碼的開頭
為了在此階段攔截執行,我為GetDeviceCaps創建了一個API掛鉤,如圖10所示。該掛鉤確認該函數在第2行中首次被調用。
GetDeviceCaps的API掛鉤集
第4行顯示了使用Speakeasy類的add_code_hook函數創建代碼掛鉤,代碼掛鉤允許你指定在模擬每個指令之前調用的回調函數。 Speakeasy還允許你通過指定begin和end參數來指定代碼掛鉤對其有效的地址范圍。
在第4行上添加代碼掛鉤之后,GetDeviceCaps掛鉤完成,并且在執行示例的下一條指令之前,將調用start_unpack_func_hook函數。此函數如圖11所示。
更改指令指針的代碼掛鉤
代碼掛鉤接收模擬器對象,當前指令的地址和大小以及上下文字典(第1行)。在第2行,代碼掛鉤將自身禁用。因為代碼掛鉤是與每個指令一起執行的,所以這會大大降低模擬速度。因此,應謹慎使用并盡快將其禁用。在第3行,該掛鉤計算了分析函數的虛擬地址。使用正則表達式定位用于執行此計算的偏移量。為了簡潔起見,省略了該示例。
self.module屬性先前是在圖2所示的示例代碼中設置的。它是從pefile的PE類中繼承而來的,它使我們可以在第3行訪問有用的函數,例如get_rva_from_offset()。該行還包括一個使用self的示例。 .module.get_base()以檢索模塊的基本虛擬地址。
最后,在第4行,使用set_pc函數更改了指令指針,并在分析代碼處繼續進行模擬。圖10和圖11中的代碼段使我們能夠在C運行時初始化完成后將執行重定向到分析代碼,并避免使用MFC初始化代碼。
發布和修復未壓縮的PE
一旦模擬達到了分析樣本的原始入口點,就該刪除PE并對其進行修復了。通常,一個掛鉤會將分析的PE的基址保存在該類的屬性中,如圖8的第15行所示。如果分析的PE在其PE標頭中未包含正確的入口點,則真實入口點也可能需要在模擬過程中捕獲。圖12顯示了如何將模擬器內存轉儲到文件的示例。
轉儲未壓縮的PE
如果要轉儲已加載到內存中的PE,則由于節對齊方式的不同,其布局將與磁盤上的布局不同。因此,可能需要修改轉儲的PE頭。一種方法是修改每個部分的PointerToRawData值以匹配其VirtualAddress字段。為了符合PE可選標頭中指定的FileAlignment值,可能需要填充每個部分的SizeOfRawData值。請記住,生成的PE不太可能成功執行。但是,這些努力將使大多數靜態分析工具能夠正確運行。
修復轉儲的PE的最后一步是修復其導入表,這是一個很復雜的任務,在此不詳細討論。但是,第一步涉及在模擬器內存中收集庫函數名稱及其地址的列表。如果知道分析程序存根使用GetProcAddress API來解析分析的PE的導入,則可以調用get_dyn_imports函數,如圖13所示。
查看動態導入
否則,你可以通過調用get_symbols函數來查詢模擬器類以檢索其符號信息,如圖14所示。
從模擬器類檢索符號信息
此數據可用于發現未壓縮PE的IAT并修復或重建其導入相關表。
總結
編寫Speakeasy腳本來分析惡意程序樣本可以分為以下步驟:
(1) 對分析存根進行反向工程,以識別:
- 分析的代碼將駐留在哪里或分配其內存的位置;
- 執行轉移到分析的代碼的位置;
- 任何可能引入問題的有問題代碼,例如不受支持的API,緩慢模擬或反分析檢查。
(2) 如有必要,請設置掛鉤以繞過有問題的代碼;
(3) 設置一個掛鉤以標識虛擬地址,以及可選的分析二進制文件的大小;
(4) 設置一個掛鉤,以在分析代碼的原始入口點執行時或之后停止模擬;
(5) 收集Windows API的虛擬地址并重建PE的導入表;
(6) 修復PE的標頭,并將字節寫入文件中以進行進一步分析;
(7) 有關分析UPX樣本的腳本的示例,請在Speakeasy存儲庫中查看UPX分析腳本。
Speakeasy框架提供了一個易于使用、靈活且函數強大的編程界面,使分析人員能夠解決諸如分析惡意程序之類的復雜問題。使用Speakeasy自動化這些解決方案可以使它們大規模執行。
本文翻譯自:
https://www.fireeye.com/blog/threat-research/2020/12/using-speakeasy-emulation-framework-programmatically-to-unpack-malware.html





































