This website is supported, hosted and funded by Corelan Consulting - https://www.corelan-consulting.com. Please follow us on Facebook (@corelanconsulting) and Twitter (@corelanconsult). Corelan training schedules: https://www.corelan-training.com/index.php/training-schedules



Please consider donating: https://www.corelan.be/index.php/donate/


21,828 views

Heap Layout Visualization with mona.py and WinDBG

Introduction

Time flies. Almost 3 weeks have passed since we announced the ability to run mona.py under WinDBG.  A lot of work has been done on mona.py in the meantime.  We improved stability and performance, updated to pykd.pyd 0.2.0.14 and ported a few additional immlib methods to windbglib.

I figured this would be a good time to look at 2 "new" features in mona.py:

  • heap
  • kb (knowledgebase)

Heap

Some of you may feel cold shivers running down your spine when you hear the word "Heap".  Perhaps you feel comfortable about your ability to explore what is stored on the thread stack, but when it comes down to heap, you’re pretty much blind or ignorant.

That’s why I decided to work on improving the "heap" function in mona.py.  Note that I will be using WinDBG in this post because "heap" takes advantage of WinDBG symbols.  The function will work on Immunity Debugger as well, but its abilities and output will be limited.

Before looking at the various mona commands, let’s agree on a couple definitions. The word "chunk" will be used to refer to a region of memory.  The word "block" is used to reference 8 bytes of memory.  "Blocks" are typically used as the unit for certain size fields in heap chunk headers.

Without further ado, let’s look at what "heap" can do.  For the sake of this exercise, I have attached WinDBG to calc.exe on XP SP3.  Of course, everything will work on Windows 7 as well (32bit or 32bit in WOW64).

If you want to follow along, please update mona.py first :

0:014> .load pykd.pyd
0:014> !py mona update
Hold on...
[+] Version compare :
    Current Version : '2.0', Current Revision : 322
    Latest Version : '2.0', Latest Revision : 322
[+] You are running the latest version
[+] Locating windbglib path
[+] Checking if C:\Program Files\Debugging Tools for Windows (x86)\windbglib.py needs an update...
[+] Version compare :
    Current Version : '1.0', Current Revision : 108
    Latest Version : '1.0', Latest Revision : 108
[+] You are running the latest version

[+] This mona.py action took 0:00:07.579000

If you don’t have mona & windbglib yet, look here :

First, run !py mona help heap to see all available options:

Usage of command 'heap' :
--------------------------
Show information about various heap chunk lists
Mandatory arguments :
    -h 
: base address of the heap to query -t : where type is 'lal' (lookasidelist), 'freelist', 'segments', 'chunks', 'layout' or 'all' 'lal' and 'freelist' only work on XP/2003 'layout' will show all heap chunks and their vtables & strings. Use on WinDBG for maximum results. Optional arguments : -stat : show statistics (also works in combination with -h heap, -t segments or -t chunks -size : only show strings of at least the specified size. Works in combination with 'layout' -after : only show current & next chunk layout entries when an entry contains this data (Only works in combination with 'layout') -v : show data / write verbose info to the Log window

The -t lal, -t freelist and -t all options are outdated and need work.  The options that we are interested in right now are "segments", "chunks" and "layout".

To get a list with all process heaps, run:

0:001> !py mona heap
Hold on...
Heaps:
------
0x000a0000 (1 segment(s) : 0x000a0640) * Default process heap
0x001a0000 (1 segment(s) : 0x001a0640) 
0x001b0000 (1 segment(s) : 0x001b0640) 
0x003c0000 (1 segment(s) : 0x003c0640) 
0x003d0000 (1 segment(s) : 0x003d0640) 
0x003f0000 (1 segment(s) : 0x003f0640) 
0x00370000 (1 segment(s) : 0x00370640) 

Please specify a valid searchtype -t
Valid values are :
   lal
   freelist
   all
   segments
   chunks
   layout

[+] This mona.py action took 0:00:00.190000

The output should match with WinDBG’s !heap command, but mona will immediately show information about the segments as well.

To see the segments for a specific heap, run:

0:001> !py mona heap -h 000a0000 -t segments
Hold on...
Heaps:
------
0x000a0000 (1 segment(s) : 0x000a0640) * Default process heap
0x001a0000 (1 segment(s) : 0x001a0640) 
0x001b0000 (1 segment(s) : 0x001b0640) 
0x003c0000 (1 segment(s) : 0x003c0640) 
0x003d0000 (1 segment(s) : 0x003d0640) 
0x003f0000 (1 segment(s) : 0x003f0640) 
0x00370000 (1 segment(s) : 0x00370640) 

[+] Processing heap 0x000a0000
Segment List for heap 0x655360:
---------------------------------
Heap : 0x000a0000 : Segment 0x000a0640 - 0x001a0000 (FirstEntry: 0x000a0680 - LastValidEntry: 0x001a0000)

[+] This mona.py action took 0:00:00.327000

(note: if you omit the -h parameter, mona.py will show all segments for all heaps)

If you’re lazy, you can also use the word "default" as argument to -h, to indicate that you want information for the default process heap.

To see all chunks in the segments for a heap, run:

0:001> !py mona heap -h 000a0000 -t chunks
Hold on...
Heaps:
------
0x000a0000 (1 segment(s) : 0x000a0640) * Default process heap
0x001a0000 (1 segment(s) : 0x001a0640) 
0x001b0000 (1 segment(s) : 0x001b0640) 
0x003c0000 (1 segment(s) : 0x003c0640) 
0x003d0000 (1 segment(s) : 0x003d0640) 
0x003f0000 (1 segment(s) : 0x003f0640) 
0x00370000 (1 segment(s) : 0x00370640) 

[+] Preparing output file 'heapchunks.txt'
    - (Re)setting logfile heapchunks.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] Processing heap 0x000a0000
Segment List for heap 0x655360:
---------------------------------
Heap : 0x000a0000 : Segment 0x000a0640 - 0x001a0000 (FirstEntry: 0x000a0680 - LastValidEntry: 0x001a0000)
    Nr of chunks : 584 
    _HEAP_ENTRY  psize   size  unused  UserPtr   UserSize
       000a0680  00008  01808   00008  000a0688  00001800 (6144) (Busy)
       000a1e88  00301  00210   00008  000a1e90  00000208 (520) (Busy)
       000a2098  00042  00228   0000e  000a20a0  0000021a (538) (Busy)
       000a22c0  00045  00030   0000e  000a22c8  00000022 (34) (Busy)
<...> 
       000b4110  00025  00040   00008  000b4118  00000038 (56) (Busy)
       000b4150  00008  00128   00008  000b4158  00000120 (288) (Busy)
       000b4278  00025  03d88   00008  000b4280  00003d80 (15744) (Last)
       0x000b8000 - 0x001a0000 (end of segment) : uncommitted

[+] This mona.py action took 0:00:03.027000

The output of this command is also written into heapchunks.txt.

Again, you can show all chunks for all segments in all heaps by omitting the -h parameter.

Of course, the output should be exactly the same as the chunk-related part in the output of the  !heap -h command in WinDBG, but mona will show a little bit more information about each chunk, making it easier to find the information you need right away.

To see some statistics about the chunks and their sizes, you can use the -stat parameter in combination with -t chunks:

0:001> !py mona heap -h 000a0000 -t chunks -stat
Hold on...
Heaps:
------
0x000a0000 (1 segment(s) : 0x000a0640) * Default process heap
0x001a0000 (1 segment(s) : 0x001a0640) 
0x001b0000 (1 segment(s) : 0x001b0640) 
0x003c0000 (1 segment(s) : 0x003c0640) 
0x003d0000 (1 segment(s) : 0x003d0640) 
0x003f0000 (1 segment(s) : 0x003f0640) 
0x00370000 (1 segment(s) : 0x00370640) 

[+] Preparing output file 'heapchunks.txt'
    - (Re)setting logfile heapchunks.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] Processing heap 0x000a0000
Segment List for heap 0x655360:
---------------------------------
Heap : 0x000a0000 : Segment 0x000a0640 - 0x001a0000 (FirstEntry: 0x000a0680 - LastValidEntry: 0x001a0000)
    Nr of chunks : 584 
    _HEAP_ENTRY  psize   size  unused  UserPtr   UserSize
    Segment Statistics:
    Size : 0x3d80 (15744) : 1 chunks (0.17 %)
    Size : 0x1800 (6144) : 1 chunks (0.17 %)
    Size : 0x1258 (4696) : 1 chunks (0.17 %)
    Size : 0x928 (2344) : 2 chunks (0.34 %)
    Size : 0x888 (2184) : 1 chunks (0.17 %)
    Size : 0x7ac (1964) : 1 chunks (0.17 %)
    Size : 0x586 (1414) : 1 chunks (0.17 %)
    Size : 0x4e4 (1252) : 1 chunks (0.17 %)
    Size : 0x4b0 (1200) : 1 chunks (0.17 %)
    Size : 0x494 (1172) : 1 chunks (0.17 %)
    Size : 0x480 (1152) : 1 chunks (0.17 %)
    Size : 0x400 (1024) : 1 chunks (0.17 %)
    Size : 0x314 (788) : 2 chunks (0.34 %)
    Size : 0x30e (782) : 1 chunks (0.17 %)
    Size : 0x308 (776) : 1 chunks (0.17 %)
    Size : 0x2fa (762) : 1 chunks (0.17 %)
    Size : 0x2f8 (760) : 1 chunks (0.17 %)
    
    < . . . >

    Total chunks : 584


Global statistics
  Size : 0x3d80 (15744) : 1 chunks (0.17 %)
  Size : 0x1800 (6144) : 1 chunks (0.17 %)
  Size : 0x1258 (4696) : 1 chunks (0.17 %)
  Size : 0x928 (2344) : 2 chunks (0.34 %)
  Size : 0x888 (2184) : 1 chunks (0.17 %)
  Size : 0x7ac (1964) : 1 chunks (0.17 %)
  Size : 0x586 (1414) : 1 chunks (0.17 %)
  Size : 0x4e4 (1252) : 1 chunks (0.17 %)
  Size : 0x4b0 (1200) : 1 chunks (0.17 %)
  Size : 0x494 (1172) : 1 chunks (0.17 %)
  Size : 0x480 (1152) : 1 chunks (0.17 %)
  Size : 0x400 (1024) : 1 chunks (0.17 %)
  Size : 0x314 (788) : 2 chunks (0.34 %)
  Size : 0x30e (782) : 1 chunks (0.17 %)
  Size : 0x308 (776) : 1 chunks (0.17 %)
  Size : 0x2fa (762) : 1 chunks (0.17 %)
  
  < . . . >

  Total chunks : 584

[+] This mona.py action took 0:00:01.562000

So far so good, let’s move on.

 

Heap layout

The "layout" routine take things one step further. It will get all process heaps (or just a selected heap), query all segments associated with a heap, get all chunks inside a segment, and attempt to discover what type of data is stored in that chunk.  Currently, mona.py is able to discover strings (ascii or unicode), BSTR objects, and objects that have a vtable.  When running the layout feature under Immunity Debugger, you won’t see any objects that have a vtable.  Not much I can do about it.

The output of the "layout" function will be written into the "heaplayout.txt" file.  We’ll take a look at some examples in a few moments.

There are a couple of parameters that will influence the output or behaviour of the "layout" command, allowing you to fine-tune the amount of information, as well as the type of information you want to see:

  • -v : this will cause all information to be written into the Log window as well.
  • -fast : this will skip the discovery of object sizes
  • -size : this will cause mona.py to skip string related entries that are smaller than the provided size
  • -after : this will cause mona.py to ignore entries inside a chunk until either a string or vtable reference is found that contains this .  It will then output everything for the current chunk.  

We’ll use 3 examples to demonstrate how mona.py can help you to see what is stored in the heap, and where certain data is placed in relation to other data on the stack.

Overall Layout

To get the maximum amount of information, simply run

!py mona heap -t layout -v

The output is grouped per heap, per segment, per chunk.  Let’s look a random location in the output :

----- Heap 0x003d0000, Segment 0x003d0640 - 0x003e0000 (1/1) -----
chunk 0x003d0680 (Usersize 0x1800, chunksize 0x1808) : Busy
chunk 0x003d1e88 (Usersize 0x88, chunksize 0x90) : Busy
chunk 0x003d1f18 (Usersize 0x88, chunksize 0x90) : Busy
chunk 0x003d1fa8 (Usersize 0x3df, chunksize 0x448) : Free
  +003f @ 003d1fe7->003d200b : String (Data : 0x23/35 bytes, 0x23/35 chars) : ComSpec=C:\WINDOWS\system32\cmd.exe
  +0021 @ 003d202c->003d2053 : String (Data : 0x26/38 bytes, 0x26/38 chars) : HOMEPATH=\Documents and Settings\peter
  +003c @ 003d208f->003d21bd : String (Data : 0x12d/301 bytes, 0x12d/301 chars) : Path=C:\Perl\site\bin;C:\Perl\bin;C:\WINDOWS\system32;C:\WINDOWS;C:\WINDOWS\System32\Wbem;c:\python2...
        @ 003d2168           : Object : 74726f54 MSCTF!CLBarItemSinkProxy::`vftable'+0x8 
  +0055 @ 003d21bd->003d21f6 : String (Data : 0x38/56 bytes, 0x38/56 chars) : PATHEXT=.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH
  +001b @ 003d2211->003d2256 : String (Data : 0x44/68 bytes, 0x44/68 chars) : PROCESSOR_IDENTIFIER=x86 Family 6 Model 15 Stepping 11, GenuineIntel
  +0081 @ 003d22d7->003d22fc : String (Data : 0x24/36 bytes, 0x24/36 chars) : TEMP=C:\DOCUME~1\peter\LOCALS~1\Temp
  +0000 @ 003d22fc->003d2320 : String (Data : 0x23/35 bytes, 0x23/35 chars) : TMP=C:\DOCUME~1\peter\LOCALS~1\Temp
  +0023 @ 003d2343->003d236f : String (Data : 0x2b/43 bytes, 0x2b/43 chars) : USERPROFILE=C:\Documents and Settings\peter
  +0000 @ 003d236f->003d23bb : String (Data : 0x4b/75 bytes, 0x4b/75 chars) : VS100COMNTOOLS=C:\Program Files\Microsoft Visual Studio 10.0\Common7\Tools\

In this excerpt, we see that

  • The first 3 chunks of segment 0x003d0640 in heap 0x003d0000 do not seem to contain strings or vtable references
  • The fourth chunk contains strings and an object.

Let’s focus on the first String in the 4th chunk for a moment.  The line contains the following values:

  • +003F : this indicates the distance from the end of the previous entry (or the start of the heap chunk in case of the first entry) to the start of the current entry (which is a string in this case)
  • @ 003d1fe7->003d200b : this is the memory range consumed by the current entry.  In case of a string (ascii or unicode), the first pointer will point to the start of the string.  In case of a BSTR, the first pointer will point to the start of the BSTR header.  In case of an "object", the pointer contains the address of the vtable reference.
  • "String" : this field indicates the type of entry, and is followed by the size of the data in  bytes and chars. Bytes indicate the number of bytes consumed by this entry, including header and terminator bytes.  In case of unicode, the bytes will be twice the amount of chars. 
  • Data : if the entry is a string, you’ll see up to the first 100 chars.  If the entry is an object, you will get the vtable reference.

Whenever a vtable reference is found, mona.py will attempt to find the size of the object it belongs to.  If it was not able to do that, you will only see the start address of the object, but no end address.    The offset placed at the start of on the line right after the object might indicate how big the object is.  You will usually need to subtract 0x8 or 0xc bytes from that value to get the actual object size.

On top of that, it’s very common that a vftable contains multiple function pointers.  Mona.py will group all function pointers within a distance of 0xc bytes together and only show one entry about it in the log.  Not perfect, but avoids a good amount of noise.

Let’s look at 2 more examples to demonstrate additional features.

Heap Spray

Imagine you want to see if a heap spray was successful, find where it is and how big the chunks are.  Let’s try the following basic heapspray script on IE8, XP SP3: