首页
论坛
课程
招聘
[原创]2021年4月15日Chrome v8 issue 1195777(CVE-2021-21224)分析(史上最详细的该漏洞分析!!!!!!)
2021-8-4 19:02 7544

[原创]2021年4月15日Chrome v8 issue 1195777(CVE-2021-21224)分析(史上最详细的该漏洞分析!!!!!!)

2021-8-4 19:02
7544

第一部分:写在前面:

 标题要吓人,才有人来!

 紧跟着前面:

 https://bbs.pediy.com/thread-267529.htm

 环境:

 Ubuntu 18.04

 turbofan 图表都是在 release的v8下产生的

 命令./d8 poc.js  --allow-natives-syntax --trace-turbo 


1.1:在v8执行时,在poc.js后面加上--trace-turbo执行参数,会产生turbofan(优化过程各个阶段的处理逻辑)图表。

1.2:分析turbofan的图表时,最重要的是找到第一个出现错误的地方。v8 优化的漏洞有一点不同于一般的溢出类或UAF类漏洞的地方是,漏洞的直接原因通常很难在调试器中直接体现出来。在传统的溢出和UAF中,我们经常可以制造一个崩溃,来体现漏洞发生的直接原因,而调试v8优化类漏洞POC的时候,经常给人一种,怎么地,就溢出了的感觉。究其原因,个人感觉比较合理的解释是其漏洞出现的根源往往是在v8工程师自己设计的逻辑层面。

1.2:0xFFFFFFFF是个什么?或者说v8会把他当作什么?

首先我们可以简单排除v8把他当成int64或unsigned int64的可能性。(这样会造成资源上的浪费),可能的解释为一个int32数字或一个unsigned int 32数字。如果解释为int32类型,那么我们打印就会输出-1,如果解释为unsigned int 类型,那么我们打印就会为4294967295。那到底v8会将其解释为什么类型呢,我们可以写个简单的代码验证一下:

var x=0xFFFFFFFF

console.log(x);

       

                                                                      图1.1.1

显然v8把0xFFFFFFFF当成了unsigned int32 的类型。换一句话说v8把0xFFFFFFFF当成unsigned int32才是其规定的合法操作,如果当成了int32类型,则会出现前后不一致的情况,导致结果出现错误。

这里写的有点啰嗦,主要是这点对理解这个漏洞原因还挺重要。

1.3:v8优化的过程中往往会会对计算的结果进行预估,进而对结果形成一个范围,而预估的范围本身,会影响到后面优化的过程,我们分析优化的漏洞通常就是分析其预估的范围是否有误,以及对后面优化过程的影响。

这点有点像那种综艺节目,第一个人看到一个东西,然后口述给第二个人听,然后第二个人口述给第三个人听,以此信息传递下去,中间有一个人理解错误的话,就会导致后面全部人理解错误。

第二部分:poc的turbofan过程分析。

2.1:POC的简单研究

原始的poc1.js

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

分别输出的是:

 

                                                             图2.1.1

这里可以看到经过优化后,参数x=0xFFFFFFFF的情况下,-1<Math.max(0,x,-1)的结果为false。和优化之前的结果不一样!

这里把poc1.js调整下,以下将其称为poc2.js:

   (function(){
      Function foo(b){
       let x=-1;
       if(b)x=0Xffffffff;
       return Math.max(0,x,-1)
   }
   console.log(foo(true));
   %PrepareFunctionForOptimization(foo);
   console.log(foo(false));
   %PrepareFunctionForOptimization(foo);
   console.log(foo(true));
  })

                                                                图 2.1.2

从图2.1.2可以看到,v8对Math.max(0,x,-1)优化前后计算的都是一样,那为什么前面poc1.js经v8优化后的-1<Math.max(0,x,-1)会返回false呢?

我们可以推测经过v8的优化,在poc1.js在运算完Math.max(0,x,-1)之后,对其结果0xFFFFFFFF(4294967295)的解释出现了问题,原本应该解释为unsigned int32 类型的,被错误的解释为int32类型。0xFFFFFFFF这个数如果解释为int32的话,结果就会为-1,这样的话-1<-1,最终结果自然就会变为false。

2.2:turbofan图表分析

关于v8 turbofan图表分析这一块本人也是新手,看别人写的都是直接看几个重要的阶段,本人不是太懂,就用笨一点的方法,把每个图相关过程都看一下

第一个看到有对结果进行范围判断的是:

2.2.1:V8.TFTypedLowering 57

                                                                          

                                                                                            图 2.2.1

图2.2.1可以看出

a):这一阶段的优化初始化过程为节点13节点19合并然后通过20节点Phi[kRepTagged]运算后,结果为Range(-1, 4294967295)

b): 这一阶段的优化是将poc.js中的Math.max(0,x,-1)拆分为两个NumberMax,将初始化数值分别与节点31的常数0和节点13的常数-1运算。

      这里并未看出存在什么问题。

2.2.2:V8.TFLoopPeeling57

                                                                                                 图 2.2.2

2.2.3:V8.TFLoadElimination 57

                                                                                                  图 2.2.3

2.2.4:V8.TFEscapeAnalysis 57

                                                                                          图 2.2.4

     以上几个优化过程大同小异,基本和第一个图表没什么区别。

2.2.5:V8.TFSimplifiedLowering 77

                                                                                                          图2.2.5

如图2.2.5:

a):这个一阶段,初始化将74节点75节点经过20节点Phi[kRepFloat64]运算后得到预估范围为Range(-1,4294967295),再通过节点65 的 ChangeFloat64ToInt64运算。

b):图2.2.5 中所示在这一阶段将poc1.js中的Math.max(0,x,-1)拆分为两个Int64LessThan,分别与66节点Int64常数0和70节点Int64常数-1运算。这里得到的范围就应该是Range(0,4294967295)

流程到这里,是没有什么问题的,这里的Int64LessThan其实只是把32位数放入64位寄存器计算。

但是紧接进行68节点的:Truncation64Int32运算,这个节点问题就非常大了,他是把前面计算出来的结果进行int32转化,按照这个逻辑进行推断的话,结果就会从Range(0,4294967295)变成Range(0,-1),对原本正确的计算结果进行错误的类型解释。

由此,我们可以推测,这个Truncation64Int32节点的生成是这里计算错误根本原因。如果说在这里还不能清晰的说明对返回结果的影响的话,那么翻到后面的V8.TFLateOptimization 190阶段就很明显了。

2.2.6 V8.TFLateOptimization 190

  

                                                                                         图 2.2.6

   

a)  如图2.2.6所示:在这个阶段经过初始化节点20 Phi[kRepFloat64]的运算,结果为Range(-1,4294967295)

b)  在这个阶段Math.max(0,x,-1)变成分别和节点66 Int64常数0和节点70 Int64常数-1进行Phli[kRepWord64]运算,我们可以推断出其结果为(0,4292967295)

再经过节点68 的 TruncateInt64ToInt32就会转化为int类型,结果就为(0,-1),最后和节点67 int32常数-1进行节点40 的 Int32LessThan运算(这边用Int32LessThan可以进一步证明v8已经把上面结果解释为Int32),得到的结果就为(true,false),然后做一些常规合法性校验,紧接着就是返回。

 这也就是为什么poc1.js在优化后当参数x=0xFFFFFFFF的情况下,结果会得到false。

第三部分:exp核心分析 

    function foo(a){
          let x=-1;
          if(a) x=0xFFFFFFFF;
          var arr = new Array(Math.sign(0-Max.max(0,x,-1)));
          arr.shift();
          let local_arr=Array(2);
          local_arr[0] = 5.1;
          let buff = new LeakArrayBuffer(0x1000)
          arr[0]=0x1122;
         return [arr, local_arr, buff];
     }

a)根据前面的分析,v8在优化过程中Math.max(0,x,-1) 产生了错误,使得结果为(0,-1)。在Math.sign(0-Math.max(0,x,-1))运算后就会变成(0,1),产生了一个意外的1,使得优化后,如果参数x=0xFFFFFFFF的话,arr的结果就会为new Array(1),产生了一个有效数组。

b)然而在v8的预估判断中,Math.max(0,x,-1)结果为(0,0),arr长度始终为0,不会是其他的值,判定为无效数组,所以arr.shift()这代码的优化结果就直接在长度的那里减1,变成0xFFFFFFFF(在内存中存储的为-1*2为0xFFFFFFFE),优化后会直接在代表数组长度的内存位置中填入0xFFFFFFFE,没有别的操作。但是因为此时由于上述原因,arr为一个有效数组,结果就产生了一个长度为0xFFFFFFFF的超长数组。(这里的利用手法和issue 1196683的利用手法一样)

c) 紧接着申请的浮点数组local_arr和一个0x1000的Buffer,然后用前面的超长数组越界改写浮点数组的长度,用于对对象的地址进行泄露。这里的原exp是通过修改DataView的相关指针来实现对任意地址的读写,这些都是v8漏洞利用的常规套路了,这里不做细究了。不过要注意这里用于写shellcode的Buffer的长度是0x1000,shellcode长度要是超过这个值要重新修改DataView的相关指针,不过在情况实际中应用中貌似也不太可能发生......




参考:https://github.com/r4j0x00/exploits/blob/master/chrome-0day/exploit.js


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


          https://chromium-review.googlesource.com/c/v8/v8/+/2820971


          https://www.anquanke.com/post/id/229482





[注意] 欢迎加入看雪团队!base上海,招聘安全工程师、逆向工程师多个坑位等你投递!

最后于 2021-8-5 10:31 被苏啊树编辑 ,原因:
收藏
点赞1
打赏
分享
最新回复 (2)
雪    币: 2106
活跃值: 活跃值 (1208)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
caolinkai 活跃值 2021-8-6 14:56
2
0
收藏
雪    币: 2742
活跃值: 活跃值 (1189)
能力值: ( LV7,RANK:110 )
在线值:
发帖
回帖
粉丝
苏啊树 活跃值 2 2021-8-6 18:14
3
0
caolinkai 收藏
突破0回复
游客
登录 | 注册 方可回帖
返回