[翻譯]Windows內核漏洞學習-內核池攻擊原理

前言

在練習HEVD的內核池溢出漏洞之前,首先先需要了解一下內核池溢出的一些攻擊方法和原理。這裏對kernelpool-exploitation進行翻譯閱讀一下。

正文

ListEntry Flink Overwrite

Win7在從ListHeads[n]分配池塊(對於給定的塊大小)時,使用的安全算法驗證的是ListHeads[n]的列表條目結構,而不會去驗證即將unlink的實際塊的結構。因此,在一個free的塊中覆蓋它的Flink可能會導致ListHeads[n]的地址被寫爲攻擊者控制的地址。(如圖所示)
在這裏插入圖片描述

這種攻擊需要至少兩個空閒的塊存在於目標ListHeads[n]列表中。否則ListHeads [n]的Blink將驗證unlink塊的Flink。在上圖中,ListHeads列表中池塊的Flink已被攻擊者選擇的地址覆蓋。反過來,當這個塊在ExAllocatePoolWithTag中分配時,算法嘗試在攻擊者控制的地址(eax)上的列表條目結構的Blink寫入ListHeads[n] (esi)的地址。

nt!ExAllocatePoolWithTag+0x4b7:
8296f067 897004 mov dword ptr [eax+4],esi

雖然不能從用戶模式的上下文輕鬆地確定esi(目標寫入地址)的值,但有時可以推斷出它的值。例如,如果只定義了一個非分頁池(如2.2中所述),esi將指向ntoskrnl的數據段中的一個固定位置(nt!NonPagedPoolDescriptor)。如果池描述符是從內存分配的,那麼可以假設它在定義的池內存範圍內的位置。因此,攻擊者可以重寫重要的全局變量或內核對象指針(例如,通過部分指針重寫),以獲得任意代碼的執行。

攻擊者還可以使用覆蓋中的用戶模式指針將任意寫入擴展到完全控制的內核分配。這源於一個事實,即ListHeads[n]。在斷開已損壞的塊的鏈接之後,Flink被更新爲指向下一個空閒塊(攻擊者控制的指針)。由於攻擊者提供的地址的Blink被更新爲指向ListHeads[n],池分配器在從空閒列表安全地解除usermode指針鏈接方面沒有問題。

Lookaside Next Pointer Overwrite

後備列表的設計是快速和輕量級的,因此不引入與雙鏈ListHeads列表相同的一致性檢查。後備列表中的每個條目都是單鏈接的,它們都有一個指向下一個條目的指針。由於沒有檢查斷言這些指針的有效性,攻擊者可能會使用池損壞漏洞強制池分配器在檢索下一個空閒後備塊時返回任意地址。反過來,這可能允許攻擊者破壞任意的內核內存。
在這裏插入圖片描述

爲了將一個lookaside下一個指針的破壞擴展到一個n字節的任意內存覆蓋,必須對目標塊的大小進行分配,直到被破壞的指針返回(圖2)。對於分頁池分配,分配unicode字符串(如NtCreateSymbolicLinkObject)的本機api提供了一種方便的方式,可以用幾乎任何字節組合填充任何大小的塊。這些api還可以用於整理和操作池內存佈局,以控制未初始化的指針和兩次釋放等可利用的原語。

在這裏插入圖片描述

與lookaside池塊不同,lookaside池頁(圖3)將下一個指針存儲在offset null,因爲沒有與它們相關聯的池頭。如果保持以下狀態,分配的池頁面將被釋放到(lookaside list)後備列表中:

  • 用於分頁池頁面數量 =1

  • 用於未分頁的池頁面數量<=3

  • 目標頁計數的後備列表未滿

當內存管理器必須請求額外的池內存時,請求的頁由由nt!MiAllocatePoolPages函數返回,這些內存內存無法從ListHeads或lookaside列表中獲得。由於這通常是由許多併發系統線程執行的,因此操作內核池佈局以在後備列表的空閒池頁面旁邊定位溢出顯然是說起來容易做起來難。另一方面,在處理後備池塊時,可以使用不經常請求的塊大小值,以便對內存佈局進行更細粒度的控制。這可以通過檢查後備管理結構中的totalallocate值來完成。

PendingFrees Next Pointer Overwrite

等待釋放的池條目存儲在單鏈的pending列表中。由於在遍歷這些列表時不執行任何檢查,因此攻擊者可以利用池損壞漏洞來損壞pending列表條目的下一個指針。反過來,這將允許攻擊者將任意地址釋放給所選的池描述符ListHeads列表,並可能控制後續池分配的內存(圖4)
在這裏插入圖片描述
攻擊延遲釋放列表的一個值得注意的警告是,內核池非常頻繁地處理這個列表(每32次釋放一次)。實際上,可以將數百個線程調度到同一個內核池,也可以在多核機器上並行地處理它們。因此,池溢出所針對的塊很可能已經從延遲釋放列表中刪除,並放到了ListHeads列表中。由於這個原因,我們很難認爲這次攻擊是可行的。但是,由於某些池描述符的使用頻率低於其他描述符(如會話池描述符),因此在某些情況下對延遲釋放列表的攻擊可能是可行的。

PoolIndex Overwrite

如果爲給定的池類型定義了多個池描述符,則池塊的PoolIndex表示關聯池描述符數組中的索引。因此,在處理ListHeads條目時,池塊總是被釋放到其適當的池描述符。然而,由於驗證不足,格式不正確的PoolIndex可能會觸發越界數組取消引用,從而允許攻擊者改寫任意內核內存。

在這裏插入圖片描述

對於分頁池,PoolIndex總是表示到分頁池描述符數組(nt!ExpPagedPoolDescriptor)中的索引。在已檢查的構建中,索引值在與ntExpNumberOfPagedPools的比較中得到驗證,以防止任何超出界限的數組訪問。但是,在free的(零售)構建中,不驗證索引。對於非分頁池,PoolIndex表示僅當numa感知系統中存在多個節點時,纔將索引引入ntExpNonPagedPoolDescriptor中。同樣,在自由構建時,不會驗證PoolIndex。

畸形的池索引(只需要2字節池溢出)可能會導致分配的池塊被釋放到空指針池描述符(圖5)。通過映射虛擬空頁,攻擊者可以完全控制池描述符及其ListHeads條目。反過來,這可能允許攻擊者在鏈接到列表時將池塊的地址寫入任意地址。這是因爲當前在前面的塊的Blink是用釋放的塊的地址更新的,例如ListHeads[n].Flink - > = FreedChunk.Blink。注意,由於釋放的塊沒有返回到任何實際池描述符,因此不需要清理內核池(刪除陳舊的條目等)。

在這裏插入圖片描述

如果啓用了延遲池釋放,那麼可以通過創建一個假冒的pendingfree列表來實現類似的效果(圖6)。此外,池描述符中的PendingFreeDepth值將大於或等於0x20,以觸發pendingfree列表的處理。
在這裏插入圖片描述

示例2演示了PoolIndex重寫如何可能導致將用戶控制的頁面地址(eax)寫入任意目標地址(esi)。爲了執行任意代碼,攻擊者可以利用這個方法用用戶模式頁面地址覆蓋一個不常用的內核函數指針,並從相同的進程上下文中觸發它的執行。

如果塊的PoolType也被重寫(例如,將其設置爲PagedPool),那麼PoolIndex重寫攻擊可以應用於任何池類型。由於這要求塊大小也被覆蓋,攻擊者必須要麼知道被覆蓋塊的大小,要麼創建一個嵌入其中的假邊界塊。這是必須的,因爲FreedBlock->塊大小= NextBlock->先前的大小必須保持,由自由算法檢查。此外,塊大小應該大於0x20,以避免後備列表(忽略PoolIndex)。但是,請注意,嵌入式池塊可能會損壞塊數據中的重要字段或指針。

Quota Process Pointer Overwrite

由於可以爲分配的池內存對進程進行記錄,池分配必須爲池算法提供足夠的信息,以便將記錄的配額返回給正確的進程。因此,池塊可以有選擇地存儲指向關聯流程對象的指針。在x64上,process對象指針存儲在池標頭的最後8個字節中(如第2.9節所述),而在x86上,指針被附加到池體。在池損壞漏洞中重寫此指針(圖7)可以允許攻擊者釋放正在使用的進程對象或損壞任意內存,以返回所佔用的配額。

在這裏插入圖片描述

每當釋放池分配時,free算法在實際將內存返回到適當的空閒列表或lookaside之前檢查池類型,尋找配額位(0x8)。如果設置了該位,它將嘗試通過調用nt!PspReturnQuota返回已計費的配額,然後取消對相關進程對象的引用。因此,重寫流程對象指針可以允許攻擊者減少任意流程對象的引用(指針)計數。隨後引用計數不一致可能導致use-after-frees如果條件得到滿足(如處理計數爲零當引用計數降低爲零)。

在這裏插入圖片描述

如果將process對象指針替換爲指向用戶模式內存的指針,則攻擊者可以創建一個僞EPROCESS對象來控制指向EPROCESS配額塊結構的指針(圖8),其中存儲了配額信息。在free時,通過減去分配的大小來更新此結構中使用的配額的值。因此,攻擊者可以在返回所佔用的配額時減少任意地址的值。只要設置了配額位和配額流程對象指針,攻擊者就可以對任何池分配掛載這兩種攻擊。

明日計劃

說實話俺還是沒太搞明白只看了大概,還是找真實案例對着理論調試可能更好懂一些。

繼續學習Windows內核漏洞

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章