-
-
[原创]CVE-2017-8548分析
-
发表于: 2018-1-24 15:33 7425
-
标签(空格分隔): Chakra
这是Microsoft Edge的javascript解析引擎Chakra的一个漏洞。这篇分析也是我这系列分析中的一部分,这一系列的漏洞虽然成因各有不同,但是我个人觉得在挖掘问题的思路上是一脉相承的。
这个漏洞与之前的几个相比比较特殊的是涉及到了代码的JIT,也就是发生了优化导致的问题。
此外这也是一个微软修了两次才修好的漏洞,韩国神童lokihardt第一次在16年12月经project zero上报了这个漏洞,编号为CVE-2017-0071。之后微软进行了错误的修补lokihardt在17年6月份再次上报了这个漏洞编号为CVE-2017-8548。
lokihardt提供的POC如下
执行POC后crash情况如下
初步观察POC可以得知,func()被反复执行导致JIT,之后对JIT函数调用了用户定义的callback函数。
首先简化POC进行调试,简化后POC如下:
crash发生在如下函数中,0x1234不满足TaggedInt和NoTaggedIntCheck因此会被当成对象指针进行RecyclableObject::FromVar(var)
也就是说这里错误的把0x1234当作指针进行操作,在Chakra中发生这种情况一般都是类型混淆导致的。
首先通过调试获取POC中func函数JIT之后的代码片断如下
在1处r15的值指向一个JavascriptNativeFloatArray对象。
JIT代码中首先会验证r15指向的对象是否是一个合法的JavascriptNativeFloatArray对象,方法就是通过比较虚表地址。之后取出Array中的Segment,验证Segment的size域来查看Segment能否容纳元素,之后直接写入新的元素0x1234。
在执行向Uint32Array写入时,因为poc中传递给func的参数是func1
,因此它不属于TaggedInt
因而会跳转到0x00001E5B544058A调用JavascriptMath::ToInt32试图做一个转化。
在这个函数过程中,用户定义的func1函数就会被执行,具体情况见下面。
在下面的调用栈中,000001fdd74905a7是JIT代码,JIT代码中经过上面说过的判断调用了JavascriptMath::ToInt32
,而最后流程会执行到ExecuteImplicitCall
去调用用户callback函数。
我们在之前的漏洞分析中说过对JavascriptNativeFloatArray数组添加一个非float值(比如对象)会导致数组转化为JavascriptArray,比如poc中的用户callback:
但是之前没有详细叙述过这个过程,这里描述一下数组类型转变的过程。
首先我们对JavascriptNativeFloatArray的赋值操作会调用到JavascriptNativeFloatArray::SetItem
注意这个函数首先调用TrySetNativeFloatArrayItem
。然后根据返回结果的Type_Id不同调用不同的赋值函数。
当返回的TypeId是NativeFloatArray时,调用JavascriptNativeFloatArray::SetItem
当返回的TypeId是JavascriptArray时,调用JavascriptArray::DirectSetItemAt<Var>
而负责判断值类型的函数JavascriptNativeFloatArray::TrySetNativeFloatArrayItem
,当发现要设置的值不属于NativeFloatArray应该储存的范畴时,会进行转化,如下所示
之后的操作包括转化Array中已有的元素、更改虚表、设置新的Type Object等。
这些操作具体是在JavascriptNativeFloatArray::ToVarArray
的子函数JavascriptNativeFloatArray::ConvertToVarArray
中实现的。
<br>
在进行转化前,Array内存如下
可以看到0x000001CA96B00660为NativeFloatArray虚表,0x00001ca96ad9140是指向Type对象的指针,0x00001ca96b006a0为segement指针,segment的size和length皆为1。
在进行转化之后内存的内容变化如下:
可以看到vtable、TypeId、element[0]都发生了改变,其中vtable是从JavascriptNativeFloatArray::vtable变成了JavascriptArray::vtable。对象的类型也从JavascriptNativeFloatArray变成JavascriptArray。
原来的Type对象如下所示,0x1f是原来的id值,对应的宏定义是TypeIds_NativeFloatArray
新的Type对象如下所示,0x1f是新的id值,对应的宏定义是TypeIds_Array
我们回过头再来看一下JIT代码,其实可以分为三段。
第一段,对应于func中的1
代码首先验证Array的类型是否为JavascriptNativeArray,之后验证segement是否可以容纳元素,之后就是进行赋值
此时内存数据如下:
第二段,对应于func中的2
代码验证了值的类型,并调用JavascriptMath::ToInt32
,注意正是这里调用了用户callback
此时内存数据如下:
可以观察到此时vtable、Type Object指针已经改变,说明此对象已经成为JavascriptArray。
注意0x00000205F82806B8
处的值已经由0x000000000001234
变成0x0000205f82f53e0
。
而0x0000205f82f53e0
其实就是callback中赋予Array的对象,证明如下:
第三段,对应于func中的3
此时执行第三次赋值操作,如果你仔细观察与第一段的区别可能就会意识到这个漏洞成因了。此时,在进行赋值操作前并没有对目标Array的类型和segment的属性做任何的验证,而是选择了直接赋值。这就执行导致了把0x1234这样一个对于JavascriptArray来说是完全非法的值(JavascriptArray中应储存TaggedInt或NoTaggedInt),从而造成了一个类型混淆。
总结一下,JIT代码生成的func函数时仅在第一次赋值前对目标Array进行了类型验证,这就导致了一旦在中途Array类型发生了改变,在第二赋值时就会发生类型混淆。
而在没有JIT的情况下,每次执行函数都由bytecode进行解释执行,调用相关的SetItem函数因而不会发生类型混淆。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课