CVE-2021-21224 - Chrome v8 issue 1195777分析

  • A+
所属分类:安全文章

更多全球网络安全资讯尽在邑安全

第一部分:写在前面:

 环境:

 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.3:0xFFFFFFFF是个什么?或者说v8会把他当作什么?

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

var x=0xFFFFFFFFconsole.log(x);

CVE-2021-21224 - Chrome v8 issue 1195777分析图1.1.1

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

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

1.4: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));  })


分别输出的是:

CVE-2021-21224 - Chrome v8 issue 1195777分析图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));  })

CVE-2021-21224 - Chrome v8 issue 1195777分析


图 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


CVE-2021-21224 - Chrome v8 issue 1195777分析

图 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

CVE-2021-21224 - Chrome v8 issue 1195777分析

图 2.2.2

2.2.3:V8.TFLoadElimination 57

CVE-2021-21224 - Chrome v8 issue 1195777分析

图 2.2.3

2.2.4:V8.TFEscapeAnalysis 57

CVE-2021-21224 - Chrome v8 issue 1195777分析

图 2.2.4

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

2.2.5:V8.TFSimplifiedLowering 77

CVE-2021-21224 - Chrome v8 issue 1195777分析

图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

 

CVE-2021-21224 - Chrome v8 issue 1195777分析

图 2.2.6

   

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

b)  在这个阶段Math.max(0,x,-1)变成分别和节点66Int64常数0,节点70Int64常数-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) 紧接着申请一个浮点数组和一个0x1000的Buffer,然后用前面的超长数组越界改写浮点数组的长度,用于对对象的地址进行泄露。这里的原exp是通过修改DataView的相关指针来实现对任意地址的读写,这些都是v8漏洞利用的常规套路了,这里不做细究了。不过要注意这里用于写shellcode的Buffer是0x1000,shellcode长度要是超过这个值要重新修改DataView的相关指针,不过在情况实际中应用中貌似也不太可能发生......

原文来自: bbs.pediy.com

原文链接: https://bbs.pediy.com/thread-268752.htm

欢迎收藏并分享朋友圈,让五邑人网络更安全

CVE-2021-21224 - Chrome v8 issue 1195777分析

欢迎扫描关注我们,及时了解最新安全动态、学习最潮流的安全姿势!


推荐文章

1

新永恒之蓝?微软SMBv3高危漏洞(CVE-2020-0796)分析复现

2

重大漏洞预警:ubuntu最新版本存在本地提权漏洞(已有EXP) 



本文始发于微信公众号(邑安全):CVE-2021-21224 - Chrome v8 issue 1195777分析

发表评论

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen: