Chakra引擎中JIT编译优化历程中的数组类型混淆误差剖析

宣布时间 2018-12-18
1、研究配景

Chakra是一个由微软为Microsoft Edge浏览器开发的JavaScript引擎。它在一个自力的CPU焦点上即时编译剧本,与浏览器并行。本文主要对Chakra引擎中JIT编译优化历程中的数组类型混淆误差举行剖析。


JavaScript引擎的性能对整个浏览器的影响至关主要, JIT编译优化是为了提高Chakra引擎性能。当在循环语句中重复执行统一段剧本代码时,若是诠释重视复执行相关的字节码,效率会很低。JIT可以将源代码直接天生气械指令,在下一次执行时直接执行机械指令。在Chakra中只有当目的函数或者循环语句被频仍挪用时才会启用JIT编译,JIT编译后天生了响应的机械指令,下一次挪用到这个语句或是函数时就会直接执行机械指令。


一旦JIT天生完成,程序就可以直接挪用JIT天生的机械指令。由于JIT是直接编译为机械指令的,以是需要预先假定操作目的的类型。若是不知足JIT的假设的话,此JIT代码就不可执行,不然就会爆发类型混淆的过失。因此JIT代码中设计了bailout功效,一旦发明不知足假设就举行bailout,bailout会放弃执行JIT代码转回使用诠释器继续执行字节码。


尊龙凯时 - 人生就是搏!


2、数组类型混淆思绪

Chakra数组可以分为三类,划分是NativeIntArray、NativeFloatArray和VarArray。NativeIntArray和NativeFloatArray数组转化成VarArray数组历程中会将数组中的原数据通过异或0xfffc000000000000转化为VarArray中的数据。也就是说VarArray会通过数组中元素的高位来判断数组中的元素是数据照旧工具。


NativeIntArray和NativeFloatArray之间混淆一样平常不可带来清静问题,可是当这二者和VarArray混淆之后就会泛起数据和工具无法区分的问题。


先看一段简朴代码。


尊龙凯时 - 人生就是搏!

这段代码在JIT优化后的体现形式是这样的。

尊龙凯时 - 人生就是搏!


若是在xxx操作历程中将NativeArray的类型改酿成了VarArray,并且JIT的优化历程并没有检测到这种转变的话,2.3023e-320就会被看成float数据存放进入VarArray的元素中,由于这个历程中数组的转变是始料未及的,以是2.3023e-320并没有通过与0xfffc000000000000异或而酿成一个可以被VarArray识别的float,以是VarArray工具在读取该元素时会将其当成一个工具来处置惩罚。


为了实现数组的类型混淆,xxx操作主流的思绪有两种,一种是通过没有检测的回调来修改数组的类型,第二种是通过合理的函数来修改数组的类型。下面通过一些实例举行简要剖析。


2.1 思绪一:通过回调修改数组类型


先来看一个简朴的例子,通过回调修改数组类型。

尊龙凯时 - 人生就是搏!


func的JIT主要片断如下:


尊龙凯时 - 人生就是搏!


凭证上述代码,可以看到call rax之后并没有验证数组a是否正当就直接举行了赋值。那么怎样改变数组a的类型呢 ?我们来看最后一次对func的挪用。


尊龙凯时 - 人生就是搏!


误差剧本将一个工具直接赋值给了参数c,并且在这个工具上挂了一个valueOf回调,c要赋值给typed数组b,而b中的元素只能是Uint32类型,以是JIT会对参数c举行一个转换(用到ToInt32),这会触发c的valueOf回调,在回调函数中通过a[0]={}给数组a赋值,这会将a由NativeFloatArray酿成VarArray,此后续代码由于没有检查a数组改变以是继续将其看成NativeFloatArray赋值造成了类型混淆。


补丁子女码如下。


尊龙凯时 - 人生就是搏!


一样平常来说,Chakra引擎在对JIT中的回调举行优化时会思量一个叫做ImplicitCallFlags的标记位,通过这个标记位,就可以检测用户函数是否可能被挪用,若是是的话就会启动bailout或举行相关检测。可是这种机制保存一些问题,好比ImplicitCallFlags标记位究竟在什么位置会被置位,它是否能�;に斜4婊氐骱奈恢� ?


一个典范的例子:CVE-2017-11802


尊龙凯时 - 人生就是搏!


这个误差较量简朴,保存于RegexHelper::StringReplace函数中,regexp的replace要领,可以界说一个回调函数,可是在着实现中并没有对回调函数举行�;�,也就是说可以直接在regexp的replace要领中修改数组类型而不被JIT检测到。


尊龙凯时 - 人生就是搏!


该误差的补丁也较量简朴,通过对两处挪用回调的位置添加ExecuteImplicitCall验证,就可以修补该误差。这个补丁同时修补了一处位于JavascriptArray::ArraySpeciesCreate中的由于建设新工具而导致的回调。


尊龙凯时 - 人生就是搏!


这种机制在实现和优化历程中有没有瑕疵呢 ?下面来看另一个例子CVE-2018-0840。


尊龙凯时 - 人生就是搏!


这是一个直接对ExecuteImplicitCall函数举行对抗的误差,其问题自己在于ExecuteImplicitCall函数的实现,其代码片断如下。


尊龙凯时 - 人生就是搏!


函数首先会执行implicitCall然后才会更新ImplicitCallFlags,纯粹从函数自己来思量似乎没什么问题,可是这内里忽略了一个可能就是回调在执行历程中若是泛起了一个异常该怎么处置惩罚,POC中的typeof实现位于JavascriptOperators::TypeofElem函数中,和误差有关的代码如下。


尊龙凯时 - 人生就是搏!


回调会通过ExecuteImplicitCall函数举行挪用,可是回调函数会触发一个异常,该异�;岜籘ypeofElem捕获,也就是说ExecuteImplicitCall函数中更新ImplicitCallFlags的操作被跳过了,由于标记位没有被更新,以是优化历程中的响应排错机制也就没有被天生,最终导致了误差的爆发。


另外一个问题是CVE-2018-8556,通过补丁信息可以知道误差保存于GlobOptBailOut.cpp的MayNeedBailOnImplicitCall函数中,从名字可以推测,这个函数主要认真判断JIT优化历程中是否对ImplicitCall天生bailout代码。


尊龙凯时 - 人生就是搏!


在该函数对工具的length属性举行获取的操作中,判断返回值的逻辑泛起了问题。


尊龙凯时 - 人生就是搏!


从逻辑上看,string和知足IsAnyArray并且不即是ObjectWithArray的工具都是可以通过验证的,也就是说typedarray也是知足条件的。


尊龙凯时 - 人生就是搏!

尊龙凯时 - 人生就是搏!


若是要给工具获取length的操作加回调或者过滤操作,工具的length属性的configurable特征必需为true,string和array的length都切合这个假定,可是typedarray却是个破例,以是可以通过给typedarray的length属性加回调的操作,去执行用户界说的代码来触发类型混淆误差。


2.2 思绪二:通过合理的函数挪用修改数组类型


接下来看第二种思绪,通过合理函数挪用来触发数组类型改变。在一些函数处置惩罚中,由于功效缘故原由会挪用ToVarArray函数对数组类型举行改变。


下面举例说明。


尊龙凯时 - 人生就是搏!


opt函数的JIT优化代码如下:


尊龙凯时 - 人生就是搏!


可以看到,在call rax之后并没有举行数组类型的检测就直接赋值了,那么这个call中究竟爆发了什么呢 ?这个call挪用了JavascriptOperators::OP_InitProto函数来初始化proto,在最后一次opt挪用时,将array看成proto给了属性链,在对属性链赋值时,若是赋值参数是一个Native数组的话会将其转换为VarArray(挪用了ToVarArray函数)。其挪用函数栈如下。


尊龙凯时 - 人生就是搏!


此时数组的类型已经爆发了改变而JIT并没有检查到这一点以是爆发了误差。


再来看一个较为重大点的例子CVE-2018-0835。


尊龙凯时 - 人生就是搏!


该误差保存于JavascriptArray::ReverseHelper函数中,函数会挪用JavascriptArray::FillFromPrototypes,该函数通过遍历prototype来填充array。


尊龙凯时 - 人生就是搏!


在程序中,函数确保prototype中的array不可是NativeArray。


尊龙凯时 - 人生就是搏!


也就是说,若是prototype是NativeArray数组则会被程序转换为VarArray,若是能够使一个数组的prototype为NativeArray,就可以通过数组的Reverse要领将其prototype的NativeArray转换为VarArray。不过这里尚有一个问题就是怎样确保prototype是NativeArray,一样平常情形下若是一个数组被看成prototype,则它会被转化为VarArray。


在JavascriptArray::EntrySort中保存如下代码。


尊龙凯时 - 人生就是搏!


若是arr是一个NativeArray,它首先会酿成一个VarArray执行sort回调,再变回NativeArray,若是能够在回调中将这个arr赋给prototype,之后它的类型又会变回来,这样就可以获得一个类型混淆误差。


2.3 思绪三:MissingItem


CVE-2018-0953同样也是通过函数挪用修改数组类型,这个误差特殊之处在于引出了另一个关注点,即数组的MissingItem。MissingItem是一个数值,在64位程序上即是0x8000000280000002。Chakra引擎在数组建设的时间会使用这个值对数组元素举行初始化,体现数组中该元素还未举行赋值,另外数组还会保存一个标记位(NoMissingValues)来标记此数组是否有未被赋值的元素。


先看看下面这段代码。


尊龙凯时 - 人生就是搏!


当执行数组的赋值操作,挪用了NativeArray的SetItem函数,SetItem函数实现如下。


尊龙凯时 - 人生就是搏!


当给NativeArray赋值时,若是这个值即是MissingItem,可以将NativeArray转化为VarArray。优化逻辑假设对数组举行赋值是一个很清静的操作,只要传入参数不是一个工具那么就不会改变数组类型,可是并没有思量到若是赋值的值即是MissingItem的话会引起数组类型的转变,正是这种疏忽导致了误差的爆发。


这个误差自己很是好明确,可是MissingItem自己又引出了一连串的问题。该误差的补丁程序修补了通过OP_SetElementI来挪用SetItem的情形,可是这样修补远远不敷,由于对该函数挪用的位置着实很是多,于是找误差的思绪酿成了寻找为NativeArray赋值的种种路径的问题。


CVE-2018-0953的误差发明者lokihardt在补丁修补后又提出两种思绪来绕过补丁,第一个是通过arraypush来挪用SetItem。


触发误差代码如下:


尊龙凯时 - 人生就是搏!


由于通过push对数组举行插入的操作会挪用SetItem,以是数组改变的情形仍然会保存。


尊龙凯时 - 人生就是搏!


第二个思绪是先直接修改数组的元素,再通过cancat来修改数组类型。误差触发代码如下:


尊龙凯时 - 人生就是搏!


POC首先通过set修改了数组中元素的值。


尊龙凯时 - 人生就是搏!


对应的JIT代码是这样的。


尊龙凯时 - 人生就是搏!


在修改了数组元素后,创造了一个有MissingItem可是HasNoMissingValues的array。


接着剧本挪用了trigger函数,由于数组的HasNoMissingValues标记位为真,下图代码中的条件是知足的。


尊龙凯时 - 人生就是搏!


由于数组有了MissingItem,以是可以举行到如下分支。


尊龙凯时 - 人生就是搏!


InternalFillFromPrototype函数会对buggy数组prototype链上所有工具挪用EnsureNonNativeArray,也就是说会对arr挪用EnsureNonNativeArray,这样就可以修改其数组类型,可是JIT引擎并不知道arr类型已经改变,以是会导致类型混淆。

针对此问题,Chakra的事情职员最先大规模的检查NativeArray的input,在LowerStElemC、


GenerateProfiledNewScObjArrayFastPath、GenerateHelperToArrayPopFastPath等诸多函数上添加了MissItem的检测(由于修补函数较多,这里就纷歧一枚举了,详情请参考地址https://github.com/Microsoft/ChakraCore/commit/91bb6d68bfe0455cde08aaa5fbc3f2e4f6cc9d04)。


可是,通过如下代码挪用的OP_Memset函数并没有对value举行检查,仍然可以用来结构拥有MissingItem可是HasNoMissingValues的array,并通过concat来获得一个类型混淆误差。


尊龙凯时 - 人生就是搏!


值得一提的是,在11月的补丁中Chakra直接对concat要领做了严酷的处置惩罚,从情形上推测应该是找到了新的要领来将MissingItem写入array,但由于网上没找到响应的信息,再加上补丁并没有对将值写入array的代码举行修补,反而限制了concat,以是也无法判断详细情形。


尊龙凯时 - 人生就是搏!


2.4 思绪四:将数组伪装成工具


最后一种思绪,通过疑惑Chakra引擎,使其在天生JIT代码历程中过失的将NativeArray看成其他工具,以至于没有在适当的位置添加检查代码。


果真的例子是CVE-2018-8466。


尊龙凯时 - 人生就是搏!


Chakra使用JavascriptArray::GetArrayForArrayOrObjectWithArray来判断工具是否是array,其逻辑如下所示。


尊龙凯时 - 人生就是搏!


通过CrossSite class来wrap一个工具的时间会替换该工具的虚表,以是被wrapping的数组将不会被识别为数组,这将导致无法在准确的地方天生对数组类型的检查并爆发类型混淆误差。


补丁除了验证虚表是否是array工具之外,还检查了工具是否是被CrossSite wrap的数组。


尊龙凯时 - 人生就是搏!


另一个例子是CVE-2018-8542,其补丁在ValueType::MergeWithObject中。


尊龙凯时 - 人生就是搏!


该函数主要用于合并两个工具,可以看到补丁添加了验证,用于确定两个工具中是否有数组,再视察一下没打过补丁的问题代码,若是两个工具都不是UninitializedObject,则合并为Object工具,大致可以获知误差爆发的缘故原由,在执行到这句的时间若是两个工具中有一个是数组,在合并时数组会被看成工具来处置惩罚,优化历程中引擎把合并的数组看成了工具,那么对数组类型是否改变的检测虽然就不被需要,于是最终导致了类型混淆。


尊龙凯时 - 人生就是搏!

3、总 结

在已往一年左右,JIT编译优化历程中的类型混淆是Chakra误差挖掘方面的一个主要关注点。从早期的使用未被�;さ幕匦髡:葱薷氖槔嘈�,再到寻找验证历程中的逻辑问题,使用数组的MissingItem特征,将数组伪装成其他类型工具思绪,我们可以看到随着研究者对Chakra引擎的深入研究,误差爆发的位置已经从简朴的工具要领逐步向JIT优化代码天生历程中爆发的种种逻辑和判断问题靠拢,误差挖掘的门槛也有了显著的提升。

尊龙凯官网入口起劲防御实验室(ADLab)

ADLab建设于1999年,是中国清静行业最早建设的攻防手艺研究实验室之一,微软MAPP妄想焦点成员,“黑雀攻击”看法首推者。阻止现在,ADLab已通过CVE累计宣布清静误差近1000个,通过 CNVD/CNNVD累计宣布清静误差近500个,一连坚持国际网络清静领域一流水准。实验室研究偏向涵盖操作系统与应用系统清静研究、移动智能终端清静研究、物联网智能装备清静研究、Web清静研究、工控系统清静研究、云清静研究。研究效果应用于产品焦点手艺研究、国家重点科技项目攻关、专业清静服务等。