从0开始学V8漏洞利用之CVE-2021-21220(八)
复现CVE-2021-21220
第六个研究的是CVE-2021-21220
,其chrome的bug编号为:1196683
可以很容易找到其相关信息:
受影响的Chrome最高版本为:89.0.4389.114
受影响的V8最高版本为:8.9.255.24
并且还附带了exp
搭建环境
一键编译相关环境:
1 | $ ./build.sh 8.9.255.24 |
漏洞分析
因为通过之前的文章,已经对模板套路很熟悉了,所以在之后的文章中,将不会过多讲诉套模板编写exp,而会让重点放在一些之前文章中没有的点上,更着重在漏洞利用技巧这块。
该漏洞的PoC如下:
1 | const _arr = new Uint32Array([2**31]); |
上述PoC来源于:https://github.com/security-dbg/CVE-2021-21220/blob/main/exploit.js
因为我认为这个PoC更利于理解该漏洞。
根据我的理解,我做了如下修改:
1 | var b = new Uint32Array([0x80000000]); |
在正常情况下,该函数的逻辑:
- b[0]为uint32类型的变量,其值为0x80000000。
- 异或了0以后,变成了int32类型,其值为-2147483648。
- 加上1以后,变成了-2147483647,赋值给了x。但是类型会被扩展成int64,因为js的变量是弱类型,如果x一开始的类型是int32,值为2147483647(0x7fffffff),那么x+1不会变成-1,而会变成。2147483648(0x80000000),因为int32被扩展成了int64。
- 然后使用math.abs函数计算绝对值,x值变为2147483647(0x7fffffff)。
- x - 0x7FFFFFFF = 0。
- 使用math.max函数计算x与0之间的最大值,为0。
- x - 1 = -1。
- 因为x=-1,所以x改为0。
- 新建了一个长度为0的数组。
- 因为长度为0,所以shitf无效,数组不变。
但是上述逻辑,经过JIT优化以后,就不一样了:
- b[0]为uint32类型的变量,其值为0x80000000。
- 将其转化成int64类型,其值为0x80000000。
- 加上1以后,变成了0x80000001。
- 然后使用math.abs函数计算绝对值,x值变为0x80000001。
- x - 0x7FFFFFFF = 2。
- 使用math.max函数计算x与0之间的最大值,为2。
- x - 1 = 1。
- 新建了一个长度为1的数组。
- shitf函数将数组的长度设置为-1,这就让我们得到了长度为-1的数组,通过该数据进行后续利用。
在JIT的优化过程中,存在两个问题:
- 将b[0]转化为int64,把符号去掉了,从Turbo流程图看,是通过
ChangeInt32ToInt64
来改变b[0]的变量类型,而在这个opcode实现的代码中:
1 | void InstructionSelector::VisitChangeInt32ToInt64(Node* node) { |
根据上面代码可以看出,如果b[0]是有符号的,那么将会使用kX64Movsxlq
指令进行转换,如果是无符号的就会使用kX64Movl
指令进行转换。
b[0]因为是一个uint32类型的变量,所以使用movl进行扩展大小,所以没有扩展其符号,导致出现了问题。
- shitf函数将数组长度设置为-1。
shift函数的正常逻辑是,判断数组的长度,如果其长度大于0,并且小于100,那么将会对长度的赋值进行优化,预测其长度,然后进行减1操作,直接写入数组的长度。
在JIT的预测当中,x的值为0,因为其预测是按照没有bug的情况进行预测的,但是实际情况x为1,这就导致实际情况的x通过了shitf的长度检查,然后却把x认为是0,从而-1,把数组的长度设置为了-1。
总结
该漏洞的成因还是挺容易理解的,这研究其原理的过程中也要学会看Turbo,后续将为专门看Turbo的opcode写一篇paper。
从0开始学V8漏洞利用之CVE-2021-21220(八)