Jump to content

為在成熟環境中運行而設計的Post-exploitation工具經常需要繞過在目標上運行的終端檢測和響應(EDR) 軟件。 EDR 經常通過掛鉤Windows API 函數進行操作,尤其是那些由ntdll 導出的函數(特別是基於Nt/Zw*() 系統調用的API 函數)。由於在正常情況下與底層操作系統的所有交互都將通過這些函數,這樣在檢測不需要的應用程序行為時就提供了一個理想的攔截點。

之前MDSec 已經在《绕过用户模式挂钩和直接调用红队的系统调用》 中討論了繞過這些掛鉤的各種方法,但是由於EDR 經常與攻擊者鬥法,因此EDR的檢測技術只有實時更新,以檢測識別用於實現繞過掛鉤的新技術。

在Nighthawk C2的開發過程中,MDSec 偶然發現了一種新的技術,用於識別某些系統調用的Syscall Number,然後可以使用該技術將ntdll 的新副本加載到內存中,從而允許剩餘的系統調用在不觸發任何已安裝的函數掛鉤的情況下成功讀取。該技術涉及濫用新的Windows 10 並行加載程序在進程初始化期間早期生成的某些系統調用的副本。

Windows 10 並行加載程序從Windows 10 開始,微軟引入了並行DLL 加載的概念。並行加載允許進程執行遞歸映射通過進程模塊導入表導入的DLL 的過程,而不是在單個線程上同步,從而在初始應用程序啟動期間提高性能。

當我們試圖理解為什麼在一個簡單的單線程應用程序中會創建三到四個額外的線程時,我們第一次注意到並行加載程序的存在。檢查這些線程可以確定它們是線程池的工作線程。

通過上網搜索其中原委時,有一篇來自2017 年黑莓的Jeffery Tang 的博客文章以及來自用戶RbMm的回答幫助了我,他在提供偽代碼以幫助闡明該過程中涉及的步驟方面也做得非常出色。這兩篇文章清楚地說明了背後的運行原因,如果對進一步了解並行加載程序工作原理有興趣,我推薦這兩篇文章。

在閱讀StackOverflow答案時,有一件事立即引起了我的注意,那就是如果發現有幾個核心低級別本機API被繞過,並行加載程序就會使並行DLL加載短路,並退回到同步模式。這些API參與了從磁盤打開和映射映像的過程。

以下是用戶RbMm的部分答复,我們將在下面進行詳細分析:

1.png

上面函數LdrpDetectDetour()的偽代碼本質上是檢查5個本機API函數NtOpenFile(), NtCreateSection(), ZwQueryAttributeFile(),ZwOpenSection()和ZwMapViewOfFile(),並確定這些字節是否已經從存儲在ntdll中的LdrpThunkSignature數組中已知的正確字節被修改。

通過對LdrpDetectDetour() 函數的快速反彙編確認上述偽代碼中描述的行為仍然存在,但應該提到的是,該函數現在額外地驗證了另外27個本機API的完整性,但仍然只是比較了詳細的5個函數的精確的系統調用存根。

用IDA Pro 檢查ntdll 發現LdrpDetectDetour() 函數是從兩個地方調用的:LdrpLoadDllInternal()(直接從LdrpLoadDll() 調用)和LdrpEnableParallelLoading()(在LdrpInitializeProcess() 的後期調用)。由於LdrpDetectDetour() 函數配置了一個全局變量,該變量可以停止並行加載並強制進一步加載同步發生,並且許多安裝了detours 的DLL(例如EDR 用戶空間組件)在加載到進程時立即執行此操作,它在加載每個新的DLL 依賴項時重複調用detour 檢測函數是有意義的。

然而,對這一過程的研究又引發了另一個問題,已知的5個本機API函數的已知良好存根從何而來?最初以為系統調用存根將在代碼生成期間的編譯時被硬編碼,但是靜態檢查LdrpThunkSignature 數組表明情況並非如此,因為在映射ntdll 之前數組沒有初始化,原因是數組駐留在未初始化的.data 中部分。

LdrpThunkSignature標識的數據交叉引用了另一個數組的使用,在LdrpCaptureCriticalThunks(),這個函數反過來調用早期LdrpInitializeProcess()之前進口依賴加載,因此第三方模塊安裝的detours 可能已加載到進程中。

快速手動反編譯LdrpCaptureCriticalThunks() 會顯示類似於以下偽代碼的實現:

2.png

從上面可以清楚地看到,LdrpCaptureCriticalThunks()將五個關鍵函數的每個系統調用存根的前16個字節從每個函數中復製到LdrpThunkSignature數組中。

從LdrpThunkSignature中恢復系統調用具有post-exploitation工具開發經驗的讀者無疑會收穫頗多,有了這五個關鍵函數和它們的Syscall Number(直接從LdrpThunkSignature 讀取)的知識,我們有足夠的本機API 函數能夠使用系統調用從磁盤讀取ntdll 的新副本。

由於LdrpThunkSignature數組不是由ntdll導出的,我們需要在ntdll .data節中找到它。該數組可以通過公共系統調用序言的出現被識別出來:

3.png

下面的代碼能夠使用這個信息來恢復所需的系統調用(MDSec 提供的用於恢復所有系統調用代碼段):

4.png

可以在MDSec ActiveBreach GitHub 存儲庫中找到使用上述方法從磁盤讀取ntdll 來恢復所有系統調用的實現。

這個實現當然是一個PoC,從opsec 的角度來看並不是最優的,例如,系統調用存根是用使用VirtualAlloc() 創建的RWX 內存分配的。

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...