This is a series of posts I'm planning to write for about a year now, I did some research here and there, but sort of forget about the whole thing and never took notes properly. I wanted to explore what kind of objects we can use for kernel pool spraying, mainly how much space they consume, what attributes they have, and at the end come up with a code snippet that will take the 'pool hole size' as an input, and dynamically tell us what kind of objects to use for this in order to control the pool allocation for our overflow. So this went into my drawer for long time, and I got excited about this again, when I saw a Twitter post from @steventseeley: https://twitter.com/steventseeley/status/904443608216031233 and decided that I need to write this after all, partially for my own interest, and finally documenting it as well :)
At this point I don't know how many parts this series will have, and how fast I will progress due to lack of time, but I decided to start this, and not ignore it again.
Microsoft has a nice list of kernel objects we can create with calling user mode function, although it's not complete, it's still a good start: https://msdn.microsoft.com/library/windows/desktop/ms724485(v=vs.85).aspx
another important link, is a list of pool tags we can spot, which can also come handy when looking at the pool allocations:
In this post I want to explore the Mutex object, as that gave me a headache recently due to incomplete notes, and show how can we find and see the actual allocation in the pool space and some basic info about the object itself.
To setup the environment, we don't need to make remote kernel debugging, it's enough to do local kernel debugging, as we will only explore the kernel memory, and we don't need to setup any breakpoints for now. So a local debugging will be sufficient for our needs. For that we will need to enable debugging in Windows:
bcdedit -debug ON
After that we will need to restart the machine. Once it's done, we can fire up WinDBG, go to Kernel Debug, and select Local. It's recommended to issue the following commands to load symbols:
.symfix
.reload
At this point we can explore the kernel memory space. I will use a Win7 SP1 x86 for my demonstration.
First if we want we can get a more comprehensive list of objects with issuing the following command:
!object \ObjectTypes
which will give us something like this:
lkd> !object \ObjectTypes
Object: 8be05880 Type: (851466d8) Directory
ObjectHeader: 8be05868 (new version)
HandleCount: 0 PointerCount: 44
Directory Object: 8be05ed0 Name: ObjectTypes
Hash Address Type Name
---- ------- ---- ----
00 851d6900 Type TpWorkerFactory
851466d8 Type Directory
01 8521a838 Type Mutant
851cddb0 Type Thread
03 857c7c40 Type FilterCommunicationPort
04 8522a360 Type TmTx
05 851d29c8 Type Controller
06 8521d0b8 Type EtwRegistration
07 851fe9c8 Type Profile
8521a9c8 Type Event
851467a0 Type Type
09 8521cce0 Type Section
8521a900 Type EventPair
85146610 Type SymbolicLink
10 851d69c8 Type Desktop
851cdce8 Type UserApcReserve
11 85221040 Type EtwConsumer
8520e838 Type Timer
12 8522a8f0 Type File
851fe838 Type WindowStation
14 860a6f78 Type PcwObject
15 8521ceb0 Type TmEn
16 851d2838 Type Driver
18 8521db70 Type WmiGuid
851fe900 Type KeyedEvent
19 851d2900 Type Device
851cd040 Type Token
20 85214690 Type ALPC Port
851cd568 Type DebugObject
21 8522a9b8 Type IoCompletion
22 851cde78 Type Process
23 8521cf78 Type TmRm
24 851d6838 Type Adapter
26 852139a8 Type PowerRequest
85218448 Type Key
28 851cdf40 Type Job
30 8521c940 Type Session
8522a428 Type TmTm
31 851cdc20 Type IoCompletionReserve
32 8520e9c8 Type Callback
33 85894328 Type FilterConnectionPort
34 8520e900 Type Semaphore
This is a list of objects that can be allocated in the kernel space. We can explore several important attributes about them, by looking into them in more detail. With the dt nt!_OBJECT_TYPE <object> we can get some details about the object, like total handles, etc... but most importantly the offset to the _OBJECT_TYPE_INITIALIZER structure which will contain a whole lot of handy stuff for us. Let's see what it gives us for the Mutant object, what I want to explore here:
lkd> dt nt!_OBJECT_TYPE 8521a838
+0x000 TypeList : _LIST_ENTRY [ 0x8521a838 - 0x8521a838 ]
+0x008 Name : _UNICODE_STRING "Mutant"
+0x010 DefaultObject : (null)
+0x014 Index : 0xe ''
+0x018 TotalNumberOfObjects : 0x15f
+0x01c TotalNumberOfHandles : 0x167
+0x020 HighWaterNumberOfObjects : 0xc4d7
+0x024 HighWaterNumberOfHandles : 0xc4ed
+0x028 TypeInfo : _OBJECT_TYPE_INITIALIZER
+0x078 TypeLock : _EX_PUSH_LOCK
+0x07c Key : 0x6174754d
+0x080 CallbackList : _LIST_ENTRY [ 0x8521a8b8 - 0x8521a8b8 ]
and to read the _OBJECT_TYPE_INITIALIZER:
lkd> dt nt!_OBJECT_TYPE_INITIALIZER 8521a838+28
+0x000 Length : 0x50
+0x002 ObjectTypeFlags : 0 ''
+0x002 CaseInsensitive : 0y0
+0x002 UnnamedObjectsOnly : 0y0
+0x002 UseDefaultObject : 0y0
+0x002 SecurityRequired : 0y0
+0x002 MaintainHandleCount : 0y0
+0x002 MaintainTypeList : 0y0
+0x002 SupportsObjectCallbacks : 0y0
+0x002 CacheAligned : 0y0
+0x004 ObjectTypeCode : 2
+0x008 InvalidAttributes : 0x100
+0x00c GenericMapping : _GENERIC_MAPPING
+0x01c ValidAccessMask : 0x1f0001
+0x020 RetainAccess : 0
+0x024 PoolType : 0 ( NonPagedPool )
+0x028 DefaultPagedPoolCharge : 0
+0x02c DefaultNonPagedPoolCharge : 0x50
+0x030 DumpProcedure : (null)
+0x034 OpenProcedure : (null)
+0x038 CloseProcedure : (null)
+0x03c DeleteProcedure : 0x82afe453 void nt!ExpDeleteMutant+0
+0x040 ParseProcedure : (null)
+0x044 SecurityProcedure : 0x82ca2936 long nt!SeDefaultObjectMethod+0
+0x048 QueryNameProcedure : (null)
+0x04c OkayToCloseProcedure : (null)
This will give us two important things:
- The pool type where this object is allocated - NonPagedPool in this case
- Offset to functions (this is important during the actual exploitation part)
After this let's allocate a mutant, and find it in the kernel pool. I made a simple short python code, that will do this:
from ctypes import *
from ctypes.wintypes import *
import os, sys
kernel32 = windll.kernel32
def alloc_not_named_mutex():
hHandle = HANDLE(0)
hHandle = kernel32.CreateMutexA(None, False, None)
if hHandle == None:
print "[-] Error while creating mutex"
sys.exit()
print hex(hHandle)
if __name__ == '__main__':
alloc_not_named_mutex()
variable = raw_input('Press any key to exit...')
This will allocate an unnamed mutex for us, print its handle and wait for exit. We need the wait, so we can explore the kernel pool in WinDBG, if the process exit, the mutex will be destroyed. I got a handle of 0x70, let's see how we can find it in WinDBG. First I need to find the Python process and switch context to it, which can be done this way:
lkd> !process 0 0 python.exe
PROCESS 86e80930 SessionId: 1 Cid: 0240 Peb: 7ffd4000 ParentCid: 0f80
DirBase: bf3fd2e0 ObjectTable: a8282b30 HandleCount: 41.
Image: python.exe
lkd> .process 86e80930
Implicit process is now 86e80930
The first command will find the process for us, and the second will switch context. Then we need to query the handle, which will give us the address of the object in memory:
lkd> !handle 70
PROCESS 86e80930 SessionId: 1 Cid: 0240 Peb: 7ffd4000 ParentCid: 0f80
DirBase: bf3fd2e0 ObjectTable: a8282b30 HandleCount: 41.
Image: python.exe
Handle table at a8282b30 with 41 entries in use
0070: Object: 86e031a8 GrantedAccess: 001f0001 Entry: 8c0d80e0
Object: 86e031a8 Type: (8521a838) Mutant
ObjectHeader: 86e03190 (new version)
HandleCount: 1 PointerCount: 1
With that, we can find the pool location, and details:
lkd> !pool 86e031a8
Pool page 86e031a8 region is Nonpaged pool
86e03000 size: 98 previous size: 0 (Allocated) IoCo (Protected)
86e03098 size: 90 previous size: 98 (Allocated) MmCa
86e03128 size: 40 previous size: 90 (Allocated) Even (Protected)
86e03168 size: 10 previous size: 40 (Free) Icp
*86e03178 size: 50 previous size: 10 (Allocated) *Muta (Protected)
Pooltag Muta : Mutant objects
86e031c8 size: 40 previous size: 50 (Allocated) Even (Protected)
86e03208 size: 40 previous size: 40 (Allocated) Even (Protected)
It shows that it takes 0x50 bytes in the Nonpaged pool region. No matter how many times we repeat this, it will be consistently 0x50. The thing I didn't know is if we can allocate plenty of unnamed mutexes. It seems that we actually can. If we put our previous code into a loop, we can see that it will work, and that they can nicely spray the heap:
851ef118 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef168 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef1b8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef208 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef258 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef2a8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef2f8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef348 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef398 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef3e8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef438 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef488 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef4d8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef528 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef578 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef5c8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef618 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef668 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef6b8 size: 50 previous size: 50 (Allocated) Muta (Protected)
851ef708 size: 50 previous size: 50 (Allocated) Muta (Protected)
So what changes if we give a name to the Mutex? Here is another Python code for that:
def alloc_named_mutex(i):
hHandle = HANDLE(0)
hHandle = kernel32.CreateMutexA(None, False, "Pool spraying is cool " + str(i))
if hHandle == None:
print "[-] Error while creating mutex"
sys.exit()
print hex(hHandle)
I pass an argument, as that will be important if we want to use this for spraying, because we can't create two mutexes with the same name.
Once we create the mutex, and we follow the same logic as before, we can see a bit difference:
*871d39e8 size: 60 previous size: 30 (Allocated) *Muta (Protected)
Pooltag Muta : Mutant objects
This time it takes 0x60 bytes, and it will be consistent. We can do the same spraying etc... but with a different size. There is something here that will be important. If we take a look at the pool allocation, we can see that thee is a pointer at offset 0x20 from the beginning of the pool chunk, to the name of the Mutex:
lkd> dd 871d39e8
871d39e8 040c0006 e174754d 00000000 00000050
871d39f8 00000000 00000000 9a06fb38 002e002e
871d3a08 aab50528 00000000 00000002 00000001
871d3a18 00000000 000a000e 86e0bd80 99a4fc07
871d3a28 0008bb02 00000001 871d3a30 871d3a30
871d3a38 00000001 00000000 00000000 01d10000
871d3a48 040b000c 6d4d6956 b299b8c8 9a087020
871d3a58 a8246340 00000000 00000000 85d4f0b0
lkd> dd aab50528
aab50528 006f0050 006c006f 00730020 00720070
aab50538 00790061 006e0069 00200067 00730069
aab50548 00630020 006f006f 0020006c 006f0031
lkd> dS aab50528
006c006f "????????????????????????????????"
006c00af "????????"
My WinDBG doesn't want to print the name, but if you take a look at the UNICODE in hex, this is the name we gave to the Mutex. If we check where this string is stored:
lkd> !pool aab50528
Pool page aab50528 region is Paged pool
aab50000 size: a8 previous size: 0 (Allocated) CMDa
aab500a8 size: 28 previous size: a8 (Free) 3.7.
aab500d0 size: 28 previous size: 28 (Allocated) NtFs
aab500f8 size: 28 previous size: 28 (Allocated) MmSm
aab50120 size: 38 previous size: 28 (Allocated) CMnb Process: 86ef6760
aab50158 size: 100 previous size: 38 (Allocated) IoNm
aab50258 size: 38 previous size: 100 (Allocated) CMDa
aab50290 size: 38 previous size: 38 (Allocated) CMNb (Protected)
aab502c8 size: 28 previous size: 38 (Allocated) MmSm
aab502f0 size: 20 previous size: 28 (Allocated) CMNb (Protected)
aab50310 size: 60 previous size: 20 (Allocated) Key (Protected)
aab50370 size: 20 previous size: 60 (Allocated) SeAt
aab50390 size: d8 previous size: 20 (Allocated) FMfn
aab50468 size: 28 previous size: d8 (Allocated) CMVa
aab50490 size: 30 previous size: 28 (Allocated) CMVa
aab504c0 size: 60 previous size: 30 (Allocated) Key (Protected)
*aab50520 size: 38 previous size: 60 (Allocated) *ObNm
Pooltag ObNm : object names, Binary : nt!ob
It's in the paged pool! I will come back to this later, but I will give some spoiler here: We can create custom size allocations in the paged pool area with using named Mutexes, and the size will depend on the name we give. Super useful for spraying in the paged pool.
No comments:
Post a Comment