Kernel Pool Attack

본 문서는 MANDT-kernelpool-PAPER 를 바탕으로 작성되었습니다.

 

1. ListEntry Flink Overwrite

for(n = BlockSize – 1; n < 512; n++){
 if(ListHeads[n].Flink == &ListHeads[n]){ // empty
 continue;
 }
 }

Kernel에서 Pool chunk를 ListHeads의 목록에서 할당할 때 Flink의 유효성을 제대로 검사하지 못하는 대신 ListHeads[n] LIST_ENTRY 구조체의 유효성만을 검사한다.

공격자가 free된 chunk에 POOL overflow를 일으킬 수 있다면 Flink를 변조시킨 후 같은 size의  chunk를 다시 할당 요청하게 되면 변조된 주소에 memory가 할당되게 된다.

kd> dc 85b73600
 85b73600 08080008 ee657645 85b73688 00000040 ....Eve..6..@...
 85b73610 00000000 00000000 00000000 00000000 ................
 85b73620 00000000 00080001 00000000 00000000 ................
 85b73630 00040001 00000000 85b73638 85b73638 ........86..86..
 85b73640 04080008 ee657645 00000000 00000040 ....Eve.....@...
 85b73650 00000000 00000000 00000001 00000001 ................
 85b73660 00000000 0008000c 87053940 00000000 ........@9......
 85b73670 00040001 00000000 85b73678 85b73678 ........x6..x6..

Event 객체를 선언하고 free시킨 상태이다. 붉은 부분을 보면 Flink 부분에 data가 있는 것을 확인할 수 있다. 이는 free된 chunk의 주소값이다.

kd> dc 85b73600
 85b73600 08080008 ee657645 10101010 00000040 ....Eve.....@...
 85b73610 00000000 00000000 00000000 00000000 ................
 85b73620 00000000 00080001 00000000 00000000 ................
 85b73630 00040001 00000000 85b73638 85b73638 ........86..86..
 85b73640 04080008 ee657645 00000000 00000040 ....Eve.....@...
 85b73650 00000000 00000000 00000001 00000001 ................
 85b73660 00000000 0008000c 87053940 00000000 ........@9......
 85b73670 00040001 00000000 85b73678 85b73678 ........x6..x6..ㄹ

Flink 부분을 0x10101010으로 변조하고 Event 객체를 더 할당받게 만들었다.

잘못된 주소에 memory를 할당받으려 해 crash가 났다. 변조한 주소값이 할당 가능한 주소라면 memory 할당이 가능해진다.

 

2. Lookaside Next Pointer Overwrite

Lookaside List는 작은 크기 chunk의 빠른 할당과 해제를 위해서 만든 list 구조이다. Chunk가 해제되면 Flink 주소에 single linked list를 형성한다.

kd> !prcb
 PRCB for Processor 0 at 82f2bd20:
 Current IRQL -- 0
 Threads-- Current 8518dc80 Next 00000000 Idle 82f35380
 Processor Index 0 Number (0, 0) GroupSetMember 1
 Interrupt Count -- 0003159b
 Times -- Dpc 000000d2 Interrupt 00000076
 Kernel 000205d6 User 00000e21

kd> dt _KPRCB 82f2bd20
 nt!_KPRCB
 +0x000 MinorVersion : 1
 +0x002 MajorVersion : 1
 +0x004 CurrentThread : 0x8518dc80 _KTHREAD
 .......
 +0x5a0 PPLookasideList : [16] _PP_LOOKASIDE_LIST
 +0x620 PPNPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
 +0xf20 PPPagedLookasideList : [32] _GENERAL_LOOKASIDE_POOL
 ........

Kernel 구조체 KPRCB를 디버거로 살펴보면 Lookaside list를 살펴볼 수 있다.

kd> dx -r1 (*((ntkrpamp!_GENERAL_LOOKASIDE_POOL *)0xffffffff82f2c388))
 (*((ntkrpamp!_GENERAL_LOOKASIDE_POOL *)0xffffffff82f2c388)) [Type: _GENERAL_LOOKASIDE_POOL]
 [+0x000] ListHead [Type: _SLIST_HEADER]
 [+0x000] SingleListHead [Type: _SINGLE_LIST_ENTRY]
 [+0x008] Depth : 0x22 [Type: unsigned short]
 [+0x00a] MaximumDepth : 0x100 [Type: unsigned short]
 [+0x00c] TotalAllocates : 0x22b8c [Type: unsigned long]
 [+0x010] AllocateMisses : 0x17edd [Type: unsigned long]
 [+0x010] AllocateHits : 0x17edd [Type: unsigned long]
 [+0x014] TotalFrees : 0x17f4c [Type: unsigned long]
 [+0x018] FreeMisses : 0x17eeb [Type: unsigned long]
 [+0x018] FreeHits : 0x17eeb [Type: unsigned long]
 [+0x01c] Type : NonPagedPool (0) [Type: _POOL_TYPE]
 [+0x020] Tag : 0x4c6f6f50 [Type: unsigned long]
 [+0x024] Size : 0x10 [Type: unsigned long]
 [+0x028] AllocateEx : 0xffffffff82f21005 [Type: void * (*)(_POOL_TYPE,unsigned long,unsigned long,_LOOKASIDE_LIST_EX *)]
 [+0x028] Allocate : 0xffffffff82f21005 [Type: void * (*)(_POOL_TYPE,unsigned long,unsigned long)]
 [+0x02c] FreeEx : 0xffffffff82f22a67 [Type: void (*)(void *,_LOOKASIDE_LIST_EX *)]
 [+0x02c] Free : 0xffffffff82f22a67 [Type: void (*)(void *)]
 [+0x030] ListEntry [Type: _LIST_ENTRY]
 [+0x038] LastTotalAllocates : 0x22b26 [Type: unsigned long]
 [+0x03c] LastAllocateMisses : 0x17e81 [Type: unsigned long]
 [+0x03c] LastAllocateHits : 0x17e81 [Type: unsigned long]
 [+0x040] Future [Type: unsigned long [2]]

PPNLookasideList[1]의 값을 보면 처음 ListHead가 나오고 그 이후로 해당 chunk의 Tag 등에 대한 정보가 나온다. 또 ListHeads의 Next를 따라가다 보면 free된 chunk끼리의 next pointer가 계속 나오게 된다.

PPNPagedLookasideList의 ListHeads와 연결되어 있는 free chunk중 특정 chunk에서 overflow를 일으킬 수 있다면 free chunk의 Flink를 변조시킨다.

그리고 같은 size의 chunk를 할당할 때마다 해당 Next pointer의 값이 반환되어 다음 할당 때 그 주소에 memory를 할당받게 된다.

결과적으로 변조된 Next pointer가 반환된다면 다음 할당 때 의도한 주소에 memory 할당을 받을 수 있게 된다.

 

3. Pending free Overwrite

kd> dt _POOL_DESCRIPTOR
 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

Pool chunk가 해제될 때 Pool Descriptor의 pendingfrees 목록에서 free되기를 기다리게 된다. 이 때 해제될 chunk들끼리는 single linked list 형태로 이뤄져있는데 그 중 하나의 chunk의 next pointer를 의도한 주소로 변조하게 되면 이후 할당시 해당 주소의 memory가 할당된다.

만약 user-mode address로 변조한다면 이후 할당에서 user-mode의 memory를 할당할 수 있다.

 

4. PoolIndex Overwrite

Pool chunk의 PoolIndex는 Pool Descriptor의 배열 색인을 나타낸다. 그래서 ListHeads 내부의 chunk는 항상 적절한 Pool Descriptor로부터 해제된다. 하지만 충분한 필터링을 거치지 않아 공격자가 PoolIndesx를 조작해 임의의 kernel memory를 덮어쓸 수 있다.

Paged Pool의 경우 PoolIndex는 항상 nt!ExpPagedPoolDescriptor에 대한 index를 나타낸다. Non-paged Pool의 경우 PoolIndex는 여러 node가 있는 경우에만 nt!ExpNonPagedPoolDescriptor에 대한 index를 나타낸다.

kd> dd nt!ExpPagedPoolDescriptor
 82f3cb60 84c35000 84c36140 84c37280 84c383c0
 82f3cb70 84c39500 00000000 00000000 00000000
 82f3cb80 00000000 00000000 00000000 00000000
 82f3cb90 00000000 00000000 00000000 00000000
 82f3cba0 00000000 00000000 00000000 00000000
 82f3cbb0 82f37940 84c35000 00000000 00000000
 82f3cbc0 00000000 00000000 00000000 00000000
 82f3cbd0 00000000 00000000 00000000 00000000

Memory를 살펴보면 index 4까지는 Descriptor의 data가 담겨져 있는 것이 보인다.

PoolIndex Overwrite는 overflow 시킬 수 있는 chunk의 PoolIndex를 조작한다. Index를 5로 조작해서 해당 chunk를 관리하는 Descriptor가 NULL pointer에 할당되게 한다. 그렇다면 NULL address에 임의로 조작 가능한 POOL_DESCRIPTOR가 생성된다.

Delayed free 적용 시, Fake pendigfrees list 를 생성해 chunk를 임의로 조작 가능하다. 이 때 PendigFreeDepth 값은 PendigFrees 목록 처리를 하기 위해 0x20보다 크거나 같아야 한다.