CVE-2021-21220 Chrome v8远程代码执行漏洞复现与分析
tips:
标记为橙色的为不严谨,有待研究
V8 漏洞利用之环境搭建
一、编译环境搭建
以下都出自这篇文章:https://zhuanlan.zhihu.com/p/493674086
更新软件列表、更新软件、安装依赖
1 |
|
安装depot_tools
1 | mkdir /root/tools && cd /root/tools |
安装ninja:
1 | git clone https://github.com/ninja-build/ninja.git |
下载v8
1 | mkdir /root/v8 && cd /root/v8 |
二、找漏洞版本commit
编译的话,需要找到漏洞版本的github的commit
受影响的Chrome最高版本为:89.0.4389.114
受影响的V8最高版本为:8.9.255.24
方法一
https://omahaproxy.appspot.com
通过这个网站可以找漏洞版本的commit
方法二
从漏洞的issue链接https://bugs.chromium.org/p/chromium/issues/detail?id=821137
找到修复的commit链接https://chromium.googlesource.com/v8/v8.git/+/b5da57a06de8791693c248b7aafc734861a3785d ,可以看到漏洞信息、存在漏洞的上一个版本(parent)、diff修复信息和漏洞poc
方法三
直接从github找commit
https://github.com/v8/v8/tags?after=8.9.255
三、编译
分了两个版本,一个是release,一个是debug
1 | 进入到v8目录,选择好要编译的commit(不然默认编译最新的) |
这里有个坑,就是,debug版本会有很多调试信息,release没有,并且,release不能使用v8的gdb脚本(如job命令),如果想要release能使用gdb脚本的话,需要执行完tools/dev/v8gen.py x64.release
后在生成的 out.gn/x64.release/args.gn
中追加
1 | v8_enable_backtrace = true |
参考:https://www.cjovi.icu/CVE/1586.html
四、配置v8自带的gdb脚本,方便调试
v8自带了gdb调试脚本
1.把v8/tools/gdbinit内容加到~/.gdbint里面
2.将v8/tools/gdb-v8-support.py放到一个目录(当前也行)
在~/.gdbint开头加入 source /自定义目录/gdb-v8-support.py
参考:https://paper.seebug.org/1821/
RCE的完整步骤
incorrect numeric (理解漏洞本身)
POC
1 | const _arr = new Uint32Array([2**31]); |
https://paper.seebug.org/1850/
https://paper.seebug.org/1556/
那么 这个长度-1的数组有什么用呢??????,见下面 Array.shift
OOB (out-of-bounds memory access) 越界访问
abusing array bounds check elimination.
有历史沿革,之前是bounds-check elimination的问题,后来去掉了,但又有新的利用方式
利用Array.shift实现oob
https://bugs.chromium.org/p/chromium/issues/detail?id=1198696
而负长度被视为一个正的大长度,因此该数组允许访问任意 OOB 数据。
1 | function foo(a) { |
通过上述漏洞,我们实现了一个长度为-1的数组arr(corrput_arr),-1扩展为无符号,就是0xffffffff,是一个很大的正数,从而可以实现越界读写,在此基础之上,进行后面的利用
1.获得了一个0xfffffff(-1长度)数组 arr(corrput_arr)
2.声明一个local_arr(rwarr),长度为2, 接着利用arr的oob,溢出修改它的长度为0x22444,也就是corrput_arr[12] = 0x22444; (或者说 arr[12] = 0x22444;) 这一位对应的是数组的长度
3.声明长度为0x1000的ArrayBuffer(corrupt_buff)
在2、3两步,我们能够得到一个数组和一个ArrayBuffer,但是我们还不能任意读写这个ArrayBuffer,一种实现方法是,通过corrput_arr的溢出,将rwarr的长度变长,覆盖到ArrayBuffer,于是我们就能够对他进行任意读写,从而实现对内存任意地址读写(其实是受限的,rwx)
为什么要强调ArrayBuffer呢?且看下面
疑问:从而实现对rwarr(local_arr)的跨界访问,为啥要这样呢?? 为啥不直接用arr
越界访问rwarr数组(实现可控的JSArrayBuffer)
这张图比较形象,我们现在可以越界访问的是corrupt_arr,然后新建了一个rwarr数组,那么可以越界访问,把rwarr的长度修改的大一点,对应代码 corrput_arr[12] = 0x22444;
那么为什么数组的第13位是代表着它的长度呢?这个具体原理方法在参考博客里,和它的数据结构有关.
越界访问corrupt_buff(实现任意地址读写)
http://www.hackdig.com/03/hack-70813.htm
背景知识:什么是backing_store? 对漏洞利用有什么用?
backing_store指向初始化JSArrayBuffer时用户申请大小的堆,如果我们控制了一个JSArrayBuffer相当于一个指针和指针的内容可以同时改写。这样我们改写backing_store读取控制的JSArrayBuffer的内容就是任意地址读;我们改写backing_store修改控制的JSArrayBuffer的内容就是任意地址写。
如果我们将这个backing_store指针修改为我们想要写入的内存地址,那么我们再调用view.setUint32(0, poc, true) 类似指令时,实际上就是向指定内存地址处写入了poc,从而达到任意地址写。
任意地址写(通过伪造backing_store)
1 | function setbackingStore(hi, low) { |
从corrupt_buff中声明一个Dataview,而backing_store记录的就是实际DataView的内存地址。如果我们将这个backing_store指针修改为我们想要写入的内存地址,那么我们再调用view.setUint32(0, poc, true) 类似指令时,实际上就是向指定内存地址处写入了poc,从而达到任意地址写。
那么我们已经可以利用rwarr实现对corrupt_buff的任意读写,即可以任意修改backing_store.
任意地址读(类型混淆)
1 | function leakObjLow(o) { |
leakObjLow函数使用corrupt_buff的slot属性,修改该属性为某一对象o,那么o的地址就会被写入到corrupt_buff所在的内存区间中,然后利用rwarr的溢出访问该值,实现泄露。
这里是不是用了类型混淆??
利用oob造成类型混淆,那怎么利用呢?
那出现类型混淆怎么利用呢?举个例子,如果我们定义一个FloatArray浮点数数组A,然后定义一个对象数组B。正常情况下,访问A[0]返回的是一个浮点数,访问B[0]返回的是一个对象元素。如果将B的类型修改为A的类型,那么再次访问B[0]时,返回的就不是对象元素B[0],而是B[0]对象元素转换为浮点数即B[0]对象的内存地址了;如果将A的类型修改为B的类型,那么再次访问A[0]时,返回的就不是浮点数A[0],而是以A[0]为内存地址的一个JavaScript对象了。
https://www.freebuf.com/vuls/203721.html
addressOf 泄露某个对象的内存地址
1 | // 泄露某个object的地址 |
fakeObject 将指定内存强制转换为一个js对象(有什么用呢?)
1 | // 将某个addr强制转换为object对象 |
如何实现任意地址读写:构造AAR/AAW原语
https://paper.seebug.org/1821/#wasm
fakeObject强制将一块内存伪造成一个数组对象??? 它的elements 指针是可控的,而这个指针指向了存储数组元素内容的内存地址。如果我们将这个指针修改为我们想要访问的内存地址,那后续我们访问这个数组对象的内容,实际上访问的就是我们修改后的内存地址指向的内容,这样也就实现了对任意指定地址的内存访问读写效果了。
哦哦哦因为可以任意访问,把这个当成一个数组对象了,那么对这个数组,我们是可以任意读取和修改的????
wasm(webassembly) 实现执行shellcode
https://paper.seebug.org/1821/#wasm
https://www.freebuf.com/vuls/203721.html
简单来说,wasm就是可以让JavaScript直接执行高级语言生成的机器码的一种技术。
https://sensepost.com/blog/2018/introduction-to-webassembly/
利用思路
首先加载一段wasm代码到内存中
然后通过addresssOf原语找到存放wasm的内存地址
接着通过任意地址写原语用shellcode替换原本wasm的代码内容
最后调用wasm的函数接口即可触发调用shellcode
参考资料
漏洞复现参考
https://blog.csdn.net/m0_56642842/article/details/118358830 这个就是教你怎么复现,不涉及原理
https://www.cnblogs.com/7omss/p/15661338.html + 1
exploit:https://share.weiyun.com/EXlNm02A
浏览器:https://share.weiyun.com/fZLcxFe9
漏洞分析、调试及RCE步骤参考
https://zhuanlan.zhihu.com/p/365297858
https://blog.csdn.net/smellycat000/article/details/116078164
https://doar-e.github.io/blog/2019/01/28/introduction-to-turbofan/
https://github.com/security-dbg/CVE-2021-21220/blob/main/exploit.js
https://buaq.net/go-97833.html
https://ruan777.github.io/2022/01/18/chrome-cve-2021-21220分析/
https://github.com/singularseclab/Slides/blob/main/2021/chrome_exploitation-zer0con2021.pdf
https://www.sohu.com/a/383228797_354899
https://bounty-team.github.io/vulnerability analysis/2021/04/16/CVE-2021-21220/
https://www.freebuf.com/vuls/269629.html
https://www.cjovi.icu/CVE/1586.html
https://xz.aliyun.com/t/5190。v8 exploit入门[PlaidCTF roll a d8]
https://gtoad.github.io/2019/07/25/V8-Debug/ V8引擎漏洞分析环境与调试方法基础
https://paper.seebug.org/1850/ 从 0 开始学 V8 漏洞利用之 CVE-2021-21220(八)
https://www.freebuf.com/vuls/230182.html。v8利用入门:从越界访问到RCE
https://www.freebuf.com/vuls/203721.html
https://www.cjovi.icu/CVE/1586.html