[翻譯] 星期二補丁- 星期三利用:24 小時內(nèi)破解 WinSock (afd.sys) 的 Windows 輔助功

原文地址:https://securityintelligence.com/posts/patch-tuesday-exploit-wednesday-pwning-windows-ancillary-function-driver-winsock/
“星期二補丁,星期三利用” 是一句古老的黑客格言,指的是每月安全補丁公開后的第二天,漏洞就被武器化。隨著安全性的提高和漏洞緩解措施變得更加復(fù)雜,制作武器化漏洞所需的研究和開發(fā)量也隨之增加。這與內(nèi)存損壞漏洞尤其相關(guān)。

補丁差異和根本原因分析
根據(jù)微軟安全響應(yīng)中心(MSRC)發(fā)布的CVE-2023-21768的詳細信息,該漏洞存在于輔助功能驅(qū)動程序(AFD)中,其二進制文件名為?afd.sys
。AFD 模塊是?Winsock API?的內(nèi)核入口點。利用這些信息,我們分析了 2022 年 12 月的驅(qū)動程序版本,并將其與 2023 年 1 月新發(fā)布的版本進行了比較。這些樣本可以從?Winbindex?單獨獲取,無需從 Microsoft 補丁中提取更改的耗時過程。分析的兩個版本如下所示。
AFD.sys / Windows 11 22H2 / 10.0.22621.608 (December 2022)
AFD.sys / Windows 11 22H2 / 10.0.22621.1105 (January 2023)
Ghidra?用于為這兩個文件創(chuàng)建二進制導(dǎo)出,以便可以在?BinDiff?中對它們進行比較。匹配功能的概述如下所示。

似乎只有一個函數(shù)發(fā)生了變化,afd!AfdNotifyRemoveIoCompletion
。這大大加快了我們對漏洞的分析速度。然后我們比較了這兩個功能。下面的屏幕截圖顯示了在?Binary Ninja?中查看反編譯代碼時修補前后代碼的變化。
Pre-patch,?afd.sys version 10.0.22621.608
.

Post-patch,?afd.sys version 10.0.22621.1105
.

上面顯示的此更改是對已識別功能的唯一更新。一些快速分析表明,正在根據(jù)以下內(nèi)容執(zhí)行檢查
PreviousMode?。 如果?PreviousMode
?為 0 (表明調(diào)用源自內(nèi)核),則將值寫入由未知結(jié)構(gòu)中的字段指定的指針。另一方面,如果 PreviousMode 不為零,則?ProbeForWrite函數(shù)被調(diào)用確保該字段中設(shè)置的指針是駐留在用戶模式中的有效地址。
補丁前版本的驅(qū)動程序中缺少此檢查。由于該函數(shù)有一個針對?PreviousMode
?的特定 switch 語句,因此假設(shè)開發(fā)人員打算添加此檢查但忘記了(我們有時都缺咖啡??。?。
從這次更新中,我們可以推斷攻擊者可以通過未知結(jié)構(gòu)的?field_0x18
?處的受控值到達此代碼路徑。如果攻擊者能夠使用內(nèi)核地址填充此字段,則可以創(chuàng)建任意內(nèi)核 Write-Where 原語。此時,尚不清楚正在寫入什么值,但任何值都可能用于本地權(quán)限升級原語。
函數(shù)原型本身包含?PreviousMode
?值和指向未知結(jié)構(gòu)的指針,分別作為第一個和第三個參數(shù)。

逆向工程
我們現(xiàn)在知道漏洞的位置,但不知道如何觸發(fā)有漏洞的代碼路徑的執(zhí)行。在開始進行概念驗證 (PoC) 之前,我們將進行一些逆向工程。
首先,交叉引用易受攻擊的函數(shù)以了解其使用地點和方式。

在?afd!AfdNotifySock
?中對易受攻擊的函數(shù)進行了一次調(diào)用。
我們重復(fù)該過程,尋找對?AfdNotifySock
?的交叉引用。我們發(fā)現(xiàn)沒有對該函數(shù)的直接調(diào)用,但其地址出現(xiàn)在名為?AfdIrpCallDispatch
?的函數(shù)指針表上方。

該表包含 AFD 驅(qū)動程序的調(diào)度例程。調(diào)度例程用于通過調(diào)用來處理來自 Win32 應(yīng)用程序的請求?DeviceIoControl. 每個函數(shù)的控制代碼可在?AfdIoctlTable
?中找到。
然而,上面的指針并不像我們預(yù)期的那樣位于?AfdIrpCallDispatch
?表中。從 Steven Vittitoe 的?Recon?談話幻燈片中,我們發(fā)現(xiàn) AFD 實際上有兩個調(diào)度表。第二個是?AfdImmediateCallDispatch
?。通過計算該表的開頭與存儲 AfdNotifySock 的指針之間的距離,我們可以計算 AfdIoctlTable 的索引,該索引顯示該函數(shù)的控制代碼是?0x12127
。

值得注意的是,它是表中最后一個輸入/輸出控制(IOCTL)代碼,表明?AfdNotifySock
?很可能是最近添加到 AFD 驅(qū)動程序中的新調(diào)度函數(shù)。
此時,我們有幾個選擇。我們可以在用戶空間對相應(yīng)的?Winsock API
?進行逆向工程,以更好地了解底層內(nèi)核函數(shù)是如何調(diào)用的,或者對內(nèi)核代碼進行逆向工程并直接調(diào)用它。我們實際上并不知道哪個 Winsock 函數(shù)對應(yīng)于?AfdNotifySock
?,因此我們選擇了后者。
我們發(fā)現(xiàn)?x86matthew?發(fā)布的一些代碼通過直接調(diào)用 AFD 驅(qū)動程序來執(zhí)行套接字操作,放棄了 Winsock 庫。從隱秘的角度來看,這很有趣,但就我們的目的而言,它是一個很好的模板,可以創(chuàng)建 TCP 套接字的句柄以向 AFD 驅(qū)動程序發(fā)出 IOCTL 請求。從那里,我們能夠到達目標(biāo)函數(shù),正如在內(nèi)核調(diào)試時到達?WinDbg?中設(shè)置的斷點所證明的那樣。

現(xiàn)在,回顧一下?DeviceIoControl
?的函數(shù)原型,通過它我們從用戶空間調(diào)用 AFD 驅(qū)動程序。參數(shù)之一 lpInBuffer 是用戶模式緩沖區(qū)。如上一節(jié)所述,該漏洞的發(fā)生是因為用戶能夠在未知數(shù)據(jù)結(jié)構(gòu)中將未經(jīng)驗證的指針傳遞給驅(qū)動程序。該結(jié)構(gòu)是通過?lpInBuffer
?參數(shù)直接從我們的用戶模式應(yīng)用程序傳入的。它作為第四個參數(shù)傳遞到 AfdNotifySock 中,并作為第三個參數(shù)傳遞到?AfdNotifyRemoveIoCompletion
?中。
此時,我們不知道如何填充?lpInBuffer
?中的數(shù)據(jù)(我們將其稱為?AFD_NOTIFYSOCK_STRUCT
?),以便通過到達?AfdNotifyRemoveIoCompletion
?中易受攻擊的代碼路徑所需的檢查。我們逆向工程過程的其余部分包括遵循執(zhí)行流程并檢查如何訪問易受攻擊的代碼。
讓我們逐一檢查一下。
我們遇到的第一個檢查是在?AfdNotifySock
?的開頭:

此檢查告訴我們?AFD_NOTIFYSOCK_STRUCT
?的大小應(yīng)等于 0x30 字節(jié),否則函數(shù)會失敗并顯示?STATUS_INFO_LENGTH_MISMATCH
?。
下一個檢查驗證結(jié)構(gòu)中各個字段中的值:

當(dāng)時我們不知道這些字段對應(yīng)什么,所以我們傳入一個?0x30
?字節(jié)數(shù)組,其中填充了?0x41
?字節(jié)(AAAAAAAAA...)。
我們遇到的下一個檢查是在調(diào)用之后?ObReferenceObjectByHandle. 該函數(shù)將輸入結(jié)構(gòu)的第一個字段作為其第一個參數(shù)。

該調(diào)用必須返回成功才能繼續(xù)到正確的代碼執(zhí)行路徑,這意味著我們必須將有效的句柄傳遞給?IoCompletionObject
?。沒有正式記錄的方法可以通過 Win32 API 創(chuàng)建該類型的對象。然而,經(jīng)過一番搜索,我們發(fā)現(xiàn)了一個未記錄的 NT 函數(shù)?NtCreateIoCompletion?之后,我們到達一個循環(huán),其計數(shù)器是結(jié)構(gòu)中的值之一:

該循環(huán)檢查結(jié)構(gòu)中的一個字段,以驗證它包含有效的用戶模式指針并將數(shù)據(jù)復(fù)制到其中。每次循環(huán)迭代后指針都會遞增。我們用有效地址填充了指針,并將計數(shù)器設(shè)置為 1。從這里,我們最終能夠到達存在漏洞的函數(shù)?AfdNotifyRemoveIoCompletion
?。

進入?AfdNotifyRemoveIoCompletion
?后,第一個檢查是結(jié)構(gòu)中的另一個字段。它必須是非零的。然后將其乘以 0x20,并與結(jié)構(gòu)體中的另一個字段一起作為指針參數(shù)傳遞到?ProbeForWrite
?。從這里,我們可以使用有效的用戶模式指針 (pData2
) 和字段?dwLen = 1
?進一步填充結(jié)構(gòu)(以便傳遞給?ProbeForWrite
?的總大小等于?0x20
?),并且檢查通過。

最后,在到達目標(biāo)代碼之前要通過的最后一個檢查是對?IoRemoveCompletion
?的調(diào)用,它必須返回 0 (STATUS_SUCCESS
)。
該函數(shù)將阻塞,直到:
完成記錄可用于?
IoCompletionObject
?參數(shù)超時到期,作為函數(shù)的參數(shù)傳入
我們通過結(jié)構(gòu)控制超時值,但簡單地將超時設(shè)置為 0 不足以讓函數(shù)返回成功。為了使該函數(shù)無錯誤地返回,必須至少有一個可用的完成記錄。經(jīng)過一番研究,我們發(fā)現(xiàn)了未記錄的功能?NtSetIoCompletion?它手動增加?IoCompletionObject
?上的 I/O 掛起計數(shù)器。在我們之前創(chuàng)建的?IoCompletionObject
?上調(diào)用此函數(shù)可確保對?IoRemoveCompletion
?的調(diào)用返回?STATUS_SUCCESS
?。

觸發(fā)任意寫位置
現(xiàn)在我們可以到達易受攻擊的代碼,我們可以用任意要寫入的地址填充結(jié)構(gòu)中的適當(dāng)字段。我們寫入該地址的值來自一個整數(shù),該整數(shù)的指針被傳遞到對?IoRemoveIoCompletion
?的調(diào)用中。?IoRemoveIoCompletion
?將此整數(shù)的值設(shè)置為調(diào)用?KeRemoveQueueEx
?的返回值。


在我們的概念驗證中,該寫入值始終等于 0x1。我們推測?KeRemoveQueueEx
?的返回值是從隊列中刪除的項目數(shù),但沒有進一步調(diào)查。此時,我們已經(jīng)有了所需的原語,并繼續(xù)完成漏洞利用鏈。我們后來證實了這個猜測是正確的,并且可以通過對?IoCompletionObject
?上的?NtSetIoCompletion
?進行額外調(diào)用來任意增加寫入值。
LPE with IORING
由于能夠在任意內(nèi)核地址寫入固定值 (0x1),我們繼續(xù)將其轉(zhuǎn)換為完整的任意內(nèi)核讀/寫。由于此漏洞影響最新版本的 Windows 11(22H2),因此我們選擇利用?Windows I/O?環(huán)對象損壞來創(chuàng)建我們的原語。Yarden Shafir?撰寫了許多關(guān)于 Windows I/O 環(huán)的優(yōu)秀文章,并且還開發(fā)并披露了我們在漏洞利用鏈中利用的原語。據(jù)我們所知,這是該原語首次被用于公共漏洞利用。?當(dāng)用戶初始化 I/O 環(huán)時,會創(chuàng)建兩個獨立的結(jié)構(gòu),一個在用戶空間,一個在內(nèi)核空間。?這些結(jié)構(gòu)如下所示。
內(nèi)核對象映射到?nt!_IORING_OBJECT
,如下所示。

請注意,內(nèi)核對象有兩個字段:RegBuffersCount
?和?RegBuffers
,它們在初始化時被清零。該計數(shù)指示有多少 I/O 操作可以在 I/O 環(huán)中排隊。另一個參數(shù)是指向當(dāng)前排隊操作列表的指針。
在用戶空間端,調(diào)用?kernelbase!CreateIoRing時,如果成功,您將返回一個 I/O 環(huán)句柄。該句柄是指向未記錄結(jié)構(gòu) (HIORING
) 的指針。我們對這種結(jié)構(gòu)的定義是從 Yarden Shafir 所做的研究中獲得的。
如果某個漏洞(例如本博文中提到的漏洞)允許您更新?RegBuffersCount
?和?RegBuffers
?字段,則可以使用標(biāo)準(zhǔn) I/O Ring API 來讀取和寫入內(nèi)核內(nèi)存。
正如我們在上面看到的,我們可以利用該漏洞在我們喜歡的任何內(nèi)核地址寫入?0x1
。要設(shè)置 I/O 環(huán)原語,我們只需觸發(fā)該漏洞兩次即可。

在第二個觸發(fā)器中,我們將?RegBuffers
?設(shè)置為可以在用戶空間中分配的地址(例如?0x0000000100000000
)。

備注:?需要驗證兩次的原因就是?RegBuffersCount
?和?RegBuffers
?分別驗證是否能通過帶有缺陷的驅(qū)動寫入。

下面的屏幕截圖顯示了這樣一個?nt!_IOP_MC_BUFFER_ENTRY
?。請注意,操作的目標(biāo)是內(nèi)核地址 (0xfffff8052831da20
),并且在本例中操作的大小為?0x8
?字節(jié)。從結(jié)構(gòu)中無法判斷這是讀操作還是寫操作。操作的方向取決于使用哪個 API 對 I/O 請求進行排隊。利用?kernelbase!BuildIoRingReadFile?導(dǎo)致任意內(nèi)核寫入,kernelbase!BuildIoRingWriteFile
?導(dǎo)致任意內(nèi)核讀取。

為了執(zhí)行任意寫入,I/O 操作的任務(wù)是從文件句柄讀取數(shù)據(jù)并將該數(shù)據(jù)寫入內(nèi)核地址。

相反,為了執(zhí)行任意讀取,I/O 操作的任務(wù)是讀取內(nèi)核地址處的數(shù)據(jù)并將該數(shù)據(jù)寫入文件句柄。

Demo
設(shè)置原語后,剩下的就是使用一些標(biāo)準(zhǔn)內(nèi)核后利用技術(shù)來泄漏系統(tǒng)(PID 4)等提升進程的令牌并覆蓋不同進程的令牌。
視頻地址:https://youtu.be/M3IPsKAsxvQ

在野利用
在我們的漏洞代碼公開后,來自360 Icesword Lab的Xiaoliang Liu(@flame36987044)首次公開披露,他們在今年早些時候發(fā)現(xiàn)了利用該漏洞的樣本(ITW)。 ITW 樣本使用的技術(shù)與我們的不同。攻擊者使用相應(yīng)的 Winsock API 函數(shù)?ProcessSocketNotifications
?觸發(fā)漏洞,而不是像我們的漏洞利用那樣直接調(diào)用 afd.sys 驅(qū)動程序。
360冰劍實驗室官方聲明如下:
“360冰劍實驗室專注于APT檢測與防御?;谖覀兊?day漏洞雷達系統(tǒng),今年1月份我們在野外發(fā)現(xiàn)了CVE-2023-21768的利用樣本,該樣本與?@chompie1337?和?@FuzzySec?公布的利用樣本不同,它是通過系統(tǒng)機制和漏洞特征進行利用的。該漏洞與?NtSetIoCompletion
?和?ProcessSocketNotifications
?有關(guān),?ProcessSocketNotifications
?獲取調(diào)用?NtSetIoCompletion
?的次數(shù),因此我們使用它來更改權(quán)限計數(shù)。”
結(jié)論和最終反思
您可能會注意到,在逆向工程的某些部分,我們的分析是膚淺的。有時,僅觀察一些相關(guān)的狀態(tài)變化并將程序的某些部分視為黑匣子是有幫助的,以避免陷入不相關(guān)的兔子洞。這使我們能夠快速扭轉(zhuǎn)漏洞,盡管最大化完成速度不是我們的目標(biāo)。此外,我們對 afd.sys 中所有報告的漏洞進行了補丁差異審查,這些漏洞被標(biāo)記為“利用可能性更大”。我們的審查顯示,除了兩個漏洞之外,所有漏洞都是由于對從用戶模式傳入的指針驗證不當(dāng)造成的。這表明,了解過去的漏洞(尤其是特定目標(biāo)內(nèi)的漏洞)的歷史知識,對于發(fā)現(xiàn)新漏洞可能會卓有成效。當(dāng)代碼庫擴展時,同樣的錯誤很可能會重復(fù)。請記住,新的 C 代碼 == 新的 bug ??。正如發(fā)現(xiàn)上述漏洞在野外被利用所證明的那樣,可以肯定地說,攻擊者也在密切監(jiān)視新的代碼庫添加。
Windows 內(nèi)核中缺乏對管理員模式訪問保護 (SMAP) 的支持,這給我們提供了豐富的選項來構(gòu)建新的純數(shù)據(jù)利用原語。這些原語在支持 SMAP 的其他操作系統(tǒng)中不可行。例如,考慮 CVE-2021-41073,這是 Linux 的 I/O 環(huán)預(yù)注冊緩沖區(qū)實現(xiàn)中的一個漏洞(我們在 Windows 中濫用相同的功能用于 R/W 原語)。此漏洞可以允許覆蓋已注冊緩沖區(qū)的內(nèi)核指針,但不能用于構(gòu)造任意 R/W 原語,因為如果該指針被替換為用戶指針,并且內(nèi)核嘗試在那里讀取或?qū)懭?,系統(tǒng)將崩潰。
盡管微軟盡了最大努力來消除人們喜愛的漏洞利用原語,但肯定會發(fā)現(xiàn)新的原語來取代它們。我們能夠利用最新版本的 Windows 11 22H2,而不會遇到?HVCI(內(nèi)存完整性和基于虛擬化的安全性)?等基于虛擬化的安全功能的任何緩解或限制。
引用
1. MSRC (CVE-2023-21768)?(https://msrc.microsoft.com/update-guide/en-US/vulnerability/CVE-2023-21768)
2. I/O Rings – When One I/O Operation is Not Enough ?(@yarden_shafir)?(https://windows-internals.com/i-o-rings-when-one-i-o-operation-is-not-enough/)
3. IoRing vs. io_uring: a comparison of Windows and Linux implementations ?(@yarden_shafir) (https://windows-internals.com/ioring-vs-io_uring-a-comparison-of-windows-and-linux-implementations/)
4. One Year to I/O Ring: What Changed? (@yarden_shafir) (https://windows-internals.com/one-year-to-i-o-ring-what-changed/)
5. One I/O Ring to Rule Them All: A Full Read/Write Exploit Primitive on Windows 11 (@yarden_shafir) (https://windows-internals.com/one-i-o-ring-to-rule-them-all-a-full-read-write-exploit-primitive-on-windows-11/)
6. Arbitrary Kernel RW using IORING’s (@FuzzySec)? (https://knifecoat.com/Posts/Arbitrary+Kernel+RW+using+IORING's)
7. NTSockets – Downloading a file via HTTP using the NtCreateFile and NtDeviceIoControlFile syscalls (@x86matthew) (https://www.x86matthew.com/view_post?id=ntsockets)
8. Reverse Engineering AFD.sys (@bool101) (https://recon.cx/2015/slides/recon2015-20-steven-vittitoe-Reverse-Engineering-Windows-AFD-sys.pdf)
9. Microsoft Windows Ancillary Function Driver for WinSock privilege escalation CVE-2023-21768 Vulnerability Report (https://exchange.xforce.ibmcloud.com/vulnerabilities/243235)