19,650 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
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.
Pingback: Analyzing heap objects with mona.py | d@n3n | ...