介绍
大家好,
在为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
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
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 ........
|
很好。我们可以看到各种各样的东西——看起来像指针的值、空值和其他一些"垃圾"。如果不单独查看每个值,很难分辨出它是什么。
使用mona,我们可以转储相同的对象,mona将尝试收集有关对象中每个dword的更多信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
|
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 '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
+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
|
显然,对象中的一些值指向字符串(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
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
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 | 0x051e0752
[+] This mona.py action took 0:00:00.640000
|
正如你在上面的输出中看到的,mona确定源对象包含对2个链接对象的引用,并决定也转储这些链接对象。重要的是要知道mona不会将字符串(ASCII或Unicode)视为对象,因为mona已经显示了字符串,即使它们是在对象内部引用的。dumpobj命令的输出被写入一个名为"dumpobj.txt"的文本文件。
供你参考,!mona info -a的输出包括mona dumpobj的输出(不打印递归对象)。如果你想了解给定地址的确切含义,你会得到类似这样的信息:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
|
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)
很明显,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。
我希望你会喜欢这两个新功能。
保持安全 & 保重
干杯
-corelanc0d3r
更新(8月17日)- “dumplog"功能现在支持转储链接对象(选项-l)。链接对象将具有对父对象的引用。(这也适用于dumpobj)。