王 越,孫 亮,王軼駿,薛 質
(1.上海交通大學,上海 200240;2.江蘇省南通市公安局,江蘇 南通 226001)
Web 瀏覽器安全是用戶在網絡環(huán)境中至關重要的一環(huán),而JavaScript 引擎是瀏覽器中的重要組件,攻擊者可以通過釣魚網頁輕易地讓用戶觸發(fā)JavaScript 引擎漏洞。JavaScript 引擎又是內存危險的,觸發(fā)漏洞后攻擊者可以構造特殊的原語來實現內存的任意讀寫,從而控制被攻擊者的設備。JavaScript 引擎漏洞在近幾年頻繁出現,伴隨的利用腳本都僅僅需要遠程瀏覽器訪問,范圍覆蓋Windows、MacOS、iOS、Android 各種操作系統(tǒng),故JavaScript引擎的安全問題已經是安全研究的重點對象。
近幾年來,JavaScript 引擎漏洞中JIT 編譯器的漏洞占據了越來越大的比重。JIT 編譯器,全稱(Just-In-Time)是一種程序語言提高運行效率的方法。如圖1 所示,JavaScript 引擎解釋執(zhí)行JavaScript 的流程為:先通過詞法和語法分析將JavaScript 腳本轉化為AST 樹,然后編譯為中間語言字節(jié)碼,接著進行逐步解釋執(zhí)行,當執(zhí)行的過程出現多次循環(huán)或者多次函數調用時,JIT 編譯器會將該部分字節(jié)碼重新編譯,通過編譯優(yōu)化提升運行效率,最終轉化為符合操作系統(tǒng)的機器碼并執(zhí)行。
圖1 解釋執(zhí)行JavaScript 流程
JIT 優(yōu)化階段,JIT 會將優(yōu)化拆分成為多個步驟(如常數折疊、循環(huán)不變量提升等),這些步驟即為每一個具體的JIT 優(yōu)化階段。JIT 優(yōu)化時會將需要優(yōu)化的代碼進行分析,當其滿足優(yōu)化階段的條件時,進行優(yōu)化。
JIT 編譯器漏洞,主要包含的是JIT 優(yōu)化相關的漏洞,優(yōu)化漏洞通常在JIT 編譯優(yōu)化過程中利用錯誤預測或繞過檢查而達到利用。其中包含繞過邊界檢查,如CVE-2015-0817、CVE-2017-2547 和CVE-2018-0769;繞過類型檢查,如CVE-2017-11802、CVE-2018-17463 和CVE-2018-4233;以及各類其他類型漏洞。
模糊測試是一種自動化漏洞挖掘技術。尤其在針對瀏覽器引擎這類復雜的系統(tǒng)時,模糊測試相比代碼審計等其他方式有著更高的挖掘漏洞效率。其核心思想是將隨機生成的輸入重復提供給應用程序,然后在處理輸入直至程序退出期間,監(jiān)視程序是否出現錯誤情況。模糊測試主要分為兩類,基于生成的方法和基于變異的方法。分述如下:
基于生成的模糊測試方法,每個輸入文件都是從頭開始生成的,通常遵循一組預定義的規(guī)則。該規(guī)則將是上下文無關并會限定所有輸入的集合。在生成的過程中,通過隨機選擇實現隨機生成。
基于變異的模糊測試方法,是從一組已知良好的種子文件開始,然后以隨機方式對它們進行變異??赡艿淖儺惏忍睾妥止?jié)翻轉、遞增和遞減整數值、插入預定義的特殊整數和字符串值等。
在模糊測試程序運行后便會不斷生成測試樣本,讓目標程序執(zhí)行,獲取執(zhí)行結果并統(tǒng)計。衡量模糊測試對目標程序漏洞挖掘的進度,通常使用的是覆蓋率指標。覆蓋率是通過樁來計算的,樁存在于目標程序的分支跳轉和函數調用處,通常都是在編譯目標程序時由編譯器完成插樁工作[1]。每次執(zhí)行目標程序后,模糊測試工具可以獲得執(zhí)行過程中抵達的樁信息,計算抵達過的樁數量相對于總數的占比,即為覆蓋率指標。
為了能盡快提高覆蓋率,提出了基于覆蓋率為導向的模糊測試方法,當樣本抵達了新的樁,則將其確定為“有趣”的樣本保留,在未來繼續(xù)對其進行變異,如此往復,逐漸遍歷系統(tǒng)的所有空間。
在現今針對JavaScript引擎的模糊測試工具中,有基于規(guī)則生成的[2,3],也有基于變異生成的[4,5,6,7],本節(jié)會介紹當今主流的幾款針對JavaScript 引擎的模糊測試工具[2,6,8],并分析其使用的模糊測試技術。
這是一款基于語料庫的生成模糊測試工具,從JavaScript 種子文件中分解出代碼單元片段,對其進行變量重命名、數據流分析、類型分析后并放入代碼塊池中,然后對代碼塊池中的片段進行隨機選擇,組合生成JavaScript 測試樣本。CodeAlchemist 在組合代碼片段時,會考慮上下文約束,從符合變量類型和數量的代碼片段中隨機選擇。該方法成功提高了模糊測試樣本的成功率。
這是一款基于中間語言的模糊測試工具,Fuzzilli 定義了一種新的中間語言FuzzIL 并在其上進行生成和變異,在組合了一系列中間語言后,統(tǒng)一將其提升為JavaScript 測試樣本。提升過程時基于上下文和類型系統(tǒng),測試樣本有著較高的JavaScript語義正確性,并且變異過程是基于覆蓋率導向的,可以達到較高的覆蓋率。
這是一款基于類型語法樹的變異模糊測試工具,將JavaScript 種子文件轉為語法樹,然后通過類型分析,產生包含類型標識的語法樹(Typed AST)。DIE 基于類型語法樹并保留種子文件結構和類型特征進行變異,優(yōu)質的樣本對于DIE 非常重要,故其選擇了大量以往的JavaScript 崩潰樣本。
對于現今針對JavaScript 引擎的模糊測試工具而言,測試樣本的成功率和覆蓋率是重要的衡量指標。對于JavaScript 引擎中的JIT 編譯器,測試樣本成功率和覆蓋率同樣是衡量模糊測試性能的重要指標。其中測試樣本成功率指成功進行JIT 優(yōu)化并執(zhí)行優(yōu)化代碼的樣本的比例,而覆蓋率指JIT 編譯器部分的覆蓋率。
當今針對JavaScript 引擎的模糊測試工具對JIT引擎的測試樣本成功率不高。原因為:(1)JIT 引擎需要特殊的條件進行觸發(fā),并不是所有測試樣本都會觸發(fā)JIT 優(yōu)化;(2)JIT 代碼會出現優(yōu)化退出的情況,而以一個會產生優(yōu)化退出的樣本作為種子文件時,會產生許多不成功的測試樣本;(3)生成過長的樣本會影響JIT 的運行時間而導致超時。
當今針對JavaScript 引擎的模糊測試工具對JIT引擎的覆蓋率也不高。原因為:(1)需要擁有優(yōu)質的JIT 種子文件作為變異樣本;(2)在模糊測試JIT 優(yōu)化階段時較為盲目,無法針對某一JIT 優(yōu)化階段進行專一的探索。
該節(jié)會介紹本模糊測試引擎的設計思路,包含生成JIT 種子文件、檢測和變異三個部分。
從觸發(fā)JIT 優(yōu)化條件而言,需要一個循環(huán)被多次執(zhí)行,這樣在循環(huán)內部的代碼將會被JIT 優(yōu)化。故設計了一個JIT 種子文件的模板,包含:(1)一個將要被優(yōu)化的opt 函數內部;(2)觸發(fā)JIT 優(yōu)化的循環(huán);(3)外部不被優(yōu)化的代碼。如圖2 所示,在修改了JavaScript 引擎的運行參數后,可以將多次調用的數量減少為10~1000 次。
圖2 種子文件生成結構
在生成JIT 種子文件時,采用基于中間語言的生成方式。相比基于語法或基本塊的生成方式,中間語言可以構造更為泛型和復雜的句法結構。另外在從中間語言提升為JavaScript 的過程中會考慮上下文約束,每一個變量不僅保存類型信息,還有方法、屬性、原型鏈信息,相比基于類型語法樹的方法能夠生成語義正確性更高的樣本。如圖3 顯示了如果只通過類型語法樹進行生成可能導致的語義錯誤,由于未考慮到原型鏈,會認為變量a 作為數組類型仍擁有splice 方法。
圖3 單一的類型系統(tǒng)可能發(fā)生的語義錯誤
在檢測上,除了崩潰捕獲、運行時長和覆蓋率信息,還需要檢測JIT 優(yōu)化是否成功執(zhí)行。JIT 編譯后會在代碼中插入一些檢查來確保JIT 優(yōu)化的正確執(zhí)行,而當檢查不被通過時,JIT 優(yōu)化將會被退出,返回到原本的字節(jié)碼進行逐步解釋執(zhí)行。在這種情況下,該測試樣本并沒有對JIT 編譯器進行有效的模糊測試。
JIT 種子文件在生成后需要被執(zhí)行一次,統(tǒng)計其運行時長、覆蓋率和JIT 執(zhí)行情況,并以此決定其是否被設置優(yōu)先級放入變異隊列中。如果生成的JIT 種子文件沒有觸發(fā)JIT 優(yōu)化或退出JIT 優(yōu)化,則認為對該種子文件的變異無法有效地對JIT 進行模糊測試,將會拋棄該種子文件。當一個JIT 種子文件抵達了新的樁,成功執(zhí)行了JIT 優(yōu)化,并且有較短的運行時長后,該種子文件將會被設置一個較高的優(yōu)先級放入變異隊列中。
變異會選擇一個JIT 種子文件,在不改變其中間語言結構的情況下,重新生成中間語言中可替換的變量,產生新的測試樣本。使用該方法的原因是觸發(fā)JIT 優(yōu)化每個階段的條件和每一條JIT 優(yōu)化的JavaScript 語句都有較大的關系。如果如Fuzzilli 在每次變異進行插入、合并等細粒度較大的變異操作,將會大幅修改測試樣本結構,從而改變已觸發(fā)的JIT 優(yōu)化階段,導致盲目地對JIT 優(yōu)化階段進行模糊測試。而本方法保持了中間語言結構,并在此之上進行的變異能大概率確保觸發(fā)的測試樣本的JIT優(yōu)化階段和JIT 種子文件的JIT 優(yōu)化階段相同,從而能更深入地探索JIT優(yōu)化階段,以達到更高的覆蓋率。
種子文件的能量分配規(guī)則類似主流的模糊測試工具[9,10]。但對于優(yōu)化函數內部和外部的能量分配和變異方式卻是不同的。每次對一個種子文件進行變異會執(zhí)行數次基于中間語言的變異操作,每次操作在優(yōu)化函數內部的概率會大于在外部的概率。在JIT 優(yōu)化函數內部會采用保留中間語言結構的變異方式,而在優(yōu)化函數外部則會細粒度大的變異方式。
本節(jié)會分別敘述本模糊測試工具實現上較為核心的幾個部分。
中間語言是一種抽象的操作,本方法定義了如LoadProperty,StoreElement,CallMethod,BeginIf 等中間語言,中間語言需要盡可能涵蓋JavaScript 的各種語言特征,如變量聲明、賦值語句、一元操作符、二元操作符、函數調用、方法調用、創(chuàng)建對象、控制流等,如圖4 所示。
圖4 中間語言描述
在生成中間語言后,對其逐條分析其上下文環(huán)境,從已有的上下文容器中獲取合適的變量,然后再分析該條中間語言生成的返回值變量,將其存入上下文容器中,最終提升為JavaScript 語句。
系統(tǒng)需要知道每一條中間語言執(zhí)行后,上下文變量的情況,來確保語義的正確性。通過維持一個容器,在分析中間語言的過程中實時地將上下文變量存儲在容器中。容器中存儲著JavaScript 內置的構造器對象,也會將全局變量和局部變量存儲其中。在逐條分析中間語言時,當遇到if-else、for 循環(huán)、函數聲明等會產生新作用域的中間語言,會生成一個新的域環(huán)境,在新作用域中生成的變量會被存儲在新域環(huán)境中。當退出該作用域時,會將該域環(huán)境以及其中的變量全部刪除。容器同時為系統(tǒng)提供獲取當前上下文環(huán)境下各種類型的變量接口。
每一個變量都繼承于Variable 類,該類包含了變量名、類型、方法、屬性、原型鏈和函數簽名。在JavaScript 中的內置類型有許多,本方法將其分類為基礎的Undefined、Int、Float、String、Object、Boolean、Function、Constructor。另外針對Object 對象再根據不同的構造器劃分,包含Symbol、Map、Set、ArrayBuffer 等。每一種類型都存儲了屬性名和方法名以及原型鏈的上一個對象指針。對于內置對象的方法或者自定義的函數,會存儲定義方法所需參數和返回值的類型作為函數簽名,以便在調用方法時準確地從上下文容器中取出可用的變量。值得注意的是,在調用方法的過程中,需要考慮原型鏈的規(guī)則,優(yōu)先尋找當前對象的方法是否被定義,然后是構造器的方法是否被定義,再尋找內置的方法。如果沒有則去尋找原型鏈上一個對象的構造器方法直至到達原型鏈頭節(jié)點。如圖5 所示,定義了JavaScript 中Array 的內置方法,在該對象中,鍵名對應著方法名,值對應著參數列表的類型,數組最后一項為返回值類型。
圖5 系統(tǒng)對JavaScript 內置方法的描述
生成測試樣本算法如圖6 所示。
圖6 生成測試樣本算法
模糊測試對于運行效率有著很高的要求,所以系統(tǒng)應盡可能提高運行效率。系統(tǒng)的限速瓶頸在于JavaScript 代碼的執(zhí)行時長,故參考了AFL[9]的思路,使用了Fork Server 來節(jié)約進程的創(chuàng)建開銷。系統(tǒng)架構如圖7 所示,模糊測試系統(tǒng)在選擇了一個種子文件后會進行變異并生成JavaScript 測試樣本,而在另一處,Fork Server 在解釋執(zhí)行JavaScript 代碼前等待信號,接收到信號后fork 進程執(zhí)行樣本,獲得結果并統(tǒng)計執(zhí)行結果(崩潰、運行錯誤、超時、運行成功)。
圖7 系統(tǒng)運行模糊測試流程
本模糊測試工具JITFuzz 使用Python3 開發(fā),實現生成種子文件和變異功能,Fork Server 和執(zhí)行結果統(tǒng)計使用C 語言編寫。
采用成功率、超時率、JIT 成功率、JIT 覆蓋率作為評價本模糊測試系統(tǒng)的主要指標,描述如下。
成功率:執(zhí)行成功的測試樣本數量/測試樣本總數。
超時率:執(zhí)行超時的測試樣本數量/測試樣本總數。
JIT 成功率:執(zhí)行成功并成功觸發(fā)JIT 優(yōu)化的測試樣本數量/測試樣本總數。
JIT 覆蓋率:抵達的樁的數量/JIT 編譯器的樁的總數。
如圖8 所示,JITFuzz 和Fuzzilli 的測試樣本成功率和超時率較為接近,優(yōu)于CodeAlchemist 和DIE的測試樣本成功率和超時率。說明本方法采用了基于中間語言的生成和變異算法,并考慮了上下文和變量特性,有效地提升了測試樣本的JavaScript 語義正確性,能夠生成正確性更高的測試樣本。
圖8 測試樣本成功率和超時率統(tǒng)計
考慮在所有測試樣本中,成功觸發(fā)JIT 優(yōu)化的比例,統(tǒng)計結果如圖9 所示,JITFuzz 相比Code Alchemist、DIE、Fuzzilli 都有更高的JIT 成功率。這說明本方法采用生成JIT 種子文件和基于中間語言結構的變異方法所產生的樣本,有更高的成功執(zhí)行JIT 優(yōu)化的概率。
此外,JITFuzz 和Fuzzilli 分析比較了模糊測試的JIT 覆蓋率情況。通過對JavaScript 引擎中JIT 編譯器部分的插樁,統(tǒng)計覆蓋率情況如圖10 所示。
圖9 測試樣本JIT 成功率統(tǒng)計
圖10 JITFuzz 和Fuzzilli 的JIT 覆蓋率統(tǒng)計
相比于Fuzzilli,JITFuzz 抵達的JIT 覆蓋率是Fuzzilli 的1.75 倍,并且在相同的樣本數量下,JITFuzz 能達到更高的覆蓋率,說明單位樣本觸發(fā)覆蓋率的數量更高,體現了本方法所采用的變異方法在針對JIT 編譯器進行模糊測試時有更好的效果。
本文提出了一種針對JavaScript 引擎JIT 編譯器進行模糊測試的方法。該方法借鑒了前人的思路,采用了基于覆蓋率和中間語言的技術,為能夠順利對JavaScript引擎進行模糊測試打下了良好的基礎。本文在此之上針對JIT 編譯器提出了新穎的模糊測試方法,首先利用觸發(fā)JIT 優(yōu)化的模板生成JIT 種子文件,并針對合適的種子文件進行保持中間語言結構的變異。這樣可以使得測試樣本有更高的JIT優(yōu)化概率和對JIT 優(yōu)化階段更為專注的探索,有利于覆蓋率的提升。實驗結果表明,相比于現今較為流行的模糊測試工具,基于本文所實現的模糊測試工具JITFuzz 有更高的JIT 成功率和JIT 覆蓋率,表明本方法針對JIT 編譯器的模糊測試具有更好的性能優(yōu)越性,為進一步的漏洞挖掘提供了良好的基礎。