v8 指针压缩
v8 指针压缩
前言
Pointer compression是v8 8.0中为提高64位机器内存利用率而引入的机制。
对于v8中64位的对象指针,它们的高32位基本是不变的,花费4字节来储存它们会浪费内存空间;所以指针压缩将64位对象指针变为32位,也就是64位指针中的低32位,将64位指针的高32位保存在r13中;访问对象时,只需要将对象指针与根寄存器的基址相加即可得到完整地址。
v8关于指针压缩的实现
首先我们能想到的实现方式是从0地址开始分配4G内存,确保v8对象分配在这4G内存范围内;v8显然没有这样做,在chrome渲染进程中可能会有多个v8实例,这个方案会导致所有v8实例都来竞争这4G内存,而我们稍微开两个chrome内存就吃满了。
“详情请参考:https://v8.dev/blog/pointer-compression”
指针压缩在v8漏洞利用中的影响
首先我们很难泄漏v8堆内存空间的高32位(r13寄存器),也就意味着我们用伪造JSArray并控制elements指针以获取任意r/w原语的时候只能在4G的堆内进行。
想要任意r/w原语我们可以在v8堆上分配ArrayBuffer并覆盖它的backing_store到任意64位内存地址,然后用TypedArray或DataView对象拿到整个64位地址空间内的任意r/w原语。
原因是backing_store使用PartitionAlloc分配,所有PartitionAlloc分配的内存都位于v8堆之外的单独内存区域中,所以没有指针压缩。
还有BigUint64Array对象,利用它也可以实现v8堆内、堆外任意r/w;观察一下BigUint64Array对象的内存布局:
1 | DebugPrint: 0x192a080c5ed1: [JSTypedArray] |
可以看到external_pointer跟base_pointer都是没有进行指针压缩的64位指针,并且data_ptr是它俩相加,所以我们可以通过覆盖external_pointer和base_pointer实现64位内存空间任意r/w;还有一点要注意的是初始时external_pointer指针刚好是r13寄存器的高32位,我们可以通过泄露external_pointer来获得v8堆的基址(虽然好像也没有什么用)。
在类型混淆漏洞的利用中,当一个数组从smi/object转为double的时候,占用空间会翻倍,反之会减半。
CVE-2020-6418
这个洞也是2020-SCTF浏览器题EasyMojo的v8部分。
poc
1 |
|
jit没有考虑到a有side effect会变为double类型数组,仍按smi处理。
利用思路
考虑到指针压缩,我们基本利用思路就有了:
- 利用类型混淆把double数组变为object数组;
- 越界读写修改布置在后面的double数组的length字段;
- 有了任意长度越界的double数组,再找到布置在后面的BigUint64Array,通过越界写覆盖BigUint64Array的base_pointer和external_poiner字段来实现任意r/w原语;
- 利用任意r/w原语构造addrOf原语和fakeObj原语;
- 向wasm的rwx内存写入shellcode;



