查看原文
其他

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

iamelli0t 代码卫士 2022-05-23

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

编译:奇安信代码卫士



本文作者 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位整数(截断)。提交前,如果当前节点的输出类型是 Signed32Unsigned32,则生成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 = 1arr.shift(); // oobvar 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)   利用该漏洞获得长度为 0xFFFFFFFFFarr(红框)

(b)   利用界外 arrcor 实现 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)

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

产品线。

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


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存