再利用Chakra引擎绕过CFG
xlab · 2015/12/24 15:00
Author:[email?protected]
0x00 前言
本文源自一次與TK閑聊,期間得知成功繞過CFG的經(jīng)過與細(xì)節(jié)(參考:[利用Chakra JIT繞過DEP和CFG])。隨即出于對技術(shù)的興趣,也抽出一些時間看了相關(guān)的東西,結(jié)果發(fā)現(xiàn)了另一處繞過CFG的位置。所以這篇文章中提到的思路與技術(shù)歸根結(jié)底是來自TK提示的,在此特別感謝。
關(guān)于CFG的分析文章已經(jīng)有很多了,想要了解的話可以參考我之前在HitCon 2015上的演講(spartan 0day & exploit)。要說明的是,本文的內(nèi)容即為我演講中馬賽克的部分,至此通過一次內(nèi)存寫實現(xiàn)edge的任意代碼執(zhí)行方法就全部公開了。
0x01 Chakra調(diào)用函數(shù)的邏輯
chakra引擎在函數(shù)調(diào)用時,會根據(jù)所調(diào)用函數(shù)狀態(tài)的不同進行不同的處理。比如第一次調(diào)用的函數(shù)、多次調(diào)用的函數(shù)、DOM接口函數(shù)及經(jīng)過jit編譯后的函數(shù)。不同的函數(shù)類型會有不同的處理流程,而這些不同的處理都會通過Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >函數(shù)調(diào)用Js::JavascriptFunction::CallFunction<1>函數(shù)來實現(xiàn)。
1.函數(shù)的首次調(diào)用與多次調(diào)用
當(dāng)調(diào)用如下腳本時,Js::JavascriptFunction::CallFunction<1>函數(shù)會被Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >函數(shù)調(diào)用。
#!js function test(){}test(); 復(fù)制代碼如果函數(shù)是第一次被調(diào)用,則執(zhí)行流程如下。
#!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 復(fù)制代碼如果再次調(diào)用這個函數(shù)的話,調(diào)用流程如下。
#!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 復(fù)制代碼兩次的調(diào)用流程大致是相同的,其中主要不同是因為,函數(shù)在第一次調(diào)用時候需要通過DeferredParsingThunk函數(shù)對其進行解析。其實函數(shù)只有在第一次調(diào)用時才進行進一步的初始化解析操作,這樣設(shè)計主要是為了效率。而后續(xù)調(diào)用再直接解釋執(zhí)行。
分析發(fā)現(xiàn),Js::JavascriptFunction::CallFunction<1>函數(shù)調(diào)用的子函數(shù)是通過Js::ScriptFunction對象中的數(shù)據(jù)獲得的。后續(xù)調(diào)用的函數(shù)Js::JavascriptFunction::DeferredParsingThunk和NativeCodeGenerator::CheckCodeGenThunk都存在于Js::ScriptFunction對象中。兩次調(diào)用中Js::ScriptFunction對象的變化。
第一次調(diào)用時的Js::ScriptFunction對象。
#!bash 0:010> u poi(06eaf050 ) chakra!Js::ScriptFunction::`vftable':0:010> dd 06eaf050 06eaf050 5f695580 06eaf080 00000000 000000000:010> dd poi(06eaf050+4) 06eaf080 00000012 00000000 06e26c00 06e1fea0 06eaf090 5f8db3f0 00000000 5fb0b454 000001010:010> u poi(poi(06eaf050+4)+0x10) chakra!Js::JavascriptFunction::DeferredParsingThunk: 復(fù)制代碼第二次調(diào)用時的Js::ScriptFunction對象。
#!bash 0:010> u poi(06eaf050 ) chakra!Js::ScriptFunction::`vftable':0:010> dd 06eaf050 06eaf050 5f695580 1ce1a0c0 00000000 000000000:010> dd poi(06eaf050+4) 1ce1a0c0 00000012 00000000 06e26c00 06e1fea0 1ce1a0d0 5f8db9e0 00000000 5fb0b454 000001010:010> u poi(poi(06eaf050+4)+0x10) chakra!NativeCodeGenerator::CheckCodeGenThunk: 復(fù)制代碼所以函數(shù)在第一次調(diào)用與后續(xù)調(diào)用的不同,是通過修改Js::ScriptFunction對象中的函數(shù)指針來實現(xiàn)的。
2.函數(shù)的jit
接下來我們看一下函數(shù)的jit。測試腳本代碼如下,多次調(diào)用test1函數(shù)觸發(fā)其jit。
#!js function test1(num) {return num + 1 + 2 + 3; }//觸發(fā)jittest1(1); 復(fù)制代碼經(jīng)過jit的Js::ScriptFunction對象。
#!bash //新的調(diào)試,對象內(nèi)存地址會不同0:010> u poi(07103050 ) chakra!Js::ScriptFunction::`vftable':0:010> dd 07103050 07103050 5f695580 1d7280c0 00000000 000000000:010> dd poi(07103050+4) 1d7280c0 00000012 00000000 07076c00 071080a0 1d7280d0 0a510600 00000000 5fb0b454 000001010: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 復(fù)制代碼Js::ScriptFunction對象中原本指向NativeCodeGenerator::CheckCodeGenThunk函數(shù)的指針,在jit之后變?yōu)橹赶騤it code的指針。實現(xiàn)了直接調(diào)用函數(shù)jit code。
這里簡單說明一下,在調(diào)用函數(shù)傳遞參數(shù)時,是先將參數(shù)左移一位,然后將最低位置1之后的值進行傳遞的(parameter = (num << 1) & 1)。所以其在獲取參數(shù)之后的第一件事是將其右移1位,獲取參數(shù)原始的值。至于為什么要這樣,我想應(yīng)該是因為腳本引擎垃圾回收機制導(dǎo)致的,引擎通過最低位來區(qū)分對象與數(shù)據(jù)。
#!bash chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >|-chakra!Js::JavascriptFunction::CallFunction<1>|-jit code 復(fù)制代碼調(diào)用jit函數(shù)時的調(diào)用棧如上所示,這就是chakra引擎調(diào)用jit函數(shù)的方法。
3.DOM接口函數(shù)
最后為了完整性,還有一類函數(shù)需要簡單介紹一下,就是DOM接口函數(shù),由其它引擎如渲染引擎提供的函數(shù)(理論上可以為任何其它引擎)。
#!js document.createElement("button"); 復(fù)制代碼執(zhí)行上面腳本則會通過下面的函數(shù)調(diào)用流程,最后調(diào)用到提供接口函數(shù)的引擎中。
#!bash chakra!Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >|-chakra!Js::JavascriptFunction::CallFunction<1>|-chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk //調(diào)用dom接口函數(shù)|-dom_interface_function //EDGEHTML!CFastDOM::CDocument::Trampoline_createElement 復(fù)制代碼當(dāng)調(diào)用dom接口函數(shù)時,Js::InterpreterStackFrame::OP_CallCommon<Js::OpLayoutDynamicProfile<Js::OpLayoutT_CallI<Js::LayoutSizePolicy<0> > > >函數(shù)及后續(xù)的處理流程中所使用的Function對象與前面不同,使用的是Js::JavascriptExternalFunction對象。然后與前面的函數(shù)調(diào)用類似,也是通過解析對象內(nèi)的函數(shù)指針,并對其進行調(diào)用,最終進入到想要調(diào)用的DOM接口函數(shù)中。
#!bash 0:010> u poi(06f2cea0) chakra!Js::JavascriptExternalFunction::`vftable':0:010> dd 06f2cea0 06f2cea0 5f696c4c 06e6f7a0 00000000 000000000:010> dd poi(06f2cea0+4) 06e6f7a0 00000012 00000000 06e76c00 06f040a0 06e6f7b0 5f8c6130 00000000 5fb0b454 000001010:010> u poi(poi(06f2cea0+4)+0x10) chakra!Js::JavascriptExternalFunction::ExternalFunctionThunk: 復(fù)制代碼這就是chakra引擎對不同類型函數(shù)的不同調(diào)用的方式。
0x02 漏洞與利用
經(jīng)過前面對chakra引擎各種調(diào)用函數(shù)方法的介紹,我們再來看一下本文的重點繞過cfg的漏洞。前面提到的在第一次調(diào)用腳本創(chuàng)建的函數(shù)時與后續(xù)調(diào)用此函數(shù)會有不同的流程。這里我們再看一下此處的邏輯,調(diào)用棧如下。
#!bash //第一次調(diào)用 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函數(shù)|-chakra!NativeCodeGenerator::CheckCodeGenThunk|-chakra!Js::InterpreterStackFrame::DelayDynamicInterpreterThunk|-jmp_code |-chakra!Js::InterpreterStackFrame::InterpreterThunk 復(fù)制代碼在前面沒有提到的是,上面調(diào)用流程中的Js::JavascriptFunction::DeferredParse函數(shù)。此函數(shù)內(nèi)部會進行函數(shù)解析相關(guān)的工作,并且返回NativeCodeGenerator::CheckCodeGenThunk函數(shù)的指針,然后在返回Js::JavascriptFunction::DeferredParsingThunk函數(shù)后對其進行調(diào)用。NativeCodeGenerator::CheckCodeGenThunk函數(shù)的指針也是通過解析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 復(fù)制代碼在這個跳轉(zhuǎn)位置上并沒有對eax中的函數(shù)指針進行CFG檢查。所以可以利用其進行eip劫持。不過還首先還要知道Js::JavascriptFunction::DeferredParse函數(shù)返回的NativeCodeGenerator::CheckCodeGenThunk函數(shù)指針是如何通過Js::ScriptFunction對象何解析出來的。解析過程如下。
#!bash 0:010> u poi(070af050) chakra!Js::ScriptFunction::`vftable':0:010> dd 070af050 + 14 070af064 076690e0 5fb11ef4 00000000 000000000:010> dd 076690e0 + 10 076690f0 076690e0 04186628 07065f90 000000000:010> dd 076690e0 + 28 07669108 07010dc0 000001a8 00000035 000000000:010> dd 07010dc0 07010dc0 5f696000 05a452b8 00000000 5f8db9e00:010> u 5f8db9e0 chakra!NativeCodeGenerator::CheckCodeGenThunk: 復(fù)制代碼如上所述,Js::JavascriptFunction::DeferredParse通過解析Js::ScriptFunction對象獲取NativeCodeGenerator::CheckCodeGenThunk函數(shù)指針,解析方法簡寫為[[[Js::ScriptFunction+14]+10]+28]+0c。所以只要偽造此處內(nèi)存中的數(shù)據(jù),即可通過調(diào)用函數(shù)來間接觸發(fā)Js::JavascriptFunction::DeferredParse函數(shù)的調(diào)用,進而劫持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 復(fù)制代碼這樣就繞過了cfg,成功劫持了eip。這種方法簡單穩(wěn)定,在獲得了內(nèi)存讀寫能力時使用是很方便的。此漏洞已于2015年7月25日報告微軟。
0x03 修補方案
本文所述的漏洞微軟已經(jīng)修補,修補方案也比較簡單就是對此處跳轉(zhuǎn)增加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 復(fù)制代碼0x04 參考
- 利用Chakra JIT繞過DEP和CFG
- spartan 0day & exploit
總結(jié)
以上是生活随笔為你收集整理的再利用Chakra引擎绕过CFG的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 梦到自己会看事了什么意思
- 下一篇: 做梦梦到鞋坏了是什么意思