freeBuf
主站

分类

云安全 AI安全 开发安全 终端安全 数据安全 Web安全 基础安全 企业安全 关基安全 移动安全 系统安全 其他安全

特色

热点 工具 漏洞 人物志 活动 安全招聘 攻防演练 政策法规

点我创作

试试在FreeBuf发布您的第一篇文章 让安全圈留下您的足迹
我知道了

官方公众号企业安全新浪微博

FreeBuf.COM网络安全行业门户,每日发布专业的安全资讯、技术剖析。

FreeBuf+小程序

FreeBuf+小程序

0day漏洞:Chromium v8引擎最新UAF代码执行漏洞分析
2022-05-31 00:01:04
所属地 海外

介绍

在Chromium v8中x64平台的指令优化中发现了UAF漏洞。成功利用此漏洞可以允许攻击者在浏览器上下文中执行任意代码。

该漏洞是由于v8在优化结束之后,指令选择阶段,选择了错误的指令,导致的内存破坏漏洞,成功利用此漏洞,可以达到代码执行的效果。

该漏洞发生在

https://chromium.googlesource.com/v8/v8/+/71a9fcc950f1b8efb27543961745ab0262cda7c4%5E

之前(含本次提交),如果想重现此漏洞,可以同步代码到此次提交之上。

注:Google已发布紧急补丁更新,请及时下载安装。

代码主要的功能及原理:

1.首先创建一个typed array数组,大小为0x10000

2.然后判断a[0]的值是否为非零,然后结果赋值给b

3.然后进行垃圾回收

4.如果b为true,打印字符串boom

运行上面的代码输出的结果:

1653925991_6294e86749d992f78bc38.png!small?1653925991325

图1:内存泄漏错误

可以看到,访问违规的地址就是a的data_ptr: 0x7f71576a0000

1653926001_6294e87121d9a2a52e239.png!small?1653926000974

图2: 访问违规地址数组a

代码首先,调用了runtime函数gc,然后[r8],0做对比:

1653926021_6294e885e6513e9668896.png!small?1653926021998

图3: 调用gc时的汇编指令

漏洞产生原因

在前两次运行的时候,v8知道对数组a的使用只是在步骤2处,后续并没有访问数组a,所以在优化编译阶段,gc把v8的数组存放的内存给回收了,数组a的内存被释放了,但是由于v8在x64平台上错误的生成了指令,导致在4处访问到了已经被回收的页面,造成了内存访问异常,也就是在0x7f6e29984162地址处,又一次的访问已经被释放的数组a的内存,从而导致crash。

优化过程:

if(b)=>if(a[0]!=0)=>Word32Equal(a[0],0) =>[cmp [r8],0 ; jnz xxxx]

因此,在执行第4步时,它正在访问已回收的同一位置,导致地址0x7f6e29984162的内存访问异常,其中再次访问已释放的数组a的内存,导致内存损坏和崩溃。

1653926048_6294e8a0e7edfb06c8662.png!small?1653926048670

图4 :漏洞出发流程图

补丁对比

修补代码删除了CanCoverForCompareZero函数,恢复了CanCover的使用CanCoverForCompareZero的作用是用来代替在函数VisitWordCompareZero中canCover。如果canCover返回了false,但是这个节点是一个比较的节点,就不需要其他的任何寄存器,可以被user节点给cover住。

一般情况下这个没有问题,但是问题恰恰出现在当访问内存的时候,此时生成的代码会直接去比较内存,而不是先获取比较结果,在去进行比较,但是当在第二次访问内存的时候,调用gc,就会造成UAF。

这时候会调用这个函数,用来访问a的数组buffer,如下图所示:

1653926095_6294e8cf48abfe3a728c1.png!small?1653926095234

在这里生成转化为具体的指令1653926107_6294e8db6b62e4acbe68b.png!small?1653926107350

因为易受攻击的代码采用CanCoverForCompareZero为true的路径,从而生成不同的指令序列(易受攻击版本VS固定版本)。

指令序列的详细分析

在分析了Turbofan的优化阶段后,我们知道这个问题发生在最后两个阶段:

在计划阶段,节点信息完全一致,但之后变得不同,我们找到了相关的指令序列。

事实上,相应的js代码是const b=a[0]!=0;

修补前:

1653926124_6294e8ec2008cdb4de719.png!small?1653926123832

修补之后:

1653926134_6294e8f66491bf3ddd549.png!small?1653926134276

乍一看,它似乎完全相同,但由于漏洞,这里生成的代码不同(请注意,114的左侧,修复前是一个点,修复后是一个圆圈和一个点,这意味着修复前的114行不是直接生成的指令,而是从33个输入中提取,以生成优化的指令序列——指令折叠。

在易受攻击代码中,当调用VisitWordCompareZero时,CanCoverForCompareZero将返回true,因为Word32Equal节点是一个比较类型位置。由于后续访问是cmp指令,v8不需要在注册表中存储比较结果,但假设后续访问(即if(b)语句)可以再次直接访问数组a的内存,并最终生成包含更少指令序列的优化代码。

这就是注册表分配之前发生的事情:

修复前:

1653926154_6294e90ad9c5087055916.png!small?1653926154767

修复前生成的相关指令:

1653926169_6294e91911165bf8f8048.png!small?1653926168709

修复后:

1653926178_6294e9224b941856f5895.png!small?1653926178021

修复后生成的相关说明:

生成25行指令序列:

1653926187_6294e92b053436a4ce52e.png!small?1653926186667

生成26行指令序列:1653926192_6294e930ee921624a1c99.png!small?1653926192578

比较修复前后的2组指令,条件指令的差异与setnzl VS setzl相反,因为修复前的代码在函数VisitWordCompareZero中执行cont->OverwriteAndNegateIfEqual(kEqual)。

1653926214_6294e94690dd5b255462b.png!small?1653926214381

以下显示了修复漏洞后的TurboFan程序集指令(注意生成的代码指令序列比修复之前更长):

1653926223_6294e94f55397365e907d.png!small?1653926223316

图5: 漏洞修补后的TurboFan程序集说明

在图5中,我们可以看到比较结果首先放在堆栈[rbp-0x28]上,然后将[rbp-0x28]与0进行比较,在这种情况下,它将不会访问gc中的内存,因此不会出现UAF问题。

修补代码对比

-// Used instead of CanCover in VisitWordCompareZero: even if CanCover(user,
-// node) returns false, if |node| is a comparison, then it does not require any
-// registers, and can thus be covered by |user|.
-bool CanCoverForCompareZero(InstructionSelector* selector, Node* user,
-                            Node* node) {
-  if (selector->CanCover(user, node)) {
-    return true;
-  }
-  // Checking if |node| is a comparison. If so, it doesn't required any
-  // registers, and, as such, it can always be covered by |user|.
-  switch (node->opcode()) {
-#define CHECK_CMP_OP(op) \
-  case IrOpcode::k##op:  \
-    return true;
-    MACHINE_COMPARE_BINOP_LIST(CHECK_CMP_OP)
-#undef CHECK_CMP_OP
-    default:
-      break;
-  }
-  return false;
-}
-
 }  // namespace
 
 // Shared routine for word comparison against zero.
@@ -2516,7 +2494,7 @@
     cont->Negate();
   }
 
-  if (CanCoverForCompareZero(this, user, value)) {
+  if (CanCover(user, value)) {
     switch (value->opcode()) {
       case IrOpcode::kWord32Equal:
         cont->OverwriteAndNegateIfEqual(kEqual);
@@ -2536,7 +2514,7 @@
       case IrOpcode::kWord64Equal: {
         cont->OverwriteAndNegateIfEqual(kEqual);
         Int64BinopMatcher m(value);
-        if (m.right().Is(0) && CanCover(user, value)) {
+        if (m.right().Is(0)) {
           // Try to combine the branch with a comparison.
           Node* const eq_user = m.node();
           Node* const eq_value = m.left().node();
@@ -2646,6 +2624,7 @@
         break;
     }
   }
+
   // Branch could not be combined with a compare, emit compare against 0.
   VisitCompareZero(this, user, value, kX64Cmp32, cont);
 }

(https://chromium.googlesource.com/v8/v8/+/71a9fcc950f1b8efb27543961745ab0262cda7c4%5E%21/#F0)

对比修补的代码,我们看到,恢复了canCover函数,删除了CanCoverForCompareZero,这样就不会将对比的节点这种情况来进去到这个switch case里,也就不会产生错误的指令生成。

利用思路

本漏洞通过堆喷的方式,覆盖a的数组内存,然后转化为类型混淆漏洞,之后再利用类型混淆,来实现读写的能力,最后达到RCE的效果,感兴趣的话可以自行尝试构造。

# 网络安全 # web安全 # 漏洞分析 # UAF漏洞分析 # Chrome 浏览器0day
免责声明
1.一般免责声明:本文所提供的技术信息仅供参考,不构成任何专业建议。读者应根据自身情况谨慎使用且应遵守《中华人民共和国网络安全法》,作者及发布平台不对因使用本文信息而导致的任何直接或间接责任或损失负责。
2. 适用性声明:文中技术内容可能不适用于所有情况或系统,在实际应用前请充分测试和评估。若因使用不当造成的任何问题,相关方不承担责任。
3. 更新声明:技术发展迅速,文章内容可能存在滞后性。读者需自行判断信息的时效性,因依据过时内容产生的后果,作者及发布平台不承担责任。
本文为 独立观点,未经授权禁止转载。
如需授权、对文章有疑问或需删除稿件,请联系 FreeBuf 客服小蜜蜂(微信:freebee1024)
被以下专辑收录,发现更多精彩内容
+ 收入我的专辑
+ 加入我的收藏
  • 0 文章数
  • 0 关注者
文章目录