Exploiting CVE-2015-0311, Part II: Bypassing Control Flow Guard on Windows 8.1

wyzsk發表於2020-08-19
作者: Chuck · 2015/04/01 15:09

0x00 概述


作者:Francisco Falcón

地址:https://blog.coresecurity.com/2015/03/25/exploiting-cve-2015-0311-part-ii-bypassing-control-flow-guard-on-windows-8-1-update-3/

三月初我們釋出了一篇分析CVE-2015-0311(Flash Player的UAF)的博文,我們概述瞭如何在Windows 7 SP1上進行利用。我們在博文的末尾提到,該博文敘述的利用過程並不適用於較新版本的Windows,如Windows 8.1 Update 3,原因是新作業系統平臺所採用的的漏洞利用緩解技術---|控制流防護(CFG)

CFG---|微軟於2014年11月在Windows 8.1 Update 3上推出的一種技術---|在每一個間接呼叫前都增加了一個檢查,用以檢查所呼叫的目的地址是否是編譯時所確定的“安全”位置之一。如果執行時檢查失敗,程式就認為存在試圖改變正常執行流程的操作,並立即退出。

在CFG出現之前,利用諸如UAF之類的漏洞進行任意記憶體讀寫等基本操作--如我們在CVE-2015-0311中所做的--通常意味著需要繞過ASLR和DEP然後最終穩定執行程式碼。CFG的特別之處就在於,即使能夠進行記憶體的讀寫等操作,最終想要執行程式碼還是需要付出可觀的額外努力。

事實證明整合到Windows 8.1 Update 3中的IE11的Flash版本在編譯時啟用了CFG,因此通用的利用方法--將物件虛擬函式表(vtable)覆蓋為指向攻擊者控制的資料的指標,然後呼叫物件虛擬函式的方法不再可用。

下面來看一下我們是如何繞過CFG對Win8.1 Update 3上IE11的Adobe Flash Player進行漏洞利用的。這篇文章假定你已經讀過part I(Exploiting CVE-2015-0311: A Use-After-Free in Adobe Flash Player),因此跳過CVE-2015-0311本身的細節的討論,直接進入到劫持瀏覽器程式的執行流上來。

0x01 CFG技術簡介


下圖所示,就是在沒有啟用CFG的Flash版本中,呼叫Vector物件的toString()方法時間接引用被覆蓋的虛擬函式表(vtable)的程式碼,上一篇博文中也提到過:

而下面是Win8.1 Update3的Flash 16.0.0.287(編譯時啟用CFG)執行同樣操作的程式碼:

___|guard_check_icall_fptr函式指標指向**ntdll!LdrpValidateUserCallTarget**,該函式負責檢查間接呼叫的目的地址是否合法。當檢查到假的虛擬函式表,改變正常執行流的企圖就會被發現,然後程式轉向**ntdll!RtlpHandleInvalidUserCallTarget**函式,該函式透過發出一個INT 29h的中斷立刻結束程式。

如想了解更多關於CFG內部機制,我推薦看一下MJ去年在Power Of Community安全會議上的演講“Windows 10 Control Flow Guard Internals”

0x02 解決方法


CFG技術是透過在編譯時新增大量對防護函式的呼叫以保護每一個間接呼叫的。Win8.1 Update 3的Flash Player 16.0.0.287包含了29238次對防護函式的呼叫。:

那麼,就存在如下幾種可以在CFG啟用的情況下修改執行流的方法:

  • 覆蓋返回地址。
  • 利用程式中未使用CFG的模組。
  • 找到因為某種原因而未被CFG保護的間接呼叫。

第一種方法要求能夠獲取到一個棧地址。儘管在改變了Vector長度後獲得了任意讀寫能力,在只知道指向堆中物件的指標的情況下,對於棧地址我還是沒有辦法獲取到。

第二種方法會在利用中引入受感染軟體之外的新的依賴,因此這種方法可能也不是很理想。

我找到了一些未被CFG保護的間接呼叫,如下例所示:

該函式指標位於.data段中,而資料段是具有讀寫許可權的,因此覆蓋該地址絕對是有可能實現的。然而,這種方法的主要問題在於你必須能夠影響程式的執行流程(在還沒有得到程式碼的執行能力之前),以使修改後的函式指標最終是可被呼叫的。那麼如果該函式僅在一些非常見的情況下呼叫(罕見的極端情況下呼叫)怎麼辦?又或者如果該函式僅在程式初始化的早期呼叫怎麼辦?

使用這種方法的另一個障礙在於如果被覆蓋的函式指標被呼叫時沒有CPU暫存器指向我們自己的資料附近,想要轉換棧以開始執行ROP鏈將會變得非常棘手。

目前為止,CFG在保護超過29000個Adobe Flash Player的間接呼叫上做的已經很好了。那麼現在的問題就變為:我們能否在Flash Player中找到未被CFG保護的間接呼叫,並且能夠以簡單的方式影響程式的執行流程,使其呼叫被覆蓋的函式指標?

如前面所述,CFG只保護那些可在編譯時確定的間接呼叫。下一個問題自然就變成:Flash Player中有不在編譯時生成的間接呼叫嗎?答案是肯定的:當然有!

Flash Player包含有一個JIT(Just-In-Time)編譯器,可以即時將ActionScript虛擬機器位元組碼翻譯成本機的機器碼以提高執行速度。JIT生成的程式碼中存在間接呼叫,又因為程式碼是在執行時生成的,因此這些程式碼裡的間接呼叫並未被CFG保護。

0x03 尋找未被保護的間接呼叫


讓我們回到上一篇文章漏洞利用過程中的某處,在該處我們可以修改Vector物件的後設資料,我們可以透過呼叫si32(0xffffffff,0x24)來設定Vector的長度為0xffffffff,而這個新的長度可以讓我們在程式的地址空間中讀寫任意記憶體資料。同時記得我們構造了一個ByteArray物件,我們的ROP鏈就儲存為Vector的第一個元素。而儲存在ByteArray+8位置的dword就是指向虛擬函式表物件的指標(類的真實名稱就是VTable_object,在core/VTable.h中定義):

現在,讓我們仔細檢查一下VTable_object,我們可以看到它包含了很多指標:

我們試著跟進幾個指標(在下面的圖片中我跟進了VTable_object + 0xD4位置的指標),就會發現它們都看起來都差不多一樣。這些都是MethodEnv物件(在core/Method.h中定義的):

MethodEnv物件中的第一個dword是指向其虛擬函式表的指標,第二個dword是一個函式指標(本例中為0x601C0A70)

MethodEnv物件VTable_object + 0xD4位置的指標被間接引用,從而產生一個間接呼叫,呼叫MethodEnv_object+4(0x601c0a70)處的函式指標,我們注意到,此處的間接呼叫程式碼是Flash JIT編譯器生成的,因此是未被CFG保護的!而且這個未保護的間接呼叫是可以透過呼叫ByteArray物件的toString()方法穩定觸發的。

我們可以在VTable_object + 0xD4位置設一個硬體斷點,當我們的ActionScript程式碼呼叫this.the_vector[0].toString()時,由JIT生成的程式碼讀取VTable_object + 0xD4位置的指標,該斷點就會命中。注意此處函式指標是從ECX+4中取出(MOV EAX, DWORD PTR DS:[ECX+4]),並直接地進行呼叫,並未先呼叫CFG防護函式進行安全驗證:

位於堆中的由JIT生成的程式碼,是透過如下路徑執行到的:首先BaseExecMgr::invokeGeneric方法(core/exec.pp中定義)呼叫BaseExecMgr::endCoerce:

#!c++
// Invoker for native or jit code used before we have jit-compiled,
// or after JIT compilation of the invoker has failed.
Atom BaseExecMgr::invokeGeneric(MethodEnv *env, int32_t argc, Atom* atomv)
{
    MethodSignaturep ms = env->get_ms();
    const size_t extra_sz = startCoerce(env, argc, ms);
    MMgc::GC::AllocaAutoPtr _ap;
    uint32_t *ap = (uint32_t *)avmStackAlloc(env->core(), _ap, extra_sz);
    unboxCoerceArgs(env, argc, atomv, ap, ms);
    return endCoerce(env, argc, ap, ms);
}

然後BaseExecMgr::endCoerce呼叫JIT生成的函式(該函式我們在上上段中講述過,其中包含未受CFG保護的間接呼叫):

#!c++
Atom BaseExecMgr::endCoerce(MethodEnv* env, int32_t argc, uint32_t *ap, MethodSignaturep ms)
{
[...]
switch(bt){
[...]
default:
{
    STACKADJUST(); // align stack for 32-bit Windows and MSVC compiler
    const Atom i = (*env->method->_implGPR)(env, argc, ap);
[...]

如下是BaseExecMgr::endCoerce的二進位制程式碼:

0x04 利用


我們已經瞭解瞭如何觸發一個未受CFG保護的間接呼叫,現在我們需要做的是在VTable_object + 0xD4位置放一個虛假的MethodEnv物件。有圖有真相,我們可以在下圖中直觀地看到物件之間的初始狀態以及呼叫關係(點選圖片可放大):

在ActionScript利用程式碼中,我們透過Nicolas Joly的基於Number物件的讀取方法可以獲取到ByteArray object + 8位置的指標,如上一篇博文中提到的:

#!c++
var vtable_object:uint = leak_8_bytes(bytearray_object_pointer + 8);

然後指標值 + 0xD4得到我們想覆蓋的目標地址,之後再計算出Vector物件(其長度我們已經覆蓋為0xffffffff)中我們需要使用的定位,以寫入目標地址:

#!c++
var target_address:uint = vtable_object + 0xd4;
/* 0x28: offset of the first element within the Vector object */
var idx: uint = (target_address - (address_of_vector + 0x28)) / 4;
this.the_vector[idx] = address_of_rop_chain >> 3;

我們將VTable_object + 0xD4位置存放的指標覆蓋為ROP鏈(儲存在上面的ActionScript程式碼片中的 address_of_rop_chain變數中)的地址。同時注意我將address_of_rop_chain右移了三次;這是因為 address_of_rop_chain是unit型別的,而ActionScript虛擬機器內部儲存機制中,unit型別的值是要先左移3次然後再異或6(6代表“Integer”標籤---|我們上一篇博文中提到過)。

最後,我們只需呼叫下ByteArray物件(儲存於this.the_vector[0])的toString()方法,就可以觸發JIT生成程式碼中的未經CFG保護的間接呼叫,然後就會啟動我們的ROP鏈。

#!c++
new Number(this.the_vector[0].toString());

注意,未保護的間接呼叫CALL EAX 呼叫我們控制的函式指標時,ECX暫存器就指向我們的ROP鏈,因此pivoting stack(一種棧轉換技術--指向的堆空間變成新的棧)開始執行ROP鏈就變得很簡單。

0x05結論


控制流防護(CFG)是一種有用的緩解技術,它可以增加利用程式碼編寫的難度和成本。但是就像任何一種已知的漏洞利用緩解技術一樣,在特定的環境下,其不可避免的存在繞過的方法。

本文介紹的繞過方法,利用了JIT生成程式碼中未經保護的間接呼叫,該方法不僅適用於Adobe Flash Player,任何使用了JIT編譯器的軟體都潛在地可以如此利用,因為執行時生成的程式碼是不會受到CFG保護的,除非開發者能夠付出額外的努力將JIT編譯器生成的程式碼固化。

請注意Flash Player的JIT編譯器其實之前就已經被利用過不止一次,用以繞過各種緩解機制:

Dion Blazakis 2010年的“Pointer inference and JIT spraying”

Fermin Serna 2013年的“Flash JIT – Spraying info leak gadgets”

本文章來源於烏雲知識庫,此映象為了方便大家學習研究,文章版權歸烏雲知識庫!

相關文章