再利用Chakra引擎繞過CFG
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::DeferredParsingThunk
和NativeCodeGenerator::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 參考
相關文章
- CVE-2021-26411在野樣本中利用RPC繞過CFG緩解技術的研究2021-04-29RPC
- js繞過-前端加密繞過2021-08-12JS前端加密
- WAF繞過之道2018-07-30
- 使用HTTP頭進行403繞過 速率繞過 Rate Limit Bypass2024-10-27HTTPMIT
- 配置ks.cfg實現自動安裝過程2018-05-21
- md5繞過2024-04-27
- waf 繞過的技巧2020-08-19
- Sql注入之WAF繞過2024-06-21SQL
- frida反除錯繞過2023-04-06除錯
- AMSI 淺析及繞過2021-10-27
- 登陸框select繞過2021-05-25
- powershell程式碼混淆繞過2020-06-21
- sqlmap常用繞過指令碼2020-10-20SQL指令碼
- PHP命令執行與繞過2024-08-16PHP
- 第五章-WAF 繞過2024-04-06
- 小程式繞過 sign 簽名2024-03-20
- 淺談利用session繞過getshell2021-08-06Session
- 繞過PowerShell執行策略方法2020-06-20
- Waf功能、分類與繞過2020-11-15
- 手把手繞過安全狗2020-10-25
- Nmap繞過防火牆掃描2020-10-10防火牆
- Java安全之JNI繞過RASP2020-12-01Java
- 繞過Snoopy的記錄功能2018-05-07OOP
- WinHex 試用期已過視窗繞過方法2018-03-29
- Windows下cmd/powershell命令混淆繞過2022-02-05Windows
- 棧溢位漏洞利用(繞過ASLR)2021-09-18
- 利用白名單繞過360例項2020-08-19
- 併發,繞不過的彎兒2020-04-07
- 檔案包含漏洞(繞過姿勢)2018-06-23
- 繞過應用程式白名單技巧2018-06-04
- 關於隱藏Selenium繞過檢測2024-08-20
- 高版本libc堆fd指標繞過2024-06-03指標
- Java安全之Filter許可權繞過2021-05-23JavaFilter
- Webscan360的防禦與繞過2020-08-19Web
- 使用sqlmap中tamper指令碼繞過waf2020-08-19SQL指令碼
- 幾種通用防注入程式繞過方法2020-08-19
- 關於SSRF和多種繞過方式2022-02-10
- DVWA檔案包含全等級繞過方法2020-12-13