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.






No comments: