點擊上方藍字,一不小心get更多黑客技能~
簡介
現(xiàn)在人們在選擇瀏覽器的時候,在諸多的考慮因素中,安全性無疑是首當其沖的。在我們的日常活動中,瀏覽器的應(yīng)用幾乎無處不在,例如通過它與親人保持聯(lián)系、編輯敏感的私人和公司文檔,甚至管理我們的金融資產(chǎn)。因此,如果網(wǎng)絡(luò)瀏覽器被黑客攻陷的話,可能會產(chǎn)生災(zāi)難性的后果。實際上,隨著瀏覽器功能的增加,其代碼也變得越來越復(fù)雜,從而增加了潛在的攻擊面。
我們微軟攻防安全研究(OSR)團隊的工作使命,便是讓計算技術(shù)變得更安全。我們每天都在通過各種方法來識別軟件,并與公司其他團隊緊密合作,以便為緩解網(wǎng)絡(luò)世界所面臨的各種攻擊提供相應(yīng)的解決方案。在我們的工作過程中,通常涉及軟件漏洞的識別。但是,我們認為,無論我們多么努力,總是會有更多的漏洞被發(fā)現(xiàn),因此,這并不是我們關(guān)注的重點。相反,我們更關(guān)心的問題是:假設(shè)存在一個漏洞,我們該怎么辦?
迄今為止,我們已經(jīng)取得了不錯的成績。比如,在我們的協(xié)助下,已經(jīng)提高了多款產(chǎn)品的安全性,其中就包括 Edge。與此同時,我們在防止遠程執(zhí)行代碼(RCE)方面也取得了取得重大進展,其中涉及的緩解措施包括:控制流程保護(CFG)、禁止導(dǎo)出、任意代碼保護(ACG)和隔離,特別是Less (LPAC)和 Guard(WDAG)。不過谷歌瀏覽器崩潰原因,我們認為,還必須對自己的安全策略進行嚴格的驗證。為此,我們經(jīng)常采用的一種方法是看看其他公司在做什么,并深入學習他們的研究結(jié)果。
為此,我們開始研究的瀏覽器,因為其安全策略的重點在于沙盒上面。我們想知道是如何阻止RCE漏洞攻擊的,并嘗試回答:沙盒模式是否強大到足以保護瀏覽器的安全?
我們的主要研究發(fā)現(xiàn)包括:
我們發(fā)現(xiàn)的CVE-2017-5121漏洞表明,在現(xiàn)代瀏覽器中找到可遠程利用的漏洞是完全可能的
相對缺乏的RCE緩解措施,意味著從內(nèi)存損壞漏洞到的路徑可能會很短
在沙箱內(nèi)進行的多次安全檢查導(dǎo)致RCE 能夠繞過同源策略(SOP),從而使RCE攻擊者可以訪問受害者的在線服務(wù)(如電子郵件,文檔和銀行會話)并保存憑據(jù)
的漏洞處理流程會導(dǎo)致安全漏洞的細節(jié)在相關(guān)的安全補丁推送到用戶之前就已經(jīng)被公開。
遠程漏洞的查找和利用
為了完成這次安全評估,我們首先需要找到某些漏洞作為突破口。通常,我們首先想到的是尋找內(nèi)存損壞漏洞,例如緩沖區(qū)溢出或UAF漏洞。對于所有網(wǎng)絡(luò)瀏覽器來說,其攻擊面都是非常廣泛的,包括V8 解釋器、Blink DOM引擎和ium PDF PDF渲染器等。對于這個項目來說,我們將把注意力集中在V8上面。
利用,我們最終為找到了一個漏洞。實際上,我們是利用 小組的基于Azure的基礎(chǔ)架構(gòu)來運行的,這是一個由背后的團隊編寫的內(nèi)部 ,使用的是我們自己的引擎。迄今為止,所有公開可用的可能都在V8上跑過了;從另一方面來說,只在內(nèi)部使用,所以我們更有可能在V8上發(fā)現(xiàn)新的漏洞。
尋找bug
與手動代碼審查相比,的一個缺點是,無法立即搞清楚到底是測試用例觸發(fā)了一個漏洞,還是意外的行為造成了一個漏洞。這對我們的OSR來說,尤其如此;我們之前沒有V8方面的使用經(jīng)驗,因此對其內(nèi)部工作機制知之甚少。在我們的測試中,產(chǎn)生的測試用例能夠可靠令V8崩潰,但并不總是以相同的方式,或者說無法以一種有利于攻擊者的方式讓其崩潰。
由于通常產(chǎn)生的代碼會非常龐大和復(fù)雜(在我們的測試中,產(chǎn)生了將近1,500行不可讀的代碼),所以,首先要做的事情就是將測試用例最小化 ——為其瘦身,直到變成一個比較小而且可理解的代碼。下面就是我們最終得到的結(jié)果:
上面的代碼看起來很令人費解,并且沒有真正實現(xiàn)任何東西,但它卻是合法的代碼。它所做的事情谷歌瀏覽器崩潰原因,就是創(chuàng)建一個奇怪的結(jié)構(gòu)化對象,然后對其中的一些字段進行設(shè)置。按理說,這不應(yīng)該觸發(fā)任何奇怪的行為,不過事實上,的確發(fā)生了這樣的情況。當使用D8運行該代碼時,崩潰發(fā)生了:
我們看到,崩潰發(fā)生在()處,說明不是位于靜態(tài)模塊中。因此,它必定是由V8的即時(JIT)編譯器動態(tài)生成的代碼。同時,我們還可以發(fā)現(xiàn)崩潰是因為rax寄存器的值為零導(dǎo)致的。
乍看起來,這像是一個經(jīng)典的空指針引用bug,這樣的話到此就可以放棄了:因為它通常是無法利用的,因為現(xiàn)代操作系統(tǒng)阻止零虛擬地址被映射。我們可以考察一下周圍的代碼,以便更好地了解可能發(fā)生的情況:
我們可以從這段代碼中提取一些有用的信息。首先,我們注意到,崩潰發(fā)生在一個函數(shù)調(diào)用之前,它看起來像一個函數(shù)調(diào)度器存根,這次崩潰主要是由于v8::::的地址被加載到該調(diào)用之前的一個寄存器中引起的。通過查看位于處的函數(shù)的代碼,我們發(fā)現(xiàn)地址確實包含一個call rbx指令,這似乎證實了我們的懷疑。它調(diào)用這一事實的卻很有趣,因為這是.方法的實現(xiàn),而我們最小化的測試用例會調(diào)用它。這似乎表明,崩潰發(fā)生在函數(shù)func0的JIT編譯版本中。
從上面的反匯編代碼中,我們可以收集到的第二條信息是,發(fā)生崩潰時寄存器rax中包含的零值是從內(nèi)存加載的。這個被加載的值好像應(yīng)該作為參數(shù)傳遞給函數(shù)調(diào)用的。我們不難看出,它是從[rdi + 0x18]處加載的。基于此,我們可以仔細看一下相關(guān)的內(nèi)存區(qū):
這里沒有發(fā)現(xiàn)非常有用的信息,只能看出大部分值都是指針。但是,知道這個值(它是一個指針)是從哪里加載的是非常有用的信息,因為它可以幫助我們弄清楚為什么這個值此前為零。利用最新的Time (TTD)功能,我們可以在該位置放置一個內(nèi)存寫入斷點(baw 8 `),然后在該函數(shù)的起始處放置一個執(zhí)行斷點,最后重新運行反跟蹤(g-)。
有趣的是,我們的內(nèi)存寫斷點并沒有觸發(fā),這意味著這個內(nèi)存片段在這個函數(shù)中沒有被初始化,或者至少沒有用到。這可能是正常的,但是如果我們借助于測試用例,例如使用o.b.bc.bca.bcab = ;行替換o.b.bc.bca.bcab = 0;行的話,我們就發(fā)發(fā)現(xiàn),導(dǎo)致崩潰的值原來所在的內(nèi)存區(qū)發(fā)生了變化:
我們看到,常數(shù)值出現(xiàn)在該內(nèi)存區(qū)域的末端。雖然這并不能證明任何事情,但是至少說明,這個內(nèi)存區(qū)域可能是JIT編譯的函數(shù)用來存儲局部變量的。還記得前面代碼崩潰的原因嗎?導(dǎo)致崩潰的原因是加載了一個本來要作為參數(shù)傳遞給.的值引發(fā)的。這進一步印證了我們的這個看法。
結(jié)合TTD,我們可以進一步確認這段內(nèi)存的確未被該函數(shù)初始化的事實,可能的原因是JIT編譯器沒有給出初始化指向用于訪問o.b.ba.bab字段的對象成員的指針的代碼。
為了證實這一點,我們可以使用-trace-turbo和-trace-turbo-graph參數(shù)在D8中運行測試用例。這樣的話,D8就會輸出(V8的JIT編譯器)任何構(gòu)建和優(yōu)化相關(guān)代碼方面的信息。我們可以將它與結(jié)合起來使用,可以顯示用于表示和優(yōu)化代碼的相關(guān)圖。
會依次對這些圖實施各種優(yōu)化。當優(yōu)化過程經(jīng)過一半,也就是在卸載優(yōu)化階段之后,將得到如下所示的代碼的流程圖:
顯然,優(yōu)化器將func0放入了無限循環(huán)中,然后從第一次循環(huán)迭代中把它拽了出來。這些信息對于了解各個代碼塊之間的相互關(guān)系非常幫助。然而,這種展示方式的缺點是,漏掉了對應(yīng)于加載函數(shù)調(diào)用的參數(shù)的節(jié)點,以及局部變量的初始化對應(yīng)的節(jié)點,但是這些信息正好都是我們比較感興趣的。
幸運的是,我們可以使用的界面來顯示這些內(nèi)容。下面我們考察第二個.調(diào)用,我們可以看到參數(shù)來自哪里,以及它被分配到哪里以及在哪里進行初始化:
(注意:需要手動修改節(jié)點標簽以提高可讀性)
在優(yōu)化階段,代碼看起來非常合理:
分配內(nèi)存塊以存儲本地對象o.b.ba(節(jié)點235),并初始化baa和bab字段
分配內(nèi)存塊以存儲本地對象o.b(節(jié)點259),并且初始化所有字段,其中需要特別指出的是,ba初始化為指向前面的o.b.ba的引用
分配內(nèi)存塊以存儲本地對象o(節(jié)點303),并且初始化所有字段
本地對象o的字段b被對對象o.b(節(jié)點185)的引用覆蓋,
本地對象字段o.b.ba.bab被加載(節(jié)點199、209和212)
調(diào)用.方法,將o.b.ba.bab傳遞為第一個參數(shù)
在這個優(yōu)化階段中,編譯的代碼看起來沒有表現(xiàn)出未初始化的局部變量行為(我們猜測這種行為是該bug的根本原因)。話雖如此,該圖的某些方面可以證明我們的猜測。請看一下節(jié)點209和212,分別加載o.b.ba和o.b.ba.bab,用于函數(shù)調(diào)用參數(shù),我們可以看到,偏移量+24和+32與崩潰代碼的反匯編結(jié)果是相對應(yīng)的:
0x17和0x1f的十進制值分別為23和31。考慮到V8標簽的值是如何區(qū)分實際對象與內(nèi)聯(lián)整數(shù)(SMI)的,這一切就順理成章了:如果一個變量的值的最低有效位為1,則被視為指向?qū)ο蟮闹羔槪駝t被視為SMI。因此,V8代碼在解引用的時候,作為一種優(yōu)化措施,會從對象偏移中減1。
由于我們?nèi)匀粺o法解釋這個bug,所以還需繼續(xù)考察這個優(yōu)化過程。在經(jīng)過分析之后,圖形變成下面的樣子:
這里有兩個顯著的差異:
代碼不再是加載o和o.b了——它被優(yōu)化為直接引用o.b,可能是因為該字段的值從未發(fā)生變化的緣故
代碼不再初始化o.b.ba;從圖中可以看出,將節(jié)點264顯示未灰色,這意味著它不再活動,因此不會被內(nèi)置到最終的代碼中
查看這個階段所有的活動節(jié)點,可以發(fā)現(xiàn)這個字段確實沒有被初始化。同時,我們在這個測試用例上運行d8,并且使用--no-turbo-選項,以省略這個優(yōu)化階段:d8不再崩潰,從而確認這就是問題所在。這個結(jié)果表明,谷歌對這個bug的修補方法為:完全禁用v8 6.1中的分析,直到v8 6.2版本中的新分析模塊就緒為止。
現(xiàn)在,我們已經(jīng)清楚了這個bug的根本原因,下面我們要做的就是尋找相應(yīng)的利用方法。雖然這個bug看上去非常強大,但實際上它完全取決于我們對未初始化的內(nèi)存片段的控制能力,以及最終的利用方式。
想了解近期最熱安全消息,看這里!
▼▼▼