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


19,203 views

Analyzing heap objects with mona.py

Introduction

Hi all,

While preparing for my Advanced exploit dev course at Derbycon, I’ve been playing with heap allocation primitives in IE.  One of the things that causes some frustration (or, at least, tends to slow me down during the research) is the ability to quickly identify objects that may be useful. After all, I’m trying to find objects that contain arbitrary data, or pointers to arbitrary data, and it’s not always easy to do so because of the noise.

I decided to add a few new features to mona.py, that should allow you to find interesting objects in a faster way.  The new features are only available under WinDBG.

To get the latest version of mona, simply run !py mona up.  Since I also upgraded to the latest version of pykd, you may have to update pykd.pyd as well before you can run the latest version of mona.  (You should see update instructions in the WinDBG log window when you try to run mona with an outdated version of pykd)

dumpobj (do)

The first new feature is "dumpobj".  This mona.py command will dump the contents of an object and provide (hopefully) useful information about the contents.   The command takes the following arguments:

Usage of command 'dumpobj' :
-----------------------------
Dump the contents of an object.

Arguments:
    -a 
: Address of object -s : Size of object (default value: 0x28 or size of chunk) Optional arguments: -l : Recursively dump objects -m : Size for recursive objects (default value: 0x28)

As you can see in the output of !py mona help dumpobj above, we need to provide at least 2 arguments:

-a

: the start location (address of the object, but you can specifiy any location you want)

-s : the size of the object.   If you don’t specify the -s argument, mona will attempt to determine the size of the object.  If that is not possible, mona will dump 0x28 bytes of the object.

Additionally, you can tell mona to dump linked objects as well.  Argument -l takes a number, which refers to the number of levels for the recursive dump.  In order to somewhat limit the size of the output (and for performance reasons), only the first 0x28 bytes of the linked objects will be printed (unless you use argument -m to overrule this behavior).

Of course, it is quite trivial to dump the contents of an object in WinDBG.  The dds or dc commands will print out objects and show some information about its contents.  In some cases, the output of dds/dc is not sufficient and it would require some additional work to further analyze the object and optional objects that are linked inside this object.

Let’s look at an example.  Let’s say we have a 0x78 byte object at 0x023a1bc0.  Of course, we can dump the contents of the object using native WinDBG commands:

0:001> dds 0x023a1bc0 L 0x78/4
023a1bc0  023a1d30
023a1bc4  023a1818
023a1bc8  00000000
023a1bcc  023a1d3c
023a1bd0  023a1824
023a1bd4  baadf00d
023a1bd8  00020000
023a1bdc  00000001
023a1be0  00160014
023a1be4  023a1a38
023a1be8  013a0138
023a1bec  023a1a68
023a1bf0  00000000
023a1bf4  00000001
023a1bf8  023a18a8
023a1bfc  00000000
023a1c00  00000000
023a1c04  00000007
023a1c08  00000007
023a1c0c  023a18d0
023a1c10  00000000
023a1c14  00000000
023a1c18  00000000
023a1c1c  00000000
023a1c20  00000000
023a1c24  00000000
023a1c28  00000000
023a1c2c  00000000
023a1c30  00000000
023a1c34  00000000

0:001> dc 0x023a1bc0 L 0x78/4
023a1bc0  023a1d30 023a1818 00000000 023a1d3c  0.:...:.....<.:.
023a1bd0  023a1824 baadf00d 00020000 00000001  $.:.............
023a1be0  00160014 023a1a38 013a0138 023a1a68  ....8.:.8.:.h.:.
023a1bf0  00000000 00000001 023a18a8 00000000  ..........:.....
023a1c00  00000000 00000007 00000007 023a18d0  ..............:.
023a1c10  00000000 00000000 00000000 00000000  ................
023a1c20  00000000 00000000 00000000 00000000  ................
023a1c30  00000000 00000000                    ........

Nice. We can see all kinds of things – values that appear to be pointers, nulls, and some other "garbage".  Hard to tell what it is without looking at each value individually.

With mona, we can dump the same object, and mona will attempt to gather more information about each dword in the object:

0:001> !py mona do -a 0x023a1bc0
Hold on...
[+] No size specified, checking if address is part of known heap chunk
    Address found in chunk 0x023a1bb8, heap 0x00240000, (user)size 0x78

----------------------------------------------------
[+] Dumping object at 0x023a1bc0, 0x78 bytes

[+] Preparing output file 'dumpobj.txt'
    - (Re)setting logfile c:\logs\HeapAlloc2\dumpobj.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.

>> Object at 0x023a1bc0 (0x78 bytes):
Offset  Address      Contents    Info
------  -------      --------    -----
+00     0x023a1bc0 | 0x023a1d30  (Heap) ptr to ASCII '0::'
+04     0x023a1bc4 | 0x023a1818  (Heap) ptr to ASCII ':'
+08     0x023a1bc8 | 0x00000000  
+0c     0x023a1bcc | 0x023a1d3c  (Heap) ptr to 0x77e46464 : ADVAPI32!g_CodeLevelObjTable+0x4
+10     0x023a1bd0 | 0x023a1824  (Heap) ptr to ASCII ':'
+14     0x023a1bd4 | 0xbaadf00d  
+18     0x023a1bd8 | 0x00020000  = UNICODE ' ' 
+1c     0x023a1bdc | 0x00000001  
+20     0x023a1be0 | 0x00160014  = UNICODE '' 
+24     0x023a1be4 | 0x023a1a38  (Heap) ptr to UNICODE 'Basic User'
+28     0x023a1be8 | 0x013a0138  (Heap) ptr to ASCII 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...'
+2c     0x023a1bec | 0x023a1a68  (Heap) ptr to UNICODE 'Allows programs to execute as a user that does not have Administrator or Power User access rights, but can still access resouces accessible by normal users.'
+30     0x023a1bf0 | 0x00000000  
+34     0x023a1bf4 | 0x00000001  
+38     0x023a1bf8 | 0x023a18a8  
+3c     0x023a1bfc | 0x00000000  
+40     0x023a1c00 | 0x00000000  
+44     0x023a1c04 | 0x00000007  
+48     0x023a1c08 | 0x00000007  
+4c     0x023a1c0c | 0x023a18d0  (Heap) ptr to ASCII ' :H:p:'
+50     0x023a1c10 | 0x00000000  
+54     0x023a1c14 | 0x00000000  
+58     0x023a1c18 | 0x00000000  
+5c     0x023a1c1c | 0x00000000  
+60     0x023a1c20 | 0x00000000  
+64     0x023a1c24 | 0x00000000  
+68     0x023a1c28 | 0x00000000  
+6c     0x023a1c2c | 0x00000000  
+70     0x023a1c30 | 0x00000000  
+74     0x023a1c34 | 0x00000000  

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

Apparently some of the values in the object point at strings (ASCII and Unicode), another point appears to link to another object (ADVAPI32!g_CodeLevelObjTable+0x4).  This is a lot more useful than dds or dc.  But we can make it even better.  We can tell mona to automatically print out linked objects as well, up to any level deep.  Let’s repeat the mona command, this time asking for linked objects up to one level deep:

0:001> !py mona do -a 0x023a1bc0 -l 1
Hold on...
[+] No size specified, checking if address is part of known heap chunk
    Address found in chunk 0x023a1bb8, heap 0x00240000, (user)size 0x78

----------------------------------------------------
[+] Dumping object at 0x023a1bc0, 0x78 bytes
[+] Also dumping up to 1 levels deep, max size of nested objects: 0x28 bytes

[+] Preparing output file 'dumpobj.txt'
    - (Re)setting logfile c:\logs\HeapAlloc2\dumpobj.txt
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.

>> Object at 0x023a1bc0 (0x78 bytes):
Offset  Address      Contents    Info
------  -------      --------    -----
+00     0x023a1bc0 | 0x023a1d30  (Heap) ptr to ASCII '0::'
+04     0x023a1bc4 | 0x023a1818  (Heap) ptr to ASCII ':'
+08     0x023a1bc8 | 0x00000000  
+0c     0x023a1bcc | 0x023a1d3c  (Heap) ptr to 0x77e46464 : ADVAPI32!g_CodeLevelObjTable+0x4
+10     0x023a1bd0 | 0x023a1824  (Heap) ptr to ASCII ':'
+14     0x023a1bd4 | 0xbaadf00d  
+18     0x023a1bd8 | 0x00020000  = UNICODE ' ' 
+1c     0x023a1bdc | 0x00000001  
+20     0x023a1be0 | 0x00160014  = UNICODE '' 
+24     0x023a1be4 | 0x023a1a38  (Heap) ptr to UNICODE 'Basic User'
+28     0x023a1be8 | 0x013a0138  (Heap) ptr to ASCII 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...'
+2c     0x023a1bec | 0x023a1a68  (Heap) ptr to UNICODE 'Allows programs to execute as a user that does not have Administrator or Power User access rights, but can still access resouces accessible by normal users.'
+30     0x023a1bf0 | 0x00000000  
+34     0x023a1bf4 | 0x00000001  
+38     0x023a1bf8 | 0x023a18a8  (Heap) ptr to 0x00000101 : 
+3c     0x023a1bfc | 0x00000000  
+40     0x023a1c00 | 0x00000000  
+44     0x023a1c04 | 0x00000007  
+48     0x023a1c08 | 0x00000007  
+4c     0x023a1c0c | 0x023a18d0  (Heap) ptr to ASCII ' :H:p:'
+50     0x023a1c10 | 0x00000000  
+54     0x023a1c14 | 0x00000000  
+58     0x023a1c18 | 0x00000000  
+5c     0x023a1c1c | 0x00000000  
+60     0x023a1c20 | 0x00000000  
+64     0x023a1c24 | 0x00000000  
+68     0x023a1c28 | 0x00000000  
+6c     0x023a1c2c | 0x00000000  
+70     0x023a1c30 | 0x00000000  
+74     0x023a1c34 | 0x00000000  

>> Object at 0x023a1d3c (0x28 bytes):
Offset  Address      Contents    Info
------  -------      --------    -----
+00     0x023a1d3c | 0x77e46464  ADVAPI32!g_CodeLevelObjTable+0x4
+04     0x023a1d40 | 0x023a1bcc  (Heap) ptr to ASCII '<:$:'
+08     0x023a1d44 | 0xbaadf00d  
+0c     0x023a1d48 | 0x00040000  = UNICODE ' ' 
+10     0x023a1d4c | 0x00000101  
+14     0x023a1d50 | 0x001a0018  = UNICODE '' 
+18     0x023a1d54 | 0x023a1c50  (Heap) ptr to UNICODE 'Unrestricted'
+1c     0x023a1d58 | 0x0090008e  (Heap) ptr to ASCII 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...'
+20     0x023a1d5c | 0x023a1c88  (Heap) ptr to UNICODE 'Software access rights are determined by the access rights of the user.'
+24     0x023a1d60 | 0x00000000  

>> Object at 0x023a18a8 (0x28 bytes):
Offset  Address      Contents    Info
------  -------      --------    -----
+00     0x023a18a8 | 0x00000101  
+04     0x023a18ac | 0x05000000  
+08     0x023a18b0 | 0x0000000a  
+0c     0x023a18b4 | 0xabababab  
+10     0x023a18b8 | 0xabababab  
+14     0x023a18bc | 0xfeeefeee  
+18     0x023a18c0 | 0x00000000  
+1c     0x023a18c4 | 0x00000000  
+20     0x023a18c8 | 0x0005000a  = UNICODE '' 
+24     0x023a18cc | 0x051807c2  

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

As you can see in the output above, mona determined that the source object contained references to 2 linked objects, and decided to dump those linked object as well.  It’s important to know that mona won’t consider strings (ASCII or Unicode) as objects, because mona already shows the strings, even if they are referenced inside the object.   The output of the dumpobj command is written to a text file called "dumpobj.txt".

For your info, the output of !mona info -a

includes the output of mona dumpobj (without printing recursive objects).  If you want to understand what exactly a given address is, you’ll get something like this:

0:001> !py mona info -a 0x023a1bc0 
Hold on...
[+] Generating module info table, hang on...
    - Processing modules
    - Done. Let's rock 'n roll.
[+] NtGlobalFlag: 0x00000070
    0x00000040 : +hpc - Enable Heap Parameter Checking
    0x00000020 : +hfc - Enable Heap Free Checking
    0x00000010 : +htc - Enable Heap Tail Checking

[+] Information about address 0x023a1bc0
     {PAGE_READWRITE}
    Address is part of page 0x013c0000 - 0x023a2000
    This address resides in the heap


Address 0x023a1bc0 found in 
    _HEAP @ 00240000, Segment @ 013c0040
                      (         bytes        )                   (bytes)
      HEAP_ENTRY      Size  PrevSize    Unused Flags    UserPtr  UserSize Remaining - state
        023a1bb8  00000090  00000158  00000018  [07]   023a1bc0  00000078  00000010   Fill pattern,Extra present,Busy  (hex)
                  00000144  00000344  00000024                   00000120  00000016   Fill pattern,Extra present,Busy  (dec)

      Chunk header size: 0x8 (8)
      Size initial allocation request: 0x78 (120)
      Total space for data: 0x88 (136)
      Delta between initial size and total space for data: 0x10 (16)
      Data : 30 1d 3a 02 18 18 3a 02 00 00 00 00 3c 1d 3a 02 24 18 3a 02 0d f0 ad ba 00 00 02 00 01 00 00 00 ...


----------------------------------------------------
[+] Dumping object at 0x023a1bc0, 0x90 bytes

[+] Preparing output file 'dumpobj.txt'
    - (Re)setting logfile c:\logs\HeapAlloc2\dumpobj.txt

>> Object at 0x023a1bc0 (0x90 bytes):
Offset  Address      Contents    Info
------  -------      --------    -----
+00     0x023a1bc0 | 0x023a1d30  (Heap) ptr to ASCII '0::'
+04     0x023a1bc4 | 0x023a1818  (Heap) ptr to ASCII ':'
+08     0x023a1bc8 | 0x00000000  
+0c     0x023a1bcc | 0x023a1d3c  (Heap) ptr to 0x77e46464 : ADVAPI32!g_CodeLevelObjTable+0x4
+10     0x023a1bd0 | 0x023a1824  (Heap) ptr to ASCII ':'
+14     0x023a1bd4 | 0xbaadf00d  
+18     0x023a1bd8 | 0x00020000  = UNICODE ' ' 
+1c     0x023a1bdc | 0x00000001  
+20     0x023a1be0 | 0x00160014  = UNICODE '' 
+24     0x023a1be4 | 0x023a1a38  (Heap) ptr to UNICODE 'Basic User'
+28     0x023a1be8 | 0x013a0138  (Heap) ptr to ASCII 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA...'
+2c     0x023a1bec | 0x023a1a68  (Heap) ptr to UNICODE 'Allows programs to execute as a user that does not have Administrator or Power User access rights, but can still access resouces accessible by normal users.'
+30     0x023a1bf0 | 0x00000000  
+34     0x023a1bf4 | 0x00000001  
+38     0x023a1bf8 | 0x023a18a8  (Heap) ptr to 0x00000101 : 
+3c     0x023a1bfc | 0x00000000  
+40     0x023a1c00 | 0x00000000  
+44     0x023a1c04 | 0x00000007  
+48     0x023a1c08 | 0x00000007  
+4c     0x023a1c0c | 0x023a18d0  (Heap) ptr to ASCII ' :H:p:'
+50     0x023a1c10 | 0x00000000  
+54     0x023a1c14 | 0x00000000  
+58     0x023a1c18 | 0x00000000  
+5c     0x023a1c1c | 0x00000000  
+60     0x023a1c20 | 0x00000000  
+64     0x023a1c24 | 0x00000000  
+68     0x023a1c28 | 0x00000000  
+6c     0x023a1c2c | 0x00000000  
+70     0x023a1c30 | 0x00000000  
+74     0x023a1c34 | 0x00000000  
+78     0x023a1c38 | 0xabababab  
+7c     0x023a1c3c | 0xabababab  
+80     0x023a1c40 | 0x00000000  
+84     0x023a1c44 | 0x00000000  
+88     0x023a1c48 | 0x00120007  = UNICODE '' 
+8c     0x023a1c4c | 0x051e0752  

[+] Disassembly:
    Instruction at 023a1bc0 : XOR BYTE PTR

dumplog (dl)

It is clear that the dumpobj command will make it easier to visualize important information inside an object.  This is certainly helpful if you already know the starting object.  What if you have been logging all Heap allocations and free operations in the application and storing the output in a log file?  Even a few lines of javascript code can be quite noisy from a Heap perspective, making it less trivial to identify interesting objects.

To make our lives easier, I decided to implement "dumplog", which will parse a log file (based on a certain syntax) and perform a "dumpobj" on each object that has been allocated, but not freed.  In the current version, dumplog will not dump linked objects, but I plan on adding this feature soon.  (probably tomorrow)

Dumplog requires a proper setup.  We need to tell WinDBG to create a log file that follows a specific convention, and we obviously must run mona dumplog in the same debug session (to make sure the logged allocations and free operations are still relevant).

The output of "!py mona help dumplog" shows this:

Usage of command 'dl' :
------------------------
Dump all objects recorded in an alloc/free log
Note: dumplog will only dump objects that have not been freed in the same logfile.
Expected syntax for log entries:
    Alloc : 'alloc(size in hex) = address'
    Free  : 'free(address)'
Additional text after the alloc & free info is fine.
Just make sure the syntax matches exactly with the examples above.
Arguments:
    -f <path/to/logfile> : Full path to the logfile

The idea is to log all Heap allocations and free operations to a log file.  In WinDBG this can be achieved using the following steps:

Before running the process and triggering the alloc/free operations that you want to capture and analyze, tell WinDBG to write the output of the log window to a text file:

.logclose
.logopen c:\\allocs.txt

Next, set up 2 logging breakpoints:

bp !ntdll + 0002e12c ".printf \"alloc(0x%x) = 0x%p\", poi(esp+c), eax; .echo; g"
bp ntdll!RtlFreeHeap "j (poi(esp+c)!=0) '.printf \"free(0x%p)\", poi(esp+c); .echo; g'; 'g';"

(based on a recent version of kernel32.dll, Windows 7 SP1).

These 2 breakpoints will print a message to the WinDBG log window each time RtlAllocateHeap and RtlFreeHeap are called, printing out valuable information about the API call.  It’s important to stick to this format, but you are free to add more text to the end of the message string.   With these 2 breakpoints active, and WinDBG configured to start writing the output of the log window to a text file, we can run the application.

When you’re ready to do analysis, break WinDBG.  Don’t close it at this point, but close the log file using the .logclose command.

We can now use mona to parse the log file, find the objects that have been allocated and not freed, and perform a mona dumpobj on each of those objects. 

!py mona dl -f c:\allocs.txt

The output will be written to dump_alloc_free.txt

I hope you’ll enjoy these 2 new features.

Stay safe & take care

cheers

-corelanc0d3r


Update (Aug 17) – the "dumplog" feature now supports dumping linked objects (option -l).  Linked objects will have a reference to the parent object.  (This applies to dumpobj as well).


© 2014 – 2021, Peter Van Eeckhoutte (corelanc0d3r). All rights reserved.

One Response to Analyzing heap objects with mona.py

Corelan Training

We have been teaching our win32 exploit dev classes at various security cons and private companies & organizations since 2011

Check out our schedules page here and sign up for one of our classes now!

Donate

Want to support the Corelan Team community ? Click here to go to our donations page.

Want to donate BTC to Corelan Team?



Your donation will help funding server hosting.

Corelan Team Merchandise

You can support Corelan Team by donating or purchasing items from the official Corelan Team merchandising store.

Protected by Copyscape Web Plagiarism Tool

Corelan on Slack

You can chat with us and our friends on our Slack workspace:

  • Go to our facebook page
  • Browse through the posts and find the invite to Slack
  • Use the invite to access our Slack workspace
  • Categories