CVE-2019-5782 v8数组越界 漏洞复现
本篇博文对CVE-2019-5782漏洞进行了简单的POC分析及EXP编写。这是2018年天府杯,360的招啓汛大神用到的一个V8引擎数组越界漏洞。这个漏洞比较强大,利用上比较容易,EXP的编写和其他之前做过的v8数组越界的利用基本上没什么变化。
bug详情:
Issue 906043: Security: Tianfu CUP RCE
https://chromium.googlesource.com/v8/v8.git/+/deee0a87c0567f9e9bf18e1c8e2417c2f09d9b04
调试准备
切换到漏洞版本:
1 | git reset --hard b474b3102bd4a95eafcdb68e0e44656046132bc9 |
POC
1 | // Copyright 2018 the V8 project authors. All rights reserved. |
实际调试中,我发现有一点点不必要的操作可省去。。方便抓住关键
1 | function fun(arg) { |
带上--allow-natives-syntax
运行POC。可以看到,a2数组的长度,从一开始定义的16变成了42,而且从a2[16]开始的值,都不是undefined,因此,很显然出现了数组越界的情况。
在debug版中%DebugPrint(a2)
会出错崩溃,因此在release版本中进行调试:
1 | pwndbg> telescope 0x086345e056d0 |
可以看到,Array中存储的length-42和elements数组中存储的length-65535,正好和POC中fun函数中的最后两次赋值对应上了。
在%OptimizeFunctionOnNextCall(fun)
前后输出a2.length就会发现,在%OptimizeFunctionOnNextCall(fun)
之后,对fun函数的调用后,导致了这一结果。%OptimizeFunctionOnNextCall()
是v8的内部指令,它告诉v8当下次调用这个函数的时候,需要调用Turbofan对指定函数进行优化。显然,漏洞出现在函数优化的过程中。
进一步观察POC和现象,发现,导致a2长度异常的两次赋值,都是通过a1进行的赋值,而且赋值索引显然是越界了。release版中查看一下a1和a2数组:
如上图可以看到,a2的length和a2.elements中的length,根据偏移计算,正好是a1[21]和a1[41],而我也尝试将fun函数中对a1越界赋值代码中的x>>16
删去,则对a1的越界赋值不会成功。
因此,可以对以上结果作总结:在对fun函数的优化过程中存在漏洞,导致了可以对a1数组进行越界写,从而可以将内存中紧跟在a1.elements后的a2数组做任意的覆写(POC中对length进行了修改)。
漏洞利用
由于可以对a2数组的length做任意值的写,可以数组越界很大的范围。因此这个漏洞利用起来不难。
我这里的利用思路是:
1 | 数组越界 -> 类型混淆 -> 泄漏对象、任意地址读写 -> 利用WASM执行shellcode |
定义类型转换工具函数
在V8中,number有两种形式,一种是float,一种是smi(small int),(还有比较新的bigint任意精度整数类型)。为方便使用,需要写一些方法用于转换数据类型:
1 | /*---------------------------datatype convert-------------------------*/ |
获得越界数组
直接仿照POC中的做法,将a2数组的length置为0xffff即可:
1 | /*---------------------------get oob array-------------------------*/ |
泄漏对象地址
我们可以定义如下一个对象,对象有两个属性,一个leak
和一个tag
属性。,其中,tag
属性是我定义来确定偏移用的标志,只需要给leak
属性赋值为目标对象,即可通过a2[offset]
将该对象的地址作为float泄漏出来。
代码实现如下,我这里通过遍历a2数组,来寻找objLeak.leak
的位置
1 | /*---------------------------leak object-------------------------*/ |
运行代码,结果如下,可见,成功实现了泄漏对象地址,当然了泄漏出来的值-1才是真正的地址。
任意地址读写
我们可以在oobArray(a2)后面申请一个ArrayBuffer对象和一个普通的object对象objLeak。然后通过对a2的数组越界,将AarrayBuffer的backing store指针覆写,即可实现任意地址读写:
代码实现如下,我这里通过以0xbeef为大小申请ArrayBuffer,然后循环遍历a2数组寻找0xbeef这个值,从而确定ArrayBuffer.backing_store的位置。
1 | /*---------------------------arbitrary read and write-------------------------*/ |
运行代码,结果如下,可见,成功将0xdeadbeef写入了objTest+0x18的位置,并且成功将它读了出来
利用WASM执行shellcode
可以通过WASM,能得到一块RWX的内存,里面放着WASM的二进制代码,将shellcode写入到这块内存,再调用WASM接口时,就会执行Shellcode了。
首先要找到这块RWX内存,相关的数据结构根据版本不同可能不太一样。通过调试,我通过这个嵌套查找可以找到这块地址:
1 | wasmInstance.exports.main -> shared_info -> data -> instance+0xe8 |
debug版运行以下测试代码,找一下这块RWX地址
1 | var wasmCode = new Uint8Array([0,97,115,109,1,0,0,0,1,133,128,128,128,0,1,96,0,1, |
在f+0x18
找到f.shared_info
在f.shared_info+0x8
处找到f.shared_info.data
在f.shared_info.data+0x10
处找到f.shared_info.data.instance
最后,在f.shared_info.data.instance+0xe8
处找到这块存放二进制代码的RWX地址:
因此,我们只需要通过刚才实现的泄漏对象地址、任意地址读写,将shellcode写入这块RWX地址,再调用这个WASM函数时,就可以执行shellcode了。
完整EXP
1 | //CVE-2019-5782 |
弹了个计算器
当然了也可以换个shellcode本地getshell:
漏洞成因分析
拿到官方的patch,
1 | diff --git a/src/compiler/type-cache.h b/src/compiler/type-cache.h |
1 | diff --git a/src/compiler/verifier.cc b/src/compiler/verifier.cc |
感谢阅读 ♪(^∇^*)
- 本文链接:http://www.sunxiaokong.xyz/2020-02-25/lzx-cve-2019-5782/
- 版权声明:本站文章除特别声明外,均为本站原创或翻译,采用 知识共享署名 CC BY 4.0 国际协议进行许可,转载前请务必署名及注明出处。