惡意軟件分析:利用Speakeasy仿真執行內核態Rootkits
概述
在2020年8月,我們發布了一篇文章,內容涉及到如何使用Speakeasy仿真框架來模擬用戶模式的惡意軟件,例如Shellcode。我們建議還沒有讀過這篇文章的讀者首先閱讀這篇文章。
除了用戶模式仿真之外,Speakeasy還支持內核模式Windows二進制文件的仿真。當惡意軟件作者使用內核模式的惡意軟件時,通常會采用設備驅動程序的形式,其最終目標是完全感染目標系統。該惡意軟件通常不與硬件交互,而是利用內核模式完全破壞系統并保持隱蔽性。
動態分析內核惡意軟件面臨的挑戰
理想情況下,可以使用反匯編程序之類的工具對內核模式的樣本進行靜態分析。但是,二進制加殼程序和用戶模式樣本一樣,容易混淆內核惡意軟件。另外,靜態分析通常成本較高,且耗費大量時間。如果我們的任務是自動分析同一惡意軟件家族的多個變種,那么可以動態分析惡意驅動程序樣本。
與用戶模式樣本相比,對內核模式惡意軟件的動態分析可能會涉及更多內容。為了調試內核惡意軟件,需要創建適當的環境。這個過程通常會涉及到將兩個獨立的虛擬機設置為調試器和被調試器。然后,可以將該惡意軟件作為按需的內核服務進行加載,這里可以使用WinDbg之類的工具遠程調試驅動程序。
此外,還存在一些使用掛鉤或其他監視技術的沙箱型應用程序,但通常以用戶模式應用程序為目標。對于內核模式代碼來說,也有類似的沙箱監視工作,會需要用到比較深入的系統級掛鉤,這可能會產生很大的噪音。
驅動程序仿真
對于惡意程序來說,仿真是一種有效的分析技術。通過這種方式,我們不需要自定義設置,并且可以大規模模擬驅動程序。此外,與沙箱環境相比,這種方式更容易實現更大的代碼覆蓋范圍。通常,rootkit可能會通過I/O請求數據包(IRP)處理程序(或其他回調)公開惡意功能。在普通的Windows系統上,當其他應用程序或設備向驅動程序發送輸入/輸出請求時,將會執行這些例程。這里包括一些常見的任務,例如讀取、寫入或將設備I/O控制(IOCTL)發送給驅動程序以執行某種類型的功能。
使用仿真的方式,可以使用IRP數據包直接調用這些入口點,以便在rootkit中標識盡可能多的功能。正如我們在第一篇Speakeasy文章中所討論的,在發現其他入口點時會對其進行仿真。驅動程序的DriverMain入口點負責初始化一個函數分配表,該表將被調用以處理I/O請求。在主入口點完成后,Speakeasy將嘗試通過提供虛擬IRP來仿真這些函數。此外,按順序模擬所有創建的系統線程或工作項,以盡可能覆蓋更多的代碼。
仿真內核模式植入工具
在這篇文章中,我們將展示Speakeasy在仿真真實內核模式植入工具(Winnti)的一個案例。盡管我們之前就使用過這個示例,但在本文中還是選擇了它,因為它以比較透明的方式實現了一些經典的rootkit功能。這篇文章并不是對惡意軟件本身進行分析,因為惡意軟件已經過時了,相反,我們將專注于仿真期間捕獲到的事件。
我們所分析的Winnti樣本的SHA-256哈希值為c465238c9da9c5ea5994fe9faf1b5835767210132db0ce9a79cb1195851a36fb,其原始文件名為tcprelay.sys。在本文的大部分案例中,我們都將檢查Speakeasy生成的仿真報告。注意,由于內核補丁程序保護(PatchGuard)可以防止對關鍵內核數據結構進行修改,因此這個32位rootkit所采用的許多技術在現代64位版本的Windows上將不起作用。
首先,我們將使用Speakeasy按照下圖所示的命令行來仿真內核驅動程序。我們指示Speakeasy創建一個完整的內核轉儲(使用“-d”標志),以便后續可以獲取內存。這里還加上了內存跟蹤標志(“-m”),它將記錄惡意軟件執行的所有內存讀取和寫入操作。這對于檢測掛鉤和直接內核對象操縱(DKOM)等非常有幫助。
用于仿真惡意驅動程序的命令行:

隨后,Speakeasy將開始仿真惡意軟件的DriverEntry函數。驅動程序的入口點負責設置被動回調例程,該例程將為用戶模式I/O請求以及用于設備添加、移除和卸載的回調提供服務。我們可以查看惡意軟件DriverEntry函數的仿真報告(在JSON報告中以“epry_point”的“ep_type”標識),表明該惡意軟件找到了Windows內核的基址。該惡意軟件通過使用ZwQuerySystemInformation API來找到所有內核模塊的基址,然后查找一個名為“ntoskrnl.exe”的工具來實現此目的。隨后,惡意軟件會手動找到PsCreateSystemThread API的地址。然后,它用于啟動系統線程以執行其實際功能。下圖展示了從惡意軟件入口點調用的API。
tcprelay.sys入口點中的關鍵功能:

隱藏驅動程序對象
該惡意軟件會在執行其主系統線程之前嘗試隱藏自身。惡意軟件首先在自己的DRIVER_OBJECT結構中查找“DriverSection”字段。該字段包含一個帶有所有已加載內核模塊的鏈表,惡意軟件嘗試將自身取消鏈接,從而在列出已加載驅動程序的API中隱藏。在下圖Speakeasy報告中的“mem_access”字段中,我們可以看到在其前后分別對DriverSection條目進行了兩次內存寫入,這會將其自身從鏈表中刪除。
表示tcprelay.sys惡意軟件的內存寫入事件,嘗試將自身取消鏈接以實現隱藏:

如之前的Speakeasy文章所述,當在運行時創建線程或其他動態入口點時,框架會跟隨它們進行仿真。在這種情況下,惡意軟件會創建一個系統線程,而Speakeasy會自動對其進行仿真。
我們轉到新創建的線程(由“system_thread”的“ep_type”標識),可以看到惡意軟件開始了真正的功能。該惡意軟件首先遍歷主機上所有正在運行的進程,然后查找名為services.exe的服務控制器進程。最重要的是,通過對JSON配置文件進行配置,可以記錄仿真樣本的進程列表。有關這些配置選項的更多信息,可以參見我們GitHub倉庫上的Speakeasy README。這個可配置進程列表的示例如下圖所示。
提供給Speakeasy的進程列表配置字段:

轉到用戶模式
一旦惡意軟件找到了services.exe進程,它將附加到進程上下文,并開始檢查用戶模式內存,以便找到導出的用戶模式函數的地址。惡意軟件會這樣做,以便可以將編碼的、駐留在內存的DLL注入到services.exe進程中。下圖展示了rootkit用于解決其用戶模式導出的API。
tcprelay.sys rootkit用于解決其用戶模式植入工具導出問題的日志API:

在解決了導出函數之后,rootkit準備注入用戶模式DLL組件。接下來,惡意軟件手動將內存中的DLL復制到services.exe進程地址空間中。這些內存寫入事件被捕獲到,如下圖所示。
將用戶模式植入復制到services.exe時捕獲到的內存寫入事件:

Rootkit用于執行用戶模式代碼的常用技術依賴于一種名為“異步過程調用”(Asynchronous Procedure Calls,APC)的Windows功能。APC是在提供的線程的上下文中異步執行的函數。使用APC,內核模式應用程序可以將代碼以隊列方式在線程的用戶模式上下文中運行。惡意軟件通常會希望注入用戶模式,因為這樣一來,就可以更容易地訪問Windows內的許多常用功能(例如網絡通信)。此外,如果在用戶模式下運行,在代碼Bug排查過程中被發現的概率較小。
為了讓APC按照隊列順序以用戶模式啟動,惡意軟件必須將線程定位為Alertable的狀態。當線程將執行交給內核線程調度程序并通知內核可以分配APC時,通常就被稱為是“可警告的”(線程等待狀態)。惡意軟件會在services.exe進程中搜索線程,一旦檢測到可警告的線程,它將為DLL分配內存以記性呢注入,然后將APC按照隊列順序執行。
Speakeasy模擬這個過程中涉及到的所有內核結構,特別是為Windows系統上的每個線程分配的執行線程對象(ETHREAD)結構。惡意軟件可能會嘗試遍歷這種不透明的結構,以識別何時設置了線程的警告標志(APC的有效候選對象)。下圖展示了Winnti惡意軟件在services.exe進程中手動解析ETHREAD結構以確認其處于可警告狀態時記錄的內存讀取事件。在撰寫本文時,默認情況下,仿真器中的所有線程都將自己表示為可警告的狀態。
tcprelay.sys惡意軟件確認線程是否處于可警告狀態時記錄的事件:

接下來,惡意軟件可以使用此線程對象執行所需的任何用戶模式代碼。其中未記錄的函數KeInitializeApc和KeInsertQueueApc將分別初始化并執行用戶模式APC。下圖顯示了惡意軟件用于將用戶模式模塊注入到services.exe進程的API集。該惡意軟件執行一個Shellcode stub作為APC的目標,然后將為注入的DLL執行一個加載程序。所有這些都可以從內存轉儲中恢復,并在后續進行分析。
tcprelay.sys rootkit用于通過APC注入到用戶模式的日志API:

網絡掛鉤
在注入到用戶模式后,內核組件將嘗試安裝網絡混淆掛鉤(推測是用于隱藏用戶模式植入工具)。Speakeasy跟蹤并標記仿真空間中的所有內存。在內核模式仿真的上下文中,這包括所有內核對象(例如驅動程序、設備對象以及內核模塊本身)。我們觀察到惡意軟件將其用戶模式植入后,立即開始嘗試掛鉤內核組件。根據靜態分析的結果,我們確認它用于網絡隱藏。
仿真報告的內存訪問部分顯示,該惡意軟件修改了netio.sys驅動程序,特別是在名為NsiEnumerateObjectsAllParametersEx的導出函數中的代碼。當系統上的用戶運行“netstat”命令時,最終會調用這個函數,與此同時惡意軟件可能會對這個函數進行掛鉤,以隱藏受感染系統上已連接的網絡端口。這個內聯掛鉤使用了下圖所示的事件標識。
惡意軟件設置的內聯函數掛鉤,用于隱藏網絡連接:

此外,惡意軟件還會掛鉤TCPIP驅動程序對象,以實現其他的網絡隱藏功能。具體而言,該惡意軟件將TCPIP驅動程序的IRP_MJ_DEVICE_CONTROL處理程序掛鉤。查詢活動連接時,用戶模式代碼可能會將IOCTL代碼發送給這個函數。通過查找對關鍵內核對象的內存寫入,可以使用Speakeasy輕松識別這種掛鉤,如下圖所示。
用于掛鉤TCPIP網絡驅動程序的內存寫入事件系統服務調度表掛鉤:

系統服務分配表掛鉤
最后,rootkit將嘗試使用系統服務分派表(SSDT)修補這種近乎古老的技術來隱藏自身。Speakeasy分配了偽造的SSDT,以便惡意軟件可以與其進行交互。SSDT是一個函數表,可以將內核功能公開給用戶模式代碼。下圖中的事件表明,SSDT結構在運行時已經被修改。
Speakeasy檢測到的SSDT掛鉤:

如果我們在IDA Pro中查看惡意軟件,就可以確認該惡意軟件已經修改了ZwQueryDirectoryFile和ZwEnumerateKeyAPI的SSDT條目,以用于隱藏自身,不會在文件系統分析和注冊表分析過程中被發現。
IDA Pro中顯示的用于文件隱藏的SSDT修改后函數:

在設置完這些掛鉤后,系統線程將會推出。驅動程序中的其他入口點(例如IRP處理程序和DriverUnload例程)不是太值得分析,其中包含的基本都是示例驅動程序代碼。
獲取注入的用戶模式植入工具
現在,我們已經知道了驅動程序在系統上隱藏自身的方式,就可以用Speakeasy創建的內存轉儲來獲取前面所說的注入的DLL。打開我們在仿真時創建的ZIP文件,可以找到上穩重引用過的內存標簽。我們迅速確認了該內存塊具有有效的PE標頭,并且能夠成功地將其加載到IDA Pro中,如下圖所示。
從Speakeasy內存轉儲中恢復的注入用戶模式DLL:

總結
在這篇文章中,我們探索了如何使用Speakeasy有效地從內核模式二進制文件中自動識別rootkit活動。Speakeasy可以用于快速分類內核二進制文件,解決了通常情況下難以進行動態分析的問題。如果想了解更多信息或查看源代碼,歡迎訪問我們的GitHub倉庫( https://github.com/fireeye/speakeasy )。
本文翻譯自:
https://www.fireeye.com/blog/threat-research/2021/01/emulation-of-kernel-mode-rootkits-with-speakeasy.html