Chrome v8漏洞 CVE-2021-30632浅析
Chrome v8漏洞 CVE-2021-30632浅析
coolboyme 看雪学苑 2024-03-22 18:02
V8 是 Google开源的高性能 JavaScript 引擎,用于 Google Chrome 等浏览器。2021 年,V8 引擎曝出漏洞 CVE-2021-30632。漏洞产生的原因是turbofan引擎优化bug导致类型混淆,可以实现远程代码执行。
这个漏洞已经有文章分析的非常详尽了(见:
https://securitylab.github.com/research/in_the_wild_chrome_cve_2021_30632/
),但阅读过程中难免还有些疑问,综合网上查询到的各种资料,弄懂以后,豁然开朗;才疏学浅,也有许多不准确和遗漏的地方。高山流水觅知音,希望可以跟同行学习交流。
编译
调试
JS对象在v8中的内部表示
v8中的JS对象总体分为两类:properties(也叫 named properties);elements (也叫indexed properties)。表示如下:
hiddenClass也称为map,它决定了JS对象是properties或者elements,见下图:
写段测试代码来实际看看:
用d8执行上述文件得到结果:
由上图可以看见,{a: “foo”, b: “bar”} 通过map (hiddenClass)被标记为JS_OBJECT_TYPE,有两个属性a和b,值分别为”foo”和”bar”。
由上图可以看见,[“foo”, “bar”]通过map被标记为JS_ARRAY_TYPE,是一个长度为2的固定数组,元素依次为”foo”和”bar”。
map的迁移
当JS对象增加或者减少属性的时候,v8的map将创建新的map,新旧map之间产生迁移关系。上述代码以此产生map0-map3 共计4个map。map0迁移至map1,map1迁移至map2,依次类推。
最终的map3不再迁移到其他map,它有一个属性取值为stable,map0-map2为not stable。
迁移也可以分叉,如下图,共产生map0到map5共计6个map。其中map3,map5为stable,其他为not stable。
具有相同”形状”的js对象,它们共享一个map。对象a,b,c的map为同一个,均为stable。
示例:
结果见下图:
Ignition, 是v8的解释器, 而 TurboFan 是v8最新的优化编译器。它们的关系如下图:
JS源码经过 Parser(词法分析、语法分析) ,送入Ignition,生成字节码执行;字节码的优点是编译快,缺点是执行慢,适用于执行次数少的代码。
执行多次的字节码被标记为热点代码,将触发优化,此时由TurboFan将字节码优化编译;过往执行中记录的数据将参与优化过程,并预测将来的的执行也将是这样。这样做得优点是,执行快,编译会消耗大量的时间,适用于循环等多次执行的函数。在执行时,跟预测不符,将触发解优化(deoptimize)。
举例说明:
执行上述js,将看到下面输出:
在代码的第8行某一次循环之后,被认定为热点函数,于是store函数被优化。这之后的store(y)都将不再执行igition产生的bytecode,而是执行TurboFan优化编译之后的代码。
在第11行store(z),由于z和y的值不同,store(z)将无法继续使用参照store(y)优化之后的代码给x赋值1,于是触发了解优化,退化为bytecode执行,给x赋值2。
我们查看漏洞
补丁(https://source.chromium.org/chromium/_/chromium/v8/v8.git/+/6391d7a58d0c58cd5d096d22453b954b3ecc6fec)
,补丁发生在JSNativeContextSpecialization::ReduceGlobalAccess函数,这个函数的作用是TurboFan对全局变量存取的优化。
补丁的807行有一个条件判断是否为PropertyCellType::kConstantType。先来了解下kConstantType。
PropertyCellType跟踪了对象的JS类型,比如JSObject, int, array, String等等,区别于Map描述了对象的”形状”。
我们看一下给全局变量赋值(AccessMode::kStore)优化相关的代码,保留关键部分:
第3行,表示当前代码块是对全局变量赋值的分支。第4行,判断全局变量的PropertyCellType。第6行,当类型为PropertyCellType::kConstantType。第7行,表示当前的优化依赖于属性的类型,如果属性类型发生了变化,那么将解优化。第10行,表示如果属性的Map是stable,那么stable属性发生变化之后,那么将解优化。第13行,表示store操作Map必须和前面保持一致,调用store时Map变化了,那么将解优化。
第7,10,13 行枚举出了三种解优化的情况:
◆全局变量属性的类型发生变化。
◆全局变量Map由stable变为not stable。
◆传入store参数的Map和前面不一致。
举例说明这三种情况:
说完全局变量的store,再来说说load。关键代码如下:
第4行,表示对全局变量的load处理。第6行,表示全局变量为kConstantType类型。第8行,表示当全局变量的Map为stable时,不能修改它的Map,否则解优化。
总结load 全局变量,当优化时刻MapA为Stable,后面修改MapA为MapB。
也举例说明下:
值得一提的是,如果插入第10行代码,那么在优化时,x为MapA not stable,第20行改变X的MapA为MapB,将不满足解优化条件:“优化时刻MapA为Stable,后面修改MapA为MapB”。
综上:全局变量store和load解优化条件:
store:
◆全局变量属性的类型发生变化。
◆全局变量Map由stable变为not stable。
◆传入store参数的Map和前面不一致。
load:
◆优化时刻MapA为Stable,后面修改MapA为MapB。
乍一看,上述解优化逻辑没有问题,然而百密一疏。竟然可以通过既有规则,构造出类型混淆!且看示例代码:
基于上面的分析,我们打印load,store的汇编验证一下:
回顾补丁:
加上补丁以后,有两处变化:1.全局变量必须是stable才会进行优化。2.store优化后,如果map变为not stable,将解优化。
这个变化将导致 store的优化失败,因为store优化时x为 not stable,不满足条件1,由此漏洞修复。
类型混淆
通过上述代码实现x的类型混淆,实际为 int arr[30]; 在oobWrite和oobRead中被优化为double arr[30],不再赘述分析。
通过调用oobWrite, oobRead可以实现越界读写。
堆风水
连续申明的js对象,在v8分配内存时,也具有连续性。
申明的arr, b, writeArr三个对象,它们的内存也是连续的。对arr进行越界读写,会访问到b和writeArr对象的数据结构。
获取对象地址
((double*)arr)[20] 对应b[0] 里面存放的值。下面的代码可以获取对象的地址。
任意地址读
((double*)arr)[24] 对应writeArr 对象的数组指针,修改数组指针,就可以实现任意地址读。
任意地址写
下面代码可以实现任意地址写:
完整d8 POC
./out/x64.release/d8 –allow-natives-syntax this.js 将会得到/bin/sh
参考
漏洞分析:
Chrome in-the-wild bug analysis: CVE-2021-30632(https://securitylab.github.com/research/in_the_wild_chrome_cve_2021_30632/)
属性访问:
https://v8.dev/blog/fast-properties
v8文档:
https://v8.dev/(https://v8.dev/)
看雪ID:coolboyme
https://bbs.kanxue.com/user-home-492418.htm
*本文为看雪论坛优秀文章,由 coolboyme 原创,转载请注明来自看雪社区
#往期推荐
2、Glibc-2.35下对tls_dtor_list的利用详解
3、对旅行APP的检测以及参数计算分析【Simplesign篇】
球分享
球点赞
球在看
点击阅读原文查看更多