详细分析谷歌紧急修复的 Chrome 0day(CVE-2021-21224)

 聚焦源代码安全,网罗国内外最新资讯!

编译:奇安信代码卫士

本文作者 iamelli0t 发布文章,详细分析了谷歌于今天修复的两个近期备受关注的两个漏洞Chrome CVE-2021-21220 和 CVE-2021-21224。如下节选编译了针对 CVE-2021-21224 漏洞的分析。

4月12日,Chromium 中的一个代码 commit 引发关注,它是针对 Chromium Javascript 引擎 v8 中某个漏洞的 bugfix。同时,该 bugfix 的回归测试用例 regress-1196683.js 也被提交。基于该回归测试用例,某研究员发布了exploit 样本。而由于 Chrome 发布管道的原因,直到4月13日该漏洞才被修复。

巧合的是,4月15日,v8 中某个 bugfix 的另外一个代码 commit 也包含在一个回归测试用例 regress-1195777.js 中。基于该测试用例,exploit 样本再次被暴露。由于最新的 Chrome 稳定版并未拉取该 bugfix commit,因此该样本仍然可在最新的 Chrome 渲染进程中遭利用。当易受攻击的 Chromium 浏览器在未启用沙箱 (-no-sandbox) 的情况下访问恶意链接时,该漏洞将被触发并造成远程代码执行。

 漏洞分析

该漏洞的 bugfix 如下:

该 commit 修复了一个整数会话节点生成错误,该节点用于在 SimplifiedLowering 阶段将64位整数转换为32位整数(截断)。提交前,如果当前节点的输出类型是 Signed32 或 Unsigned32,则生成TruncateInt64ToInt32 节点。提交后,如果当前节点的输出类型是 Unsigned32,则接下来需检查 use_info 的类型。只有当 use_info.type_check() == TypeCheckKind::kNone 时,才会生成 TruncateInt64ToInt32。

首先,通过 regress-1195777.js 分析该漏洞的根因:

(function() {
  function foo(b) {
    let x = -1;
    if (b) x = 0xFFFFFFFF;
    return -1 < Math.max(0, x, -1);
  }
  assertTrue(foo(true));
  %PrepareFunctionForOptimization(foo);
  assertTrue(foo(false));
  %OptimizeFunctionOnNextCall(foo);
  assertTrue(foo(true));
})();

触发 JIT 的函数 foo 中的关键代码是 return -1 < Math.max(0, x, -1) 。我们重点关注 TurboFan 关键阶段中的 Math.max(0, x, -1) 优化进程:

(1)TyperPhase

Math.max(0, x, -1) 对应于节点56 和节点58。节点58的输出是节点41

的输入:SpeculativeNumberLessThan (<)。

(2)TypedLoweringPhase

Math.max(0, x, -1) 中的两个常数参数 0,-1(节点54 和节点55)被常数节点32 和节点14 替换。

(3) SimplifiedLoweringPhase

原始的 NumberMax 节点56和节点58被 Int64LessThan + Select 节点替换。原始的节点41:SpeculativeNumberLessThan 被替换为 Int32LessThan。当处理 SpeculativeNumberLessThan 的输入节点时,由于输入节点 (Select) 的输出类型是 Unsigned32,则该漏洞被触发,且节点76: TruncateInt64ToInt32 的生成不正确。

Math.max(0, x, -1) 结果被截断为 Signed32。因此,当 Math.max(0, x, -1) 中的 x 是 Unsigned32 时,它会被 TruncateInt64ToInt32 截断为 Signed32。

最后,利用该漏洞,JIT 中异常值为1的变量x可通过如下代码获得(期望的值应该是0):

function foo(flag){
  let x = -1;
  if (flag){ 
    x = 0xFFFFFFFF;
  }
  x = Math.sign(0 - Math.max(0, x, -1));
  return x;
}

利用分析

从根因分析来看,当 TurboFan 执行整数数据类型转换(扩展、阶段)时,都会触发CVE-2021-21220 和 CVE-2021-21224。利用这两个漏洞,可以获取 JIT 中异常值为1的变量 x。

根据遭在野利用的样本,利用步骤如下:

(1) 通过出错值为1的变量 x,创建一个数组,长度为1。

(2) 通过Array.prototype.shift() 获取长度为 0xFFFFFFFF 的界外数组。

关键代码如下:

var arr = new Array(x);  // wrong: x = 1
arr.shift();      // oob
var cor = [1.8010758439469018e-226, 4.6672617056762661e-62, 1.1945305861211498e+103];
return [arr, cor];

变量 arr = new Array(x) 的 JIT 代码为:

Rdi 是 arr 的长度,其值为1。它通过指针压缩向左移动一位 (rdi+rdi),并存储在JSArray.length 属性 (+0xC) 中。

Arr.shift() 的 JIT 代码为:

Arr.shift() 后,arr 的长度直接由常数 0xFFFFFFFE 分配,优化过程如下:

(1) TyperPhase

 数组长度分配操作主要由节点152和节点153组成。节点152计算 Array.length-1。节点153将结算结果保存在 Array.length (+0xC)中。

(2) LoadEliminationPhase

由于通过 Ignition 获取的x的值为0,因此常数折叠 (0-1=-1) 获得常数 0xFFFFFFFF。左移一位后,它是 0xFFFFFFFE,并存储在 Array.length (+0xC) 中。这样,获得长度为 0xFFFFFFFF 的界外数组。

获得界外数组后,接下来就是常见步骤了:

(3) 凭借该界外数组实现 addrof/fakeobj

(4) 构造一个虚假 JSArray,利用 addrof/fakeobj实现任意内存读取/写入原语

Exploit 样本中 arr 和 cor 的内存布局如下:

(a)   利用该漏洞获得长度为 0xFFFFFFFFF的arr(红框)

(b)   利用界外 arr 和 cor 实现 addrof/fakeobj(绿框)

(c)   利用界外 arr 修改 cor 的长度(黄框)

(d)   利用界外 cor,泄露映射和 cor 的属性(蓝框),伪造一个 JSArray并利用该虚假 JSArray 实现任意内存读/写原语。

(5) 借助 WebAssembly 执行 shellcode

最后,借助 WebAssembly 创建属性为 RWX 的内存页面。该 shellcode 被复制到内存页并在最后执行。

利用截图如下:

推荐阅读

又一枚 Chrome 0day现身

详细分析 Chrome V8 JIT 漏洞 CVE-2021-21220

原文链接

https://iamelli0t.github.io/2021/04/20/Chromium-Issue-1196683-1195777.html

题图:Pixabay License

转载请注明“转自奇安信代码卫士 https://codesafe.qianxin.com”。

奇安信代码卫士 (codesafe)

国内首个专注于软件开发安全的

产品线。

    觉得不错,就点个 “在看” 或 "赞” 吧~

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值