使用mona.py分析堆对象
引言
在准备Derbycon高级漏洞开发课程期间,我一直在研究IE中的堆分配原语。在研究过程中,快速识别可能有用的对象是一个令人沮丧(或至少会减慢研究速度)的问题。毕竟,我需要找到包含任意数据或指向任意数据指针的对象,但由于噪声干扰,这并不总是容易实现。
我决定为mona.py添加一些新功能,以便更快地找到有趣的对象。这些新功能仅在WinDBG下可用。
要获取最新版本的mona,只需运行!py mona up
。由于我还升级到了最新版本的pykd,在运行最新版本的mona之前,您可能需要更新pykd.pyd。(当您尝试使用过时版本的pykd运行mona时,应在WinDBG日志窗口中看到更新说明)
dumpobj (do)
第一个新功能是"dumpobj"。这个mona.py命令将转储对象的内容并提供(希望)有用的内容信息。该命令接受以下参数:
1
2
3
4
5
6
7
8
9
10
|
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)
|
如上面!py mona help dumpobj
的输出所示,我们需要至少提供2个参数:
-a
:起始位置(对象的地址,但您可以指定任何位置)
-s
:对象的大小。如果不指定-s参数,mona将尝试确定对象的大小。如果不可能,mona将转储对象的0x28字节。
此外,您可以告诉mona也转储链接的对象。参数-l
接受一个数字,表示递归转储的级别数。为了限制输出大小(以及性能原因),仅打印链接对象的前0x28字节(除非使用参数-m
来覆盖此行为)。
当然,在WinDBG中转储对象内容非常简单。dds
或dc
命令将打印出对象并显示其内容的一些信息。在某些情况下,dds/dc的输出不够充分,需要额外的工作来进一步分析对象以及在此对象内部链接的可选对象。
让我们看一个例子。假设我们在0x023a1bc0有一个0x78字节的对象。当然,我们可以使用本机WinDBG命令转储对象的内容:
1
2
3
4
5
|
0:001> dds 0x023a1bc0 L 0x78/4
023a1bc0 023a1d30
023a1bc4 023a1818
023a1bc8 00000000
...
|
使用mona,我们可以转储相同的对象,mona将尝试收集有关对象中每个dword的更多信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
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
>> 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
...
|
显然,对象中的一些值指向字符串(ASCII和Unicode),另一个指针似乎链接到另一个对象(ADVAPI32!g_CodeLevelObjTable+0x4)。这比dds或dc有用得多。但我们可以让它更好。我们可以告诉mona自动打印出链接的对象,最多可以深入到任何级别。让我们重复mona命令,这次要求链接对象最多深入一级:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
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
>> Object at 0x023a1bc0 (0x78 bytes):
Offset Address Contents Info
------ ------- -------- -----
+00 0x023a1bc0 | 0x023a1d30 (Heap) ptr to ASCII '0::'
...
>> Object at 0x023a1d3c (0x28 bytes):
Offset Address Contents Info
------ ------- -------- -----
+00 0x023a1d3c | 0x77e46464 ADVAPI32!g_CodeLevelObjTable+0x4
...
>> Object at 0x023a18a8 (0x28 bytes):
Offset Address Contents Info
------ ------- -------- -----
+00 0x023a18a8 | 0x00000101
...
|
如上面的输出所示,mona确定源对象包含对2个链接对象的引用,并决定也转储这些链接对象。重要的是要知道mona不会将字符串(ASCII或Unicode)视为对象,因为mona已经显示了字符串,即使它们在对象内部被引用。dumpobj命令的输出被写入名为"dumpobj.txt"的文本文件。
供您参考,!mona info -a
的输出包括mona dumpobj的输出(不打印递归对象)。如果您想了解给定地址的确切含义,您将得到类似这样的信息:
1
2
3
4
5
6
7
|
0:001> !py mona info -a 0x023a1bc0
Hold on...
[+] Information about address 0x023a1bc0
{PAGE_READWRITE}
Address is part of page 0x013c0000 - 0x023a2000
This address resides in the heap
...
|
dumplog (dl)
很明显,dumpobj命令将使可视化对象内部的重要信息变得更容易。如果您已经知道起始对象,这肯定有帮助。如果您一直在记录应用程序中的所有堆分配和释放操作并将输出存储在日志文件中,该怎么办?即使几行javascript代码从堆的角度来看也可能相当嘈杂,使得识别有趣的对象变得不那么简单。
为了使我们的生活更轻松,我决定实现"dumplog",它将解析日志文件(基于特定语法)并对每个已分配但未释放的对象执行"dumpobj"。在当前版本中,dumplog不会转储链接对象,但我计划很快添加此功能。(可能在明天)
Dumplog需要适当的设置。我们需要告诉WinDBG创建一个遵循特定约定的日志文件,并且显然必须在相同的调试会话中运行mona dumplog(以确保记录的分配和释放操作仍然相关)。
“!py mona help dumplog"的输出显示:
1
2
3
4
5
6
7
8
9
10
11
|
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
|
想法是将所有堆分配和释放操作记录到日志文件中。在WinDBG中,可以通过以下步骤实现:
在运行进程并触发要捕获和分析的分配/释放操作之前,告诉WinDBG将日志窗口的输出写入文本文件:
1
2
|
.logclose
.logopen c:\\allocs.txt
|
接下来,设置2个日志记录断点:
1
2
|
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';"
|
(基于最近版本的kernel32.dll,Windows 7 SP1)。
这两个断点将在每次调用RtlAllocateHeap和RtlFreeHeap时向WinDBG日志窗口打印消息,打印有关API调用的有价值信息。坚持这种格式很重要,但您可以在消息字符串的末尾添加更多文本。在这两个断点处于活动状态且WinDBG配置为开始将日志窗口的输出写入文本文件的情况下,我们可以运行应用程序。
当您准备进行分析时,中断WinDBG。此时不要关闭它,但使用.logclose命令关闭日志文件。
我们现在可以使用mona来解析日志文件,找到已分配但未释放的对象,并对每个对象执行mona dumpobj。
1
|
!py mona dl -f c:\allocs.txt
|
输出将写入dump_alloc_free.txt
我希望您会喜欢这两个新功能。
保持安全,保重
cheers
-corelanc0d3r
更新(8月17日)- “dumplog"功能现在支持转储链接对象(选项-l)。链接对象将具有对父对象的引用。(这也适用于dumpobj)。