<dd id="sga4y"><optgroup id="sga4y"></optgroup></dd>
<nav id="sga4y"><code id="sga4y"></code></nav>
  • <optgroup id="sga4y"></optgroup>

    原文地址:http://drops.wooyun.org/tips/11565

    Author:[email protected]

    0x00 前言


    本文源自一次與TK閑聊,期間得知成功繞過CFG的經過與細節(參考:[利用Chakra JIT繞過DEP和CFG])。隨即出于對技術的興趣,也抽出一些時間看了相關的東西,結果發現了另一處繞過CFG的位置。所以這篇文章中提到的思路與技術歸根結底是來自TK提示的,在此特別感謝。

    關于CFG的分析文章已經有很多了,想要了解的話可以參考我之前在HitCon 2015上的演講(spartan 0day & exploit)。要說明的是,本文的內容即為我演講中馬賽克的部分,至此通過一次內存寫實現edge的任意代碼執行方法就全部公開了。

    0x01 Chakra調用函數的邏輯


    chakra引擎在函數調用時,會根據所調用函數狀態的不同進行不同的處理。比如第一次調用的函數、多次調用的函數、DOM接口函數及經過jit編譯后的函數。不同的函數類型會有不同的處理流程,而這些不同的處理都會通過Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >函數調用Js::JavascriptFunction::CallFunction<1>函數來實現。

    1.函數的首次調用與多次調用

    當調用如下腳本時,Js::JavascriptFunction::CallFunction<1>函數會被Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >函數調用。

    #!js
    function test(){}
    
    test();
    

    如果函數是第一次被調用,則執行流程如下。

    #!bash
    chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >
        |-chakra!Js::JavascriptFunction::CallFunction<1>
            |-chakra!Js::JavascriptFunction::DeferredParsingThunk
                |-chakra!Js::JavascriptFunction::DeferredParse
                |-chakra!NativeCodeGenerator::CheckCodeGenThunk
                    |-chakra!Js::InterpreterStackFrame::DelayDynamicInterpreterThunk
                        |-jmp_code
                            |-chakra!Js::InterpreterStackFrame::InterpreterThunk
    

    如果再次調用這個函數的話,調用流程如下。

    #!bash
    chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >
        |-chakra!Js::JavascriptFunction::CallFunction<1>
            |-chakra!NativeCodeGenerator::CheckCodeGenThunk
                |-chakra!Js::InterpreterStackFrame::DelayDynamicInterpreterThunk
                    |-jmp_code
                        |-chakra!Js::InterpreterStackFrame::InterpreterThunk
    

    兩次的調用流程大致是相同的,其中主要不同是因為,函數在第一次調用時候需要通過DeferredParsingThunk函數對其進行解析。其實函數只有在第一次調用時才進行進一步的初始化解析操作,這樣設計主要是為了效率。而后續調用再直接解釋執行。

    分析發現,Js::JavascriptFunction::CallFunction<1>函數調用的子函數是通過Js::ScriptFunction對象中的數據獲得的。后續調用的函數Js::JavascriptFunction::DeferredParsingThunkNativeCodeGenerator::CheckCodeGenThunk都存在于Js::ScriptFunction對象中。兩次調用中Js::ScriptFunction對象的變化。

    第一次調用時的Js::ScriptFunction對象。

    #!bash
    0:010> u poi(06eaf050 )
    chakra!Js::ScriptFunction::`vftable':
    
    0:010> dd 06eaf050 
    06eaf050  5f695580 06eaf080 00000000 00000000
    
    0:010> dd poi(06eaf050+4) 
    06eaf080  00000012 00000000 06e26c00 06e1fea0
    06eaf090  5f8db3f0 00000000 5fb0b454 00000101
    
    0:010> u poi(poi(06eaf050+4)+0x10)
    chakra!Js::JavascriptFunction::DeferredParsingThunk:
    

    第二次調用時的Js::ScriptFunction對象。

    #!bash
    0:010> u poi(06eaf050 )
    chakra!Js::ScriptFunction::`vftable':
    
    0:010> dd 06eaf050 
    06eaf050  5f695580 1ce1a0c0 00000000 00000000
    
    0:010> dd poi(06eaf050+4)
    1ce1a0c0  00000012 00000000 06e26c00 06e1fea0
    1ce1a0d0  5f8db9e0 00000000 5fb0b454 00000101
    
    0:010> u poi(poi(06eaf050+4)+0x10)
    chakra!NativeCodeGenerator::CheckCodeGenThunk:
    

    所以函數在第一次調用與后續調用的不同,是通過修改Js::ScriptFunction對象中的函數指針來實現的。

    2.函數的jit

    接下來我們看一下函數的jit。測試腳本代碼如下,多次調用test1函數觸發其jit。

    #!js
    function test1(num)
    {
        return num + 1 + 2 + 3;
    }
    
    //觸發jit
    
    test1(1);
    

    經過jit的Js::ScriptFunction對象。

    #!bash
    //新的調試,對象內存地址會不同
    
    0:010> u poi(07103050 )
    chakra!Js::ScriptFunction::`vftable':
    
    0:010> dd 07103050 
    07103050  5f695580 1d7280c0 00000000 00000000
    
    0:010> dd poi(07103050+4)
    1d7280c0  00000012 00000000 07076c00 071080a0
    1d7280d0  0a510600 00000000 5fb0b454 00000101
    
    0:010> u poi(poi(07103050+4)+0x10)              //jit code
    0a510600 55              push    ebp
    0a510601 8bec            mov     ebp,esp
    0a510603 81fc5cc9d005    cmp     esp,5D0C95Ch
    0a510609 7f21            jg      0a51062c
    0a51060b 6a00            push    0
    0a51060d 6a00            push    0
    0a51060f 68d0121b04      push    41B12D0h
    0a510614 685c090000      push    95Ch
    0a510619 e802955b55      call    chakra!ThreadContext::ProbeCurrentStack2 (5fac9b20)
    0a51061e 0f1f4000        nop     dword ptr [eax]
    0a510622 0f1f4000        nop     dword ptr [eax]
    0a510626 0f1f4000        nop     dword ptr [eax]
    0a51062a 6690            xchg    ax,ax
    0a51062c 6a00            push    0
    0a51062e 8d6424ec        lea     esp,[esp-14h]
    0a510632 56              push    esi
    0a510633 53              push    ebx
    0a510634 b8488e0607      mov     eax,7068E48h
    0a510639 8038ff          cmp     byte ptr [eax],0FFh
    0a51063c 7402            je      0a510640
    0a51063e fe00            inc     byte ptr [eax]
    0a510640 8b450c          mov     eax,dword ptr [ebp+0Ch]
    0a510643 25ffffff08      and     eax,8FFFFFFh
    0a510648 0fbaf01b        btr     eax,1Bh
    0a51064c 83d802          sbb     eax,2
    0a51064f 7c2f            jl      0a510680
    0a510651 8b5d14          mov     ebx,dword ptr [ebp+14h] //ebx = num
    0a510654 8bc3            mov     eax,ebx        //eax = num (num << 1 & 1)
    0a510656 d1f8            sar     eax,1          //eax = num >> 1
    0a510658 732f            jae     0a510689
    0a51065a 8bf0            mov     esi,eax
    0a51065c 8bc6            mov     eax,esi
    0a51065e 40              inc     eax            //num + 1
    0a51065f 7040            jo      0a5106a1
    0a510661 8bc8            mov     ecx,eax
    0a510663 83c102          add     ecx,2          //num + 2
    0a510666 7045            jo      0a5106ad
    0a510668 8bc1            mov     eax,ecx
    0a51066a 83c003          add     eax,3          //num + 3
    0a51066d 704a            jo      0a5106b9
    0a51066f 8bc8            mov     ecx,eax
    0a510671 d1e1            shl     ecx,1          //ecx = num << 1
    0a510673 7050            jo      0a5106c5
    0a510675 41              inc     ecx            //ecx = num += 1
    0a510676 8bd9            mov     ebx,ecx
    0a510678 8bc3            mov     eax,ebx
    0a51067a 5b              pop     ebx
    0a51067b 5e              pop     esi
    0a51067c 8be5            mov     esp,ebp
    0a51067e 5d              pop     ebp
    0a51067f c3              ret
    

    Js::ScriptFunction對象中原本指向NativeCodeGenerator::CheckCodeGenThunk函數的指針,在jit之后變為指向jit code的指針。實現了直接調用函數jit code。

    這里簡單說明一下,在調用函數傳遞參數時,是先將參數左移一位,然后將最低位置1之后的值進行傳遞的(parameter = (num << 1) & 1)。所以其在獲取參數之后的第一件事是將其右移1位,獲取參數原始的值。至于為什么要這樣,我想應該是因為腳本引擎垃圾回收機制導致的,引擎通過最低位來區分對象與數據。

    #!bash
    chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >
        |-chakra!Js::JavascriptFunction::CallFunction<1>
            |-jit code
    

    調用jit函數時的調用棧如上所示,這就是chakra引擎調用jit函數的方法。

    3.DOM接口函數

    最后為了完整性,還有一類函數需要簡單介紹一下,就是DOM接口函數,由其它引擎如渲染引擎提供的函數(理論上可以為任何其它引擎)。

    #!js
    document.createElement("button");
    

    執行上面腳本則會通過下面的函數調用流程,最后調用到提供接口函數的引擎中。

    #!bash
    chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >
        |-chakra!Js::JavascriptFunction::CallFunction<1>
            |-chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk //調用dom接口函數
                |-dom_interface_function    //EDGEHTML!CFastDOM::CDocument::Trampoline_createElement
    

    當調用dom接口函數時,Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >函數及后續的處理流程中所使用的Function對象與前面不同,使用的是Js::JavascriptExternalFunction對象。然后與前面的函數調用類似,也是通過解析對象內的函數指針,并對其進行調用,最終進入到想要調用的DOM接口函數中。

    #!bash
    0:010> u poi(06f2cea0)
    chakra!Js::JavascriptExternalFunction::`vftable':
    
    0:010> dd 06f2cea0 
    06f2cea0  5f696c4c 06e6f7a0 00000000 00000000
    
    0:010> dd poi(06f2cea0+4)
    06e6f7a0  00000012 00000000 06e76c00 06f040a0
    06e6f7b0  5f8c6130 00000000 5fb0b454 00000101
    
    0:010> u poi(poi(06f2cea0+4)+0x10)
    chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk:
    

    這就是chakra引擎對不同類型函數的不同調用的方式。

    0x02 漏洞與利用


    經過前面對chakra引擎各種調用函數方法的介紹,我們再來看一下本文的重點繞過cfg的漏洞。前面提到的在第一次調用腳本創建的函數時與后續調用此函數會有不同的流程。這里我們再看一下此處的邏輯,調用棧如下。

    #!bash
    //第一次調用
    chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >
        |-chakra!Js::JavascriptFunction::CallFunction<1>
            |-chakra!Js::JavascriptFunction::DeferredParsingThunk
                |-chakra!Js::JavascriptFunction::DeferredParse    //獲取NativeCodeGenerator::CheckCodeGenThunk函數
                |-chakra!NativeCodeGenerator::CheckCodeGenThunk
                    |-chakra!Js::InterpreterStackFrame::DelayDynamicInterpreterThunk
                        |-jmp_code  
                            |-chakra!Js::InterpreterStackFrame::InterpreterThunk
    

    在前面沒有提到的是,上面調用流程中的Js::JavascriptFunction::DeferredParse函數。此函數內部會進行函數解析相關的工作,并且返回NativeCodeGenerator::CheckCodeGenThunk函數的指針,然后在返回Js::JavascriptFunction::DeferredParsingThunk函數后對其進行調用。NativeCodeGenerator::CheckCodeGenThunk函數的指針也是通過解析Js::JavascriptFunction對象獲得的。代碼如下。

    #!js
    int __cdecl Js::JavascriptFunction::DeferredParsingThunk(struct Js::ScriptFunction *p_script_function)
    {
        NativeCodeGenerator_CheckCodeGenThunk = Js::JavascriptFunction::DeferredParse(&p_script_function);
        return NativeCodeGenerator_CheckCodeGenThunk();
    }
    
    
    .text:002AB3F0 push    ebp
    .text:002AB3F1 mov     ebp, esp
    .text:002AB3F3 lea     eax, [esp+p_script_function]
    .text:002AB3F7 push    eax             ; struct Js::ScriptFunction **
    .text:002AB3F8 call    Js::JavascriptFunction::DeferredParse
    .text:002AB3FD pop     ebp
    .text:002AB3FE jmp     eax
    

    在這個跳轉位置上并沒有對eax中的函數指針進行CFG檢查。所以可以利用其進行eip劫持。不過還首先還要知道Js::JavascriptFunction::DeferredParse函數返回的NativeCodeGenerator::CheckCodeGenThunk函數指針是如何通過Js::ScriptFunction對象何解析出來的。解析過程如下。

    #!bash
    0:010> u poi(070af050)
    chakra!Js::ScriptFunction::`vftable':
    
    0:010> dd 070af050 + 14
    070af064  076690e0 5fb11ef4 00000000 00000000
    
    0:010> dd 076690e0 + 10
    076690f0  076690e0 04186628 07065f90 00000000
    
    0:010> dd 076690e0 + 28
    07669108  07010dc0 000001a8 00000035 00000000
    
    0:010> dd 07010dc0 
    07010dc0  5f696000 05a452b8 00000000 5f8db9e0
    
    0:010> u 5f8db9e0
    chakra!NativeCodeGenerator::CheckCodeGenThunk:
    

    如上所述,Js::JavascriptFunction::DeferredParse通過解析Js::ScriptFunction對象獲取NativeCodeGenerator::CheckCodeGenThunk函數指針,解析方法簡寫為[[[Js::ScriptFunction+14]+10]+28]+0c。所以只要偽造此處內存中的數據,即可通過調用函數來間接觸發Js::JavascriptFunction::DeferredParse函數的調用,進而劫持eip,具體如下。

    #!bash
    0:010> g
    Breakpoint 0 hit
    eax=603ba064 ebx=063fba10 ecx=063fba40 edx=063fba40 esi=00000001 edi=058fc6b0
    eip=603ba064 esp=058fc414 ebp=058fc454 iopl=0         nv up ei ng nz na po cy
    cs=001b  ss=0023  ds=0023  es=0023  fs=003b  gs=0000             efl=00000283
    chakra!`dynamic initializer for 'DOMFastPathInfo::getterTable''+0x734:
    603ba064 94              xchg    eax,esp
    603ba065 c3              ret
    

    這樣就繞過了cfg,成功劫持了eip。這種方法簡單穩定,在獲得了內存讀寫能力時使用是很方便的。此漏洞已于2015年7月25日報告微軟。

    0x03 修補方案


    本文所述的漏洞微軟已經修補,修補方案也比較簡單就是對此處跳轉增加cfg檢查。

    #!bash
    .text:002AB460 push    ebp
    .text:002AB461 mov     ebp, esp
    .text:002AB463 lea     eax, [esp+arg_0]
    .text:002AB467 push    eax
    .text:002AB468 call    Js::JavascriptFunction::DeferredParse
    .text:002AB46D mov     ecx, eax        ; this
    .text:002AB46F call    ds:___guard_check_icall_fptr  //增加cfg檢查
    .text:002AB475 mov     eax, ecx
    .text:002AB477 pop     ebp
    .text:002AB478 jmp     eax
    .text:00
    

    0x04 參考


    97av