Windows kernel pool 初探(2014.12)


Windows kernel pool

 

1. 簡介

Kernel pool類似於Windows用戶層所使用Heap,其為內核組件提供系統資源。在系統初始化的時候,內存管理模塊就創建了pool

嚴格的來說,pool只分為nonpaged poolpaged pool兩類。

nonpaged pool:

  1. 只能常駐於物理內存地址,不能映射。(reside in physical memory at all times and can be accessed at any time without incurring page fault.)
  2. 只能在DPC/dispatch level或者更高IRQL才可以訪問。

     

Paged pool:

  1. 可以映射(can be paged into and out of the system).
  2. 任何級別的IRQL都可以訪問,所以每個進程都可以訪問。

     

兩種pool都會被映射到每一個進程空間內,都使用ExAllocatePool()ExFreePool()來分配和釋放。

IRQL是系統用來管理中斷優先權,下圖中的數值越大就表示優先權越高。

2. 一些結構

系統默認的pool有:

– One non­paged pool

– Two paged pools

– One session paged pool (和每一個session有關,每一個用戶都不同)

    

每一個pool 都有一個POOL_DESCRIPTOR結構來管理pool,其功能包括:

1.跟蹤pool的分配和釋放,使用的page數量等。

2.維護free pool thunks lists

 

初始化的POOL_DESCRIPTOR是放在nt!PoolVector數組(這個值是系統的全局變量)里面的。

POOL_DESCRIPTOR的結構如下:

該結構相關的一些變量介紹:

– PoolType:

NonPagedPool=0,PagedPool=1

– PoolIndex:

nonpaged pool & paged session pool =0

paged pool=index of the pool descriptor in nt!ExpPagedPoolDescriptor

– PendingFrees:

單項鏈表,表示將要釋放的pool

– ListHeads:

大小512ListEntry結構數組(0開始,8個字節遞增),雙向鏈表,表示已經釋放了的pool,可以隨時再次被使用。

2.1 NonPaged Pool

1. pool的數量存儲在nt!ExpNumberOfNonPagedPools中。

2. 單處理器系統中,nt!PoolVector數組的第一成員就是nonpaged pool descriptor

nt!NonPagedPoolDescriptor,其是寫入到.data中的定值。

kd> dt nt!_POOL_DESCRIPTOR poi(nt!PoolVector)

3.多處理系統中,每一個node(NUMA system call processors and memory)都有自己的nonpaged pool descriptor

存儲在nt!ExpNonPagedPoolDescriptor中。

 

Windbg中:

PoolVector數組(似乎在xp下是一個定值)

2.2 Paged Pool

 

1. Pool的數量存儲在nt!ExpNumberOfPagedPools中。

2. 單處理器系統中,4paged pool descriptors地址在nt!ExpPagedPoolDescriptor數組中存儲(index of 1-4)

3. 多處理器系統中,每一個node定義一個paged pool descriptor

4. 還有一類特殊的按頁分配的pool(prototype pools/full page allocations),位於nt!ExpPagedPoolDescriptor的第一個索引(index 0)

現在用windbg查看一下nonpaged poolpaged pool在內核中的模樣:

paged pool descriptor

nt!ExpPagedPoolDescriptor[0]= PoolVector[1]poi(0x8055c520)=poi(0x8055c560+4)

paged pool descriptor of prototy pools

paged pool descriptor of normal paged pool

2.3 Session Pool

xp下,win7的差別似乎有點大

1. Pageable system memory(paged pool)

2. PagedPool  member of nt!MmSessionSpace structure

Usually 0xbf7f0000+0x244(XP)

3. 使用函數nt!MiInitializeSessionPool初始化。

作為session space 使用,不會使用Lookaside lists(快表)

4. Point of pool descriptor 存放在nt! ExpSessionPoolDescriptor中。

2.4 ListHeads(x86)

作為pool descriptor的成員之一,其是由大小為512,每個成員為LIST_ENTRY的數組構成的:

– LIST_ENTRY為一個雙向鏈表,表示大小相同的free chunks

所表示的pool大小以8字節遞增,最大為4080 bytes

空閑的chunks是以BlockSize為索引加入到ListHeads 中去,1 BlockSize=8 bytes

– chunkBlockSize計算公式BlockSize= (NumBytes+0xF) >> 3每一個chunk 都有一個8 byte pool header用來管理該chunk

ListHeads的樣子大概如下:

2.5 Kernel Pool Header(x86)

Pool Header是用來管理pool thunk的,里面存放一些為釋放和分配所需要的信息。

– PreviousSize: 前一個chunkBlockSize

– PoolIndex : 所在大poolpool descriptorindex。這是用來檢查釋放pool的算法是否釋放正確了。

– PoolType: Free=0,Allocated=(PoolType|2)

– PoolTag: 4個可打印字符,標明由哪段代碼負責。(4 printable characters identifying the code responsible for the allocation)

2.6 LIST_ENTRY

當一個pool chunk釋放並准備放到ListJHeads list的時候,其pool header結構會添加一個LIST_ENTRY結構,這個LIST_ENTRYListHeads 使用管理free pool thunk的雙向鏈表。

BlockSize npool chunk free之后鏈入到ListHeads中的樣子

Chunk釋放前和釋放后的變化:

利用windbg看一下pool chunk吧。

首先利用!pool列出系統中所有的paged pool(似乎不包括Lookaside lists)

一部分如下:

可以發現其都是以一個page大小(0x1000)顯示出來的,那么接下來查看其中任意一個page,部分為如:

查看其中一個首地址為e10212b0,大小為10 bytes,標記為freepool chunk

再看看LIST_ENTRY里面的內容:

ListHeads中和該pool thunk相鄰的thunk:

兩者的大小相同。

2.7 Lookaside List

xp下)

Windows 還使用了Lookaside(快表?)這種單項鏈表(LIFO)來管理更快的pool分配。

1. BlockSize最大為32,也就是最大為256 bytes.

2. 8 bytes大小遞增,每種類型的Lookaside 只有32項。

3. The Prcessor Control Block(KPRCB)中定,Each entry(KPRCB定義的_PP_LOOKASIDE_LIST) holds 2 single chained lists of nt!_GENERAL_LOOKASIDE structures: one "per processor" P, one "system wide" L

4. 不會存在"找零錢"的現象(類似應用層的heap)

利用windbg查看相關值。

查看fs:[20]的值或使用!prcb命令,都可以得到KPRCB:

– PPPagedLookasideList for paged pool.

– PPNPagedLookasideList for nonpaged pool.

– PPLookasideList for frequently requested fixed size allocations(比如I/O 請求包和memory descriptor lists?)

P: "per processor"

L: "system wide"

關於Lookasdide的單項鏈表是怎么管理其空閑鏈表的,還未知,留疑。
2.8 Large Pool

(存在多處疑問)

如果需求的pool大於4080 bytes,ListHeads將無法滿足。用戶調用nt!ExpAllocateBigPool函數,而系統是使用nt!AllcocatePoolPages來處理。當pool page allocator分配了一塊poolA "frag" chunk(BlockSize=1,previous size 0)會馬上加入到這塊page,便於ListHeads來管理這塊剩下的page

  

3.分配和釋放算法

3.1 分配

內核函數ExAllocatePoolWithTag(或者其衍生函數)來負責pool的分配,該函數會依次嘗試Lookaside listsListHeads lists最后才會向pool page allocator申請一塊page

1. 如果沒有找到剛好合適的chunk,會對返回的chunk進行拆分。

2. 如果需要,會擴充pool的大小。

分配算法的偽代碼(xp):

分配算法的偽代碼(win7):

增加了session paged pool safe unlink

如果返回的thunk大於所需要的,就會被分割:

1. 如果分配的thunk在頁的開始(頁的開始都是0x1000的倍數),那么就使用該thunk開始的部分    

2. 否則是否使用thunk結束的部分。

兩種情況,沒有用完的thunk都會重新加入到ListHeads合適位置,以減小碎片化。

3.2 釋放

使用內核函數ExFreePoolWithTag(或者其衍生函數)來處理pool的釋放。其會根據pool header提供的信息來操作。

1. 依次嘗試使用Lookaside ListListHeads

2. 如果相鄰的pool thunkfree,會發生合並(Merge)操作,減小碎片化。

3. 必要時會釋放page

釋放函數的偽代碼(XP)

注意發生合並的條件!

PAGE_ALIGNED(NextEntry)可以理解為判斷NextEntry是否為一個page的開始。

釋放函數的偽代碼(Win7)

增加了safe unlinkPendingFree ListsSession pool

 

Free Pool Chunk Ordering

1. 釋放到the lookasidepool descriptor ListHeads pool thunk一般是在其管理free pool thunk鏈表中的首個位置。但是被分割剩下來的thunk是放在其尾部的。

2. 一般分配pool時都會從appropriate list選用最近所使用過的。這樣可以盡可能減少CPU的使用。

4.Kernel Pool Exploitation

Traditional ListEntry Attacks (< Windows 7)

Pool BugChecks(XP)

當系統因為pool的相關原因發生異常的時候,會出現以下藍屏代碼:

0x19: BAD_POOL_HEADER

0x41: MUST_SUCCEED_POOL_EMPTY

0xc1: SPECIAL_POOL_DETECTED_MEMORY_CORRUPTION

0xc2: BAD_POOL_CALLER

其中的一些只有在"Checked Build"的版本才會出現。

 

一些BugCheck發生的條件:

0xc2|0x7:if PoolType&4=0 嘗試釋放已經釋放掉了的pool chunk

0x19|0x20:if PreviousSize of next chunk !=BlockSize of current chunk

 

Checked Build(測試版本):

BugCheck 0x19 |0x3:if (Entry->Flink)->Blink!=Entry or (Entry->Blink)->Flink!=Entry

這似乎是Win7中加入的safe unlink的思想,但是沒有在XP的消費者版本中加入該安全機制。

 

Write 4 Techniques

如有這樣一段雙向鏈表關系:

PLIST_ENTRY e,b,f;

f=e->Flink;

b=e->Blink;

 

e從雙向鏈表中卸載的時候,會發生這樣的操作:

b->Flink=f;

f->Blink=b;

Or

e->Blink->Flink=f;

e->Flink->Blink=b;

 

如果可以控制eFlink(What)Blink(Where)的值,那么借此unlink就有了一個對任意地址寫入任意數據的機會了。

*(Where)=What

*(What+4)=Where

前面兩種情況都發生在pool chunk free的時候,而后兩種情況發生在pool chunk分配的時候,第4項中的MmNonPagedPoolFreeListHeadXP下用來管理大於1 pagepool的。

所謂的Kernel Pool Overflow:

a. Write4 on Merge with Next

首先our chunk發生overflow,覆蓋掉了和其相鄰的chunk,如果被覆蓋的nextchunkfree,這個時候our chunk釋放的時候,就會和next chunk發生合並操作。但是在合並的操作發生之前,會發生一次unlink操作:nextchunk會從所在ListHeads卸載下來,而這次卸載就為"Write4"提供了機會。因為"Flink""Blink"都是我們可以控制的。

注意圖中兩個chunk的狀態(free or not free)

要讓此時的"Write4"按照預期發生,需要一些條件:

b. Write4 on Merge with Previous

和上面類似,our chunk覆蓋了下一個chunk,但是此時要釋放的並不是our chunk而是next chunk,使"Write4"發生的方法有所不同:需要在next chunkPool Header結構之前構造一個fake pool chunk,並標記為free。這樣next chunk free的時候,因為其會先檢查相鄰的pool chunk,如果發現free pool chunk,就會發生合並操作,我們構造的fake free pool chunk會從ListHeads List卸載下來,此時"Write4"就有機會了。

同樣,"Write4"發生的一些條件:

chunk we overflowed作為current chunk,描述中的previous chunknext chunk都是相對比chunk we overflowed的。

對此有一個疑問:怎么樣保證的fake chunk會從ListHeads執行卸載操作?而不是從Lookaside

下面的這兩種方法和上面的不同,都是在chunk分配的時候才會發生"Write4"

c.Write4 on ListHeads Unlink

如果overflowfree pool chunk 被再次的分配,那么其從ListHeadsunlink的時候,"Write4"就有可能發生了。條件:

1. If the chunk was requested through ListHeads list:no other constraint on BlockSize, PreviousSize…

2. 如果可以我們overflowfree pool chunkListHeads[BlockSize]->Flink

3. 下一次申請BlockSize大小的pool時,overflowchunk就會被再使用。

d.Write4 on MMFREE_POOL_ENTRY Unlink

MmNonPagedPoolFreeListHead XP下管理大於1 page free pool的雙向鏈表,MMFREE_POOL_ENTRY用來管理每一個pool chunk。和上面的方法類似,如果overflowed pool chunk被再次利用(allocated)的時候,就會產生"Write4"的機會了。

 

What & Where

上面簡單的介紹了各種"Write4"方法,那么擁有了這樣一個對任意地址寫任意內容的機會,應該怎么來利用呢?

關於pool overflows expliot的一些坑:

一些想法:

1. nt!KiDebugRoutine function pointer

內核發生異常時,KiDispatchException會檢查KiDebugRoutione是否為NULL,如果不為NULL 會進行調用。所以可以通過改寫該處的值達到讓shellcode執行的目的。

2.Context specific function pointers 上下文的函數調用?

3. Function pointers arrays

-nt!HalDispatchTable

-Interrupt Dispatch Table (IDT)

4. Kernel instructions – page is RWE!(什么意思??)

 

一些善后工作:

 

by:會飛的貓
轉載請注明:http://www.cnblogs.com/flycat-2016


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM