Jump to content

Windows DSE(Driver Signature Enforcement)即任何驅動程序或第三方程式都要經過微軟簽名才能確保為正版、安全的程序。代碼完整性是15 年前由Microsoft 首次推出的威脅防護功能。在基於x64 的Windows 版本上,內核模式驅動程序必須在每次加載到內存時進行數字簽名和檢查,這也被稱為驅動強制簽名(DSE),檢測是否將未簽名的驅動程序或系統文件加載到內核中,或系統文件是否被修改(可能由具有管理權限的惡意軟件運行),可以提高操作系統的安全性。

為了克服這些限制,攻擊者使用有效的數字證書(無論是頒發給他們的還是他們竊取的)或者他們在運行時禁用DSE。獲得證書的難度很大,但篡改證書則純粹是一個技術上的挑戰。

儘管微軟近年來一直在致力於解決驅動強制簽名方面存在的問題,並提供了一系列解決方案,但利用眾所周知,篡改DSE的方法越來越多,用此方法攻擊的案例也明顯增加,這促使我們要深入地研究這個問題。

我們會在本文分享我們的研究結果,以及兩種DSE篡改方法的細節。

DSE實現過程j00ru是谷歌項目Zero的一名安全研究員,他層發表了一篇關於Windows 7中代碼完整性實現的介紹。

ntoskrnl.exe與附加的內核庫CI.dll(代碼完整性)一起工作。在操作系統初始化階段,內核設置nt!g_CiEnabled 並使用指向nt!g_CiCallbacks 結構的指針調用CI!CiInitialize 例程來初始化CI.dll。它依次設置CI!g_CiOptions 並在返回內核之前填充CI!CiValidateImageHeader、CI!CiValidateImageData 和CI!CiQueryInformation 回調的地址。

要使用回調函數,包裝器函數存在於ntoskrnl.exe中。他們檢查那個nt!g_CiEnabled設置為TRUE,適當的回調不是NULL,然後調用它。

當內核加載驅動程序時,執行通過nt!MmLoadSystemImage 到nt!MmCreateSection 並最終到nt!MiValidateImageHeader 例程。這樣,依次調用nt!SeValidateImageHeader 和nt!SeValidateImageData 包裝器。每個回調都應在成功時返回零,否則返回非零值。

fig1.webp.jpg

驅動程序加載時的調用堆棧

“Rootkits and Bootkits: Reversing Modern Malware and Next Generation Threats”一書詳細說明了Windows 8 中的實現更改過程:刪除nt!g_CiEnabled 變量,因此DSE 狀態僅由CI!g_CiOptions 確定,更多回調函數被添加到CI.dll提供的接口中。書中沒有描述的另一個變化是,如果驗證標頭成功,那麼驗證數據回調將不會被調用。

在Windows 8.1上,回調結構的符號名稱被更改為nt!SeCiCallbacks。

內核模式簽名策略除了簽名的加密有效性之外,微軟強制簽名證書只能由支持CA 的交叉證書頒發。這可以防止攻擊者簡單地在每台計算機上安裝自己的CA證書。

從Windows 10Redstone開始(2016年8月發布),驅動程序簽名策略有所改變,需要微軟自己進行第二次簽名。這是通過一個web門戶網站完成的,在那裡開發人員上傳他們的簽名二進製文件,並將它們發送給微軟。從攻擊者的角度來看,這意味著將你的有效載荷傳播給防御者,這與他們通常想要的相反。至少有一個記錄在案的案例表明,這種新措施沒有阻止攻擊者。

內核補丁保護KPP 或PatchGuard 於2005 年首次推出,是x64 版Windows 的一項功能,可防止修補內核。 “修補內核”是指修改ntoskrnl.exe 和其他關鍵系統驅動程序和數據結構(SSDT、IDT、GDT 等)的代碼。

它通過定期檢查這些受保護區域是否被修改而工作。如果檢測到系統被修改,則會觸發藍屏,使系統停止。 PatchGuard在每一個新的Windows版本中都會更新,這使得攻擊者很難開發出適用於所有版本的通用繞過技術。

ntoskrnl.exe中的回調結構和CI!g_CiOptions變量分別從Windows 8和Windows 8.1開始受PatchGuard保護。

DSE在野外被篡改儘管j00ru 得出結論,重寫nt!g_CiEnabled 或CI!g_CiOptions 等私有符號可能相對困難,但這正是攻擊者選擇的方向。眾所周知,臭名昭著的Turla APT 開發了這種技術,安全研究人員對其進行了逆向工程並發布了他們的代碼。

這些私有符號通過簡單的模式匹配來定位,幾乎不需要任何更改:

fig2.webp.jpg

常規PTE 順著SLAT PTE 突出顯示差異

在x86架構上,SLAT pte (Page Table Entries)處理權限不同於常規的PTE:讀\寫權限是分開的,執行權限需要顯式設置,並且只能為ring 0(內核模式)授權。 hypervisor使用SLAT 頁表來強制VTL 之間的隔離並使VTL1 可以訪問它們,所以安全內核使用它們來實現VBS特性,而hypervisor本身並不為這些功能本身實現任何代碼/邏輯。

受HyperVisor 保護的代碼完整性HVCI,最初稱為Device Guard,是隨著VBS 的引入而發布的,這是完整性執行的另一層。

加載新驅動程序後,安全內核也會被觸發並使用其自己的代碼完整性庫SKCI.dll(安全內核代碼完整性)實例。在Secure World (VTL1) 中的當前策略中驗證並檢查數字簽名以得到授權。只有這樣才能將可執行和不可寫權限應用於相應GPA 的SLAT 頁表。因此,NT 內核(VTL0) 不能修改任何以前加載的代碼或運行任何新代碼,而無需在進程中使用安全內核。

fig3.webp.jpg

Windows 10 SKCI.dll 中SkciInialize 及其自身CiOptions 變量的反彙編

內核數據保護KDP 旨在保護在Windows 內核(即操作系統代碼本身)中運行的驅動程序和軟件免受數據驅動的攻擊。它最初是在Windows 10 20H1 中引入的。

使用KDP,在內核模式下運行的軟件可以靜態(其自身映像的一部分)或動態(只能初始化一次的池內存)保護只讀內存。 KDP 僅在VTL1 中為支持受保護內存區域的GPA 建立寫保護,使用SLAT 頁表供管理程序強制執行。這樣,在NT 內核(VTL0) 中運行的任何軟件都不能擁有更改內存所需的權限。

KDP並不強制如何轉換受保護區域的GVA範圍映射,根據開發人員的介紹,KDP目前只定期驗證受保護的內存區域是否轉換為適當的GPA。

從Windows 11 開始,CI.dll 選擇使用靜態KDP (MmProtectDriverSection API) 來強化自身,將所有相關的CI 策略變量放置在名為“CiPolicy”的單獨部分中。

fig4.webp.jpg

Windows 11 中CI.dll 中CiInitializePolicy 和CiPolicy 部分的反彙編

驅動程序阻止列表這是通過Windows Defender 應用程序控制(WDAC) 或HVCI 策略強制執行的,目前已有一些第三方安全產品供應商也採用了這種做法。可以在此處找到最新的阻止列表。

阻止列表拒絕攻擊者輕鬆訪問內核寫入原語。雖然它非常有效,但它不是一種主動措施,只能處理以前發現的驅動程序。因此,這種緩解措施對驅動程序中的零日漏洞無效,防御者必須不斷追踪它們,始終落後於攻擊者一步。

新的篡改發現技術從上面的描述中,敏銳的讀者可以看到,如果沒有啟用HVCI,則可能在不進行任何代碼修補的情況下篡改DSE。

方法一:“頁面交換”

僅僅因為不再可能寫入CI!CiOptions 並不意味著它的值不能改變。該變量仍被虛擬地址訪問,並且每次都會發生到物理地址的轉換過程。因此,我們將改為更改翻譯結果。

通過將物理頁面從受KDP 保護的頁面交換到我們擁有的頁面,我們重新獲得了對內存的完全控制權。交換GPA 僅意味著更改PTE(頁表條目)中的PFN(頁幀號),它本質上只是另一個指針。

我們可以為任何給定的虛擬地址計算PTE 的虛擬地址,避免每次遍歷所有頁表。頁表位於Windows 內核用來管理分頁結構的虛擬內存區域中,稱為“PTE 空間”。 PTE Base 由KASLR(內核地址空間佈局隨機化)隨機化,從Windows 10 Redstone 開始。在之前的研究中,我們展示了一種可靠的方法來找到它。

除了寫入之外,執行此方法還需要內核讀取和內存分配原語。以下是C 偽代碼的分步實現過程:

fig5.webp.jpg

頁面交換的C偽代碼

可以使用來自用戶空間的頁面,因此內核內存分配原語變得多餘。對於CI.dll,可以使用變量的默認值而不是複制頁面。結果,內核空間的讀取次數顯著減少,因為只剩下少數必要的PTE 讀取。

方法二:“回調交換”有一段時間,KDP 似乎提高了防止DSE 篡改的門檻,因為頁面交換也需要內核讀取原語。我們再次查看了CI.dll 和ntoskrnl.exe 是如何集成的,然後我們想,“為什麼要使用CI.dll 呢?讓我們使用自己的回調而不是CI!CiValidateImageHeader”。

fig6.webp.jpg

回調交換演示

上圖證明無需讀取任何內核空間數據即可找到所有必要的地址,具體過程如下:

首先,在ntoskrnl.exe 中找到回調結構。該結構作為參數傳遞給CI!CiInitialize,這樣我們就可以從調用中獲得它的地址。內核只調用該函數一次,因此我們查找使用其導入表條目的CALL或JMP指令。找到調用站點後,返回到“.data”部分中指向未初始化內存的參數的寄存器分配。

fig7.webp.jpg

在Windows 11的ntoskrnl.exe中反彙編SepInitializeCodeIntegrity和SeCiCallbacks

接下來,尋找要使用的替換回調函數。我們需要一個不帶參數並返回零的函數。幸運的是,ntoskrnl.exe 有一些符合此要求的導出函數,例如FsRtlSyncVolumes 或ZwFlushInstructionCache,因此只需調用GetProcAddress 即可。

最後,找到要恢復的原始回調函數。回調由CI!CipInitialize 在結構中設置,因此它將引用所有回調。所有回調都設置在所有Windows 構建的單個基本代碼塊中。搜索這種指令模式,如下圖所示,並從lea 指令中提取偏移量。要驗證偏移量是否確實導致函數,遍歷PE 的異常目錄以查找具有相同起始地址的RUNTIME_FUNCTION 條目。

fig8.webp.jpg

Windows 11中CI.dll中CipInitialize的反彙編

KDP 保護被“設計”繞過,因為ntoskrnl.exe 沒有選擇使用回調結構。更改回調的另一個優點是篡改通過記錄的查詢系統信息API 不可見。

雖然解析地址可能需要多行代碼,但它是在用戶空間中完成的,因此只需要內核寫入原語,這與當前眾所周知的DSE 篡改方法相同。儘管PoC 向內核空間寫入了8 個字節(64 位指針的大小),但可以通過在CI.dll 中查找回調目標來減少這個數字。在撰寫本文時,來自TrustedSec的Adam Chester也發布了一篇最新的研究文章,他選擇了一種替代方法,通過掃描他創建的二進制簽名來找到原始回調。

緩解措施HVCI 涵蓋了所有篡改方法,因為它在加載驅動程序時執行自己的驗證。儘管HVCI 已經存在多年,但直到最近才在新的Windows 安裝中默認啟用。因此,我們一定要想出一個替代方案。

我們試圖找到一種方法來在驅動程序加載期間確認DSE 的狀態。此外,一個將支持程序的阻塞。在這一點上,如何獲得DSE 狀態的可見性應該是顯而易見的,防御者可以利用攻擊者用來查找內部變量的相同策略。畢竟,它已被證明是穩定的。

考慮到一個被篡改的狀態只能持續很短的時間,假設我們開始運行時系統狀態是有效的是合理的。此時,將保留內部變量的副本。

有3個選項可以攔截驅動程序加載:

1.在NtLoadDriver API 的用戶空間中放置一個鉤子。

2.使用註冊表回調來監視驅動程序註冊表項路徑上的操作。

3.使用文件系統微過濾器回調來創建驅動程序文件的部分(IRP_MJ_ACQUIRE_FOR_SECTION_SYNCHRONIZATION)。

要知道回調是否作為驅動程序加載的一部分被觸發,它需要檢查當前進程是SYSTEM 並且調用堆棧源自ntoskrnl.exe 而不是任何其他驅動程序。

一旦驅動程序加載被攔截,通過檢查任何變量的變化來檢測DSE 篡改。此時,可以簡單地通過使用錯誤狀態代碼阻止I\O 請求或將變量恢復到其保存狀態來進行預防。

總結我們在本文中介紹了微軟如何嘗試在運行時保護DSE,並介紹了兩種新方法來篡改它並成功加載未簽名的驅動程序。

雖然HVCI 提供了最強大的解決方案,但我們共享了一種額外的方法來檢測和防止DSE 在運行時篡改。在內核模式下執行代碼對攻擊者仍然具有吸引力,因為它通常是破壞計算機上更高特權元素(例如管理程序、UEFI 和SMM)或擊敗終端安全產品的必備手段。我們可能會看到TTP 的轉變,由於驅動程序阻止列表,攻擊者會轉向使用更多的內核1日漏洞來利用未修補的漏洞。

隨著硬件輔助的安全功能變得越來越普遍,以及微軟致力於利用它們的努力,攻擊者利用DSE 篡改可能會在可預見的未來逐漸消失。儘管如此,配置錯誤和老舊系統仍將存在這種攻擊。

0 Comments

Recommended Comments

There are no comments to display.

Guest
Add a comment...