#dokydoky

Windows Kernel Pool 본문

Windows Exploit

Windows Kernel Pool

dokydoky 2016. 11. 30. 01:31

안녕하세요. dokydoky입니다.

Windows Kernel에서 pool을 공략하기 위해서는 pool에 대한 이해가 필요합니다.

Windows7 32bit를 기준으로 Kernel pool, ListHeads, Lookaside List에 대해서 설명해보았습니다.


0x1. Kernel Pool

시스템 초기화때, 메모리 관리자가 시스템 노드의 갯수에 따라 동적인 사이즈의 메모리 pool을 만듭니다. 

각 pool은 pool descriptor라는 구조체에 의해 정의되며, pool descriptor는 pool의 사용을 추적하고, 메모리 타입과 같은 pool 속성을 정의합니다.

[pool descriptor structure]

0: kd> dt nt!_POOL_DESCRIPTOR
   +0x000 PoolType         : _POOL_TYPE
   +0x004 PagedLock        : _KGUARDED_MUTEX
   +0x004 NonPagedLock     : Uint4B
   +0x040 RunningAllocs    : Int4B
   +0x044 RunningDeAllocs  : Int4B
   +0x048 TotalBigPages    : Int4B
   +0x04c ThreadsProcessingDeferrals : Int4B
   +0x050 TotalBytes       : Uint4B
   +0x080 PoolIndex        : Uint4B
   +0x0c0 TotalPages       : Int4B
   +0x100 PendingFrees     : Ptr32 Ptr32 Void
   +0x104 PendingFreeDepth : Int4B
   +0x140 ListHeads        : [512] _LIST_ENTRY

[Kernel Pool 종류]

 - Paged Pool : 하드디스크에 페이징되는 pool

 - Non-Paged Pool : 하드디스크에 페이징되지 않는 pool

 - Session Pool


Paged Pool과 Non-Paged Pool의 갯수는 아래와 같습니다.

 

Paged Pool (nt!ExpNumberOfPagedPools)

Non-Paged Pool (nt!ExpNumberOfNonPagedPools)

 uniprocessor

4개

nt!ExpPagedPoolDescriptor[1] ~ [4]

 1개

nt!PoolVector[0]

 multiprocessor

 node 당 1개

 node 당 1개

nt!ExpNonPagedPoolDescriptor array

* uni/multi processor 모두 prototype pool이 1개 존재(nt!ExpPagedPoolDescriptor[0])

  prototype pool을 Paged Pool로 사용 시, paged pool을 총 5개까지 사용가능



pool은 아래 그림과 같이 page 단위(크기 : 0x1024)로 할당됩니다.


pool에 데이터를 할당할 때는 ExAllocatePoolWithTag API를 사용하며, 할당되는 데이터를 chunk라고 표현합니다.

PVOID ExAllocatePoolWithTag(
  _In_ POOL_TYPE PoolType,
  _In_ SIZE_T    NumberOfBytes,
  _In_ ULONG     Tag
);
chunk들은 page에 할당되며, page가 가득차면 새로운 page가 할당되어 그곳에 다시 chunk가 할당됩니다.

chunk들이 free 된다면 ListHead와 Lookaside List에서 관리됩니다.



0x2. ListHead

chunk들은 각각 8바이트의 _POOL_HEADER를 가지고 있습니다.

0: kd> dt nt!_POOL_HEADER
   +0x000 PreviousSize     : Pos 0, 9 Bits
   +0x000 PoolIndex        : Pos 9, 7 Bits
   +0x002 BlockSize        : Pos 0, 9 Bits
   +0x002 PoolType         : Pos 9, 7 Bits
   +0x000 Ulong1           : Uint4B
   +0x004 PoolTag          : Uint4B
   +0x004 AllocatorBackTraceIndex : Uint2B
   +0x006 PoolTagHash      : Uint2B

그리고 free되면 _POOL_HEADER 뒤에 _LIST_ENTRY가 붙습니다.
0: kd> dt nt!_LIST_ENTRY
   +0x000 Flink            : Ptr32 _LIST_ENTRY
   +0x004 Blink            : Ptr32 _LIST_ENTRY

위에서 pool은 pool descriptor에 의해 관리된다고 했습니다.

pool descriptor를 보면 ListHeads라는 멤버가 있는데, 이 멤버는 free된 chunk들을 double linked list로 관리합니다.

이 때, _LIST_ENTRY 구조체가 사용됩니다.

0: kd> dt nt!_POOL_DESCRIPTOR
   +0x000 PoolType         : _POOL_TYPE
   +0x004 PagedLock        : _KGUARDED_MUTEX
   +0x004 NonPagedLock     : Uint4B
   +0x040 RunningAllocs    : Int4B
   +0x044 RunningDeAllocs  : Int4B
   +0x048 TotalBigPages    : Int4B
   +0x04c ThreadsProcessingDeferrals : Int4B
   +0x050 TotalBytes       : Uint4B
   +0x080 PoolIndex        : Uint4B
   +0x0c0 TotalPages       : Int4B
   +0x100 PendingFrees     : Ptr32 Ptr32 Void
   +0x104 PendingFreeDepth : Int4B
   +0x140 ListHeads        : [512] _LIST_ENTRY

[출처 : http://www.slideshare.net/scovetta/kernelpool]


실제로 Paged Pool에서 ListHeads를 살펴봅시다.

Paged Pool 출력
  ExpPagedPoolDescriptor[0] : prototype pool
  ExpPagedPoolDescriptor[1]~[4] : Paged Pool

0: kd> dd nt!ExpPagedPoolDescriptor L5
829ab018  8432c000 8432d140 8432e280 8432f3c0
829ab028  84330500



모든 kernel pool은 pool의 정보를 관리하는 구조체가 필요합니다.
Paged Pool, Non-Paged pool은 _POOL_DESCRIPTOR 구조체를 이용해 정보를 관리합니다.
ExpPagedPoolDescriptor[1]의 정보를 확인해봅시다. 
 
0: kd> dt nt!_POOL_DESCRIPTOR 8432d140
   +0x000 PoolType         : 1 ( PagedPool )
   +0x004 PagedLock        : _KGUARDED_MUTEX
   +0x004 NonPagedLock     : 1
   +0x040 RunningAllocs    : 0n1364173
   +0x044 RunningDeAllocs  : 0n1309226
   +0x048 TotalBigPages    : 0n879
   +0x04c ThreadsProcessingDeferrals : 0n0
   +0x050 TotalBytes       : 0xe31480
   +0x080 PoolIndex        : 1
   +0x0c0 TotalPages       : 0n2896
   +0x100 PendingFrees     : 0xad57ee08  -> 0xabe01d00 Void
   +0x104 PendingFreeDepth : 0n29
   +0x140 ListHeads        : [512] _LIST_ENTRY [ 0x8432d280 - 0x8432d280 ]

PoolType은 PagedPool임을 확인할 수 있습니다.
멤버들 중 ListHeads는 _LIST_ENTRY 구조체의 배열이며,
free된 chunk들을 double linked list로 관리하고 있습니다.
_LIST_ENTRY구조체는 아래와 같이 앞 chunk의 주소, 뒤 chunk의 주소를 가르키는 구조체입니다.

0: kd> dt nt!_LIST_ENTRY
   +0x000 Flink            : Ptr32 _LIST_ENTRY
   +0x004 Blink            : Ptr32 _LIST_ENTRY

각 chunk들은 _POOL_HEADER를 가지고 있으며, free가 되면 아래와 같이 _LIST_ENTRY가 추가됩니다.

| _POOL_HEADER | _LIST_ENTRY | .... |


ListHeads는 8바이트 크기로 증가하며 최대 4080바이트까지 할당까지 사용됩니다.
ListHeads 배열은 block size로 정렬되며, BlockSize = (NumberOfBytes+0xF)로 계산됩니다.

0: kd> dd /c 2 8432d140+140
8432d280  8432d280 8432d280    -> ListHeads[0]
8432d288  925ecc18 ad57edf8    -> ListHeads[1], Blocksize=0x2, size=0x10 (header 8바이트)
8432d290  abbe9bc0 b0ab5df0    -> ListHeads[2], Blocksize=0x3, size=0x18
8432d298  ac5f31f0 ab28de18    -> ListHeads[3]
8432d2a0  ad1579d8 8db6d380          ...
8432d2a8  acf59d88 ad4c8388
8432d2b0  81f685a8 ac5472b0
8432d2b8  b0bba868 b1759918
8432d2c0  a8f50410 ad5ec100
8432d2c8  acf22580 adc03778
8432d2d0  8432d2d0 8432d2d0
8432d2d8  b16a37d0 ad7e24b0
8432d2e0  ac209848 8c4d0c20
8432d2e8  ac224e00 99a65c18
8432d2f0  8c551c10 ad4b63c0
8432d2f8  8432d2f8 8432d2f8
            ...

0: kd> dt -r nt!_LIST_ENTRY 925ecc18 
 [ 0x99a4b908 - 0x8432d288 ]
   +0x000 Flink            : 0x99a4b908 _LIST_ENTRY [ 0x888ac338 - 0x925ecc18 ]
      +0x000 Flink            : 0x888ac338 _LIST_ENTRY [ 0x8c530f78 - 0x99a4b908 ]
         +0x000 Flink            : 0x8c530f78 _LIST_ENTRY [ 0x92533748 - 0x888ac338 ]
         +0x004 Blink            : 0x99a4b908 _LIST_ENTRY [ 0x888ac338 - 0x925ecc18 ]
      +0x004 Blink            : 0x925ecc18 _LIST_ENTRY [ 0x99a4b908 - 0x8432d288 ]
         +0x000 Flink            : 0x99a4b908 _LIST_ENTRY [ 0x888ac338 - 0x925ecc18 ]
         +0x004 Blink            : 0x8432d288 _LIST_ENTRY [ 0x925ecc18 - 0xad57edf8 ]
   +0x004 Blink            : 0x8432d288 _LIST_ENTRY [ 0x925ecc18 - 0xad57edf8 ]
      +0x000 Flink            : 0x925ecc18 _LIST_ENTRY [ 0x99a4b908 - 0x8432d288 ]
         +0x000 Flink            : 0x99a4b908 _LIST_ENTRY [ 0x888ac338 - 0x925ecc18 ]
         +0x004 Blink            : 0x8432d288 _LIST_ENTRY [ 0x925ecc18 - 0xad57edf8 ]
      +0x004 Blink            : 0xad57edf8 _LIST_ENTRY [ 0x8432d288 - 0xabe01cf0 ]
         +0x000 Flink            : 0x8432d288 _LIST_ENTRY [ 0x925ecc18 - 0xad57edf8 ]
         +0x004 Blink            : 0xabe01cf0 _LIST_ENTRY [ 0xad57edf8 - 0xac02e208 ]


poollist라는 확장 플러그인을 이용해 아래와 같이 확인할 수도 있습니다.

0: kd> !poollist
Pool Descriptors
 Unknown[0]:                    00000000
 Unknown[0]:                    00000000
 PagedPool[0]:                  8432c000
 PagedPool[1]:                  8432d140
 PagedPool[2]:                  8432e280
 PagedPool[3]:                  8432f3c0
 PagedPool[4]:                  84330500
 PagedPoolSession[0]:           8aeb2e00

0: kd> !poolinfo -v -f 8432d140 -b 10
Pool Descriptor PagedPool[1] at 8432d140
 PoolType:                     01 (PagedPool)
 PoolIndex:                    00000001
 PendingFreeDepth:             0000001D
 RunningAllocs:                0014D0CD
 RunningDeAllocs:              0013FA2A
 TotalBigPages:                0000036F
 ThreadsProcessingDeferrals:   00000000
 TotalBytes:                   00E31480
 TotalPages:                   00000B50
ListHeads[01]: size=010
  925ecc10: size:010 prev:018 index:01 type:00 tag:CMVI
  99a4b900: size:010 prev:018 index:01 type:00 tag:CMVI
  888ac330: size:010 prev:330 index:01 type:00 tag:CMVI
  8c530f70: size:010 prev:070 index:01 type:00 tag:CMVI
  92533740: size:010 prev:068 index:01 type:00 tag:CMVI
  8c50e210: size:010 prev:068 index:01 type:00 tag:CMVI
  81e8f2a8: size:010 prev:060 index:01 type:00 tag:Pp  
  888f3358: size:010 prev:038 index:01 type:00 tag:CMVI
  88995938: size:010 prev:078 index:01 type:00 tag:CMVI
  905d1780: size:010 prev:008 index:01 type:00 tag:CMVI
  8bed9570: size:010 prev:020 index:01 type:00 tag:CMVI
  99fc4f88: size:010 prev:048 index:01 type:00 tag:CMVI
  88951350: size:010 prev:018 index:01 type:00 tag:CMVI
  888ecf38: size:010 prev:028 index:01 type:00 tag:CMVI
  8beda640: size:010 prev:0A0 index:01 type:00 tag:CMVI
  99a0a8c0: size:010 prev:050 index:01 type:00 tag:CMVI
  8df97bb8: size:010 prev:098 index:01 type:00 tag:CMVI
  99fe66b8: size:010 prev:3F0 index:01 type:00 tag:CMVI
  87e62a40: size:010 prev:030 index:01 type:00 tag:CMVI
  925a43a0: size:010 prev:008 index:01 type:00 tag:CMVI
  94f2f3d0: size:010 prev:050 index:01 type:00 tag:CMVI
  8894b768: size:010 prev:048 index:01 type:00 tag:CMVI
  888c2470: size:010 prev:028 index:01 type:00 tag:CMVI
  8bf6be70: size:010 prev:038 index:01 type:00 tag:CMVI
  88826258: size:010 prev:018 index:01 type:00 tag:CMVI
  8bedf660: size:010 prev:038 index:01 type:00 tag:CMVI
  93ab9ce8: size:010 prev:018 index:01 type:00 tag:CMVI
  8c46a310: size:010 prev:040 index:01 type:00 tag:CMVI
  88957d78: size:010 prev:020 index:01 type:00 tag:CMVI

                          .....


poolinfo를 통해 본 주소가 직접 ListHead에서 확인한 주소보다 8만큼 작은 이유는 POOL_HEADER 때문.



0x3. Lookaside Lists

kernel은 작은 pool chunk들의 빠른 할당, 해제를 위해 singly-linked lookaside list를 사용합니다.

ListHeads가 pool별로 할당이 되었다면, Lookaside Lists는 프로세서별로 할당이 됩니다.


Lookaside List 는 processor별 KPRCB에 할당

0: kd> !prcb
PRCB for Processor 0 at 8293ae20:
Current IRQL -- 28
Threads--  Current 82944480 Next 00000000 Idle 82944480
Processor Index 0 Number (0, 0) GroupSetMember 1
Interrupt Count -- 0000491f
Times -- Dpc    00000015 Interrupt 0000005c 
         Kernel 0000059f User      0000003d 

KPRCB의 주소를 확인해보면 8293ae20임을 할 수 있습니다.


0: kd> dt nt!_KPRCB 8293ae20
    ....
   +0x5a0 PPLookasideList  : [16] _PP_LOOKASIDE_LIST
   +0x620 PPNPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
   +0xf20 PPPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
    ...

KPRCB 구조체는 paged(PPPagedLookasideList), non-paged(PPNPagedLookasideList) 그리고 주로 고정된 사이즈 할당에 사용되는 특수목적의 lookaside list(PPLookasideList)도 포함하고 있습니다. paged, non paged lookaside list는 최대 32크기의 블록사이즈를 갖습니다. _GENERAL_LOOKASIDE_POOL 구조체는 아래와 같습니다.


0: kd> dt nt!_GENERAL_LOOKASIDE_POOL
   +0x000 ListHead         : _SLIST_HEADER
   +0x000 SingleListHead   : _SINGLE_LIST_ENTRY
   +0x008 Depth            : Uint2B
   +0x00a MaximumDepth     : Uint2B
   +0x00c TotalAllocates   : Uint4B
   +0x010 AllocateMisses   : Uint4B
   +0x010 AllocateHits     : Uint4B
   +0x014 TotalFrees       : Uint4B
   +0x018 FreeMisses       : Uint4B
   +0x018 FreeHits         : Uint4B
   +0x01c Type             : _POOL_TYPE
   +0x020 Tag              : Uint4B
   +0x024 Size             : Uint4B
   +0x028 AllocateEx       : Ptr32     void* 
   +0x028 Allocate         : Ptr32     void* 
   +0x02c FreeEx           : Ptr32     void 
   +0x02c Free             : Ptr32     void 
   +0x030 ListEntry        : _LIST_ENTRY
   +0x038 LastTotalAllocates : Uint4B
   +0x03c LastAllocateMisses : Uint4B
   +0x03c LastAllocateHits : Uint4B
   +0x040 Future           : [2] Uint4B

0: kd> dt nt!_SINGLE_LIST_ENTRY
   +0x000 Next             : Ptr32 _SINGLE_LIST_ENTRY

SingleListHead.Next는 singly lookaside List의 첫 번째 free pool chunk를 가리킵니다.
그리고 Depth는 리스트의 길이를 명시하고, 보통 4(nt!ExMinimumLookasideDepth), 최대 256의 크기를 가집니다. 


poollist 플러그인을 이용하면 아래와 같이 사용할 수 있습니다.

0: kd> !poollist -l

0: kd> !poolinfo -s -l -t NonPaged [address]

0: kd> !poolinfo -s -l -t Paged [address]



0x4. 참고자료

https://media.blackhat.com/bh-dc-11/Mandt/BlackHat_DC_2011_Mandt_kernelpool-wp.pdf
PoC Training "Windows Kernel Exploitation" by Ashfaq Ansari




Comments