王 越,孫 亮,王軼駿,薛 質
(1.上海交通大學,上海 200240;2.江蘇省南通市公安局,江蘇 南通 226001)
Web 瀏覽器安全是用戶在網絡環境中至關重要的一環,而JavaScript 引擎是瀏覽器中的重要組件,攻擊者可以通過釣魚網頁輕易地讓用戶觸發JavaScript 引擎漏洞。JavaScript 引擎又是內存危險的,觸發漏洞后攻擊者可以構造特殊的原語來實現內存的任意讀寫,從而控制被攻擊者的設備。JavaScript 引擎漏洞在近幾年頻繁出現,伴隨的利用腳本都僅僅需要遠程瀏覽器訪問,范圍覆蓋Windows、MacOS、iOS、Android 各種操作系統,故JavaScript引擎的安全問題已經是安全研究的重點對象。
近幾年來,JavaScript 引擎漏洞中JIT 編譯器的漏洞占據了越來越大的比重。JIT 編譯器,全稱(Just-In-Time)是一種程序語言提高運行效率的方法。如圖1 所示,JavaScript 引擎解釋執行JavaScript 的流程為:先通過詞法和語法分析將JavaScript 腳本轉化為AST 樹,然后編譯為中間語言字節碼,接著進行逐步解釋執行,當執行的過程出現多次循環或者多次函數調用時,JIT 編譯器會將該部分字節碼重新編譯,通過編譯優化提升運行效率,最終轉化為符合操作系統的機器碼并執行。

圖1 解釋執行JavaScript 流程
JIT 優化階段,JIT 會將優化拆分成為多個步驟(如常數折疊、循環不變量提升等),這些步驟即為每一個具體的JIT 優化階段。JIT 優化時會將需要優化的代碼進行分析,當其滿足優化階段的條件時,進行優化。
JIT 編譯器漏洞,主要包含的是JIT 優化相關的漏洞,優化漏洞通常在JIT 編譯優化過程中利用錯誤預測或繞過檢查而達到利用。其中包含繞過邊界檢查,如CVE-2015-0817、CVE-2017-2547 和CVE-2018-0769;繞過類型檢查,如CVE-2017-11802、CVE-2018-17463 和CVE-2018-4233;以及各類其他類型漏洞。
模糊測試是一種自動化漏洞挖掘技術。尤其在針對瀏覽器引擎這類復雜的系統時,模糊測試相比代碼審計等其他方式有著更高的挖掘漏洞效率。其核心思想是將隨機生成的輸入重復提供給應用程序,然后在處理輸入直至程序退出期間,監視程序是否出現錯誤情況。模糊測試主要分為兩類,基于生成的方法和基于變異的方法。分述如下:
基于生成的模糊測試方法,每個輸入文件都是從頭開始生成的,通常遵循一組預定義的規則。該規則將是上下文無關并會限定所有輸入的集合。在生成的過程中,通過隨機選擇實現隨機生成。
基于變異的模糊測試方法,是從一組已知良好的種子文件開始,然后以隨機方式對它們進行變異。可能的變異包含比特和字節翻轉、遞增和遞減整數值、插入預定義的特殊整數和字符串值等。
在模糊測試程序運行后便會不斷生成測試樣本,讓目標程序執行,獲取執行結果并統計。衡量模糊測試對目標程序漏洞挖掘的進度,通常使用的是覆蓋率指標。覆蓋率是通過樁來計算的,樁存在于目標程序的分支跳轉和函數調用處,通常都是在編譯目標程序時由編譯器完成插樁工作[1]。每次執行目標程序后,模糊測試工具可以獲得執行過程中抵達的樁信息,計算抵達過的樁數量相對于總數的占比,即為覆蓋率指標。
為了能盡快提高覆蓋率,提出了基于覆蓋率為導向的模糊測試方法,當樣本抵達了新的樁,則將其確定為“有趣”的樣本保留,在未來繼續對其進行變異,如此往復,逐漸遍歷系統的所有空間。
在現今針對JavaScript引擎的模糊測試工具中,有基于規則生成的[2,3],也有基于變異生成的[4,5,6,7],本節會介紹當今主流的幾款針對JavaScript 引擎的模糊測試工具[2,6,8],并分析其使用的模糊測試技術。
這是一款基于語料庫的生成模糊測試工具,從JavaScript 種子文件中分解出代碼單元片段,對其進行變量重命名、數據流分析、類型分析后并放入代碼塊池中,然后對代碼塊池中的片段進行隨機選擇,組合生成JavaScript 測試樣本。CodeAlchemist 在組合代碼片段時,會考慮上下文約束,從符合變量類型和數量的代碼片段中隨機選擇。該方法成功提高了模糊測試樣本的成功率。
這是一款基于中間語言的模糊測試工具,Fuzzilli 定義了一種新的中間語言FuzzIL 并在其上進行生成和變異,在組合了一系列中間語言后,統一將其提升為JavaScript 測試樣本。提升過程時基于上下文和類型系統,測試樣本有著較高的JavaScript語義正確性,并且變異過程是基于覆蓋率導向的,可以達到較高的覆蓋率。
這是一款基于類型語法樹的變異模糊測試工具,將JavaScript 種子文件轉為語法樹,然后通過類型分析,產生包含類型標識的語法樹(Typed AST)。DIE 基于類型語法樹并保留種子文件結構和類型特征進行變異,優質的樣本對于DIE 非常重要,故其選擇了大量以往的JavaScript 崩潰樣本。
對于現今針對JavaScript 引擎的模糊測試工具而言,測試樣本的成功率和覆蓋率是重要的衡量指標。對于JavaScript 引擎中的JIT 編譯器,測試樣本成功率和覆蓋率同樣是衡量模糊測試性能的重要指標。其中測試樣本成功率指成功進行JIT 優化并執行優化代碼的樣本的比例,而覆蓋率指JIT 編譯器部分的覆蓋率。
當今針對JavaScript 引擎的模糊測試工具對JIT引擎的測試樣本成功率不高。原因為:(1)JIT 引擎需要特殊的條件進行觸發,并不是所有測試樣本都會觸發JIT 優化;(2)JIT 代碼會出現優化退出的情況,而以一個會產生優化退出的樣本作為種子文件時,會產生許多不成功的測試樣本;(3)生成過長的樣本會影響JIT 的運行時間而導致超時。
當今針對JavaScript 引擎的模糊測試工具對JIT引擎的覆蓋率也不高。原因為:(1)需要擁有優質的JIT 種子文件作為變異樣本;(2)在模糊測試JIT 優化階段時較為盲目,無法針對某一JIT 優化階段進行專一的探索。
該節會介紹本模糊測試引擎的設計思路,包含生成JIT 種子文件、檢測和變異三個部分。
從觸發JIT 優化條件而言,需要一個循環被多次執行,這樣在循環內部的代碼將會被JIT 優化。故設計了一個JIT 種子文件的模板,包含:(1)一個將要被優化的opt 函數內部;(2)觸發JIT 優化的循環;(3)外部不被優化的代碼。如圖2 所示,在修改了JavaScript 引擎的運行參數后,可以將多次調用的數量減少為10~1000 次。

圖2 種子文件生成結構
在生成JIT 種子文件時,采用基于中間語言的生成方式。相比基于語法或基本塊的生成方式,中間語言可以構造更為泛型和復雜的句法結構。另外在從中間語言提升為JavaScript 的過程中會考慮上下文約束,每一個變量不僅保存類型信息,還有方法、屬性、原型鏈信息,相比基于類型語法樹的方法能夠生成語義正確性更高的樣本。如圖3 顯示了如果只通過類型語法樹進行生成可能導致的語義錯誤,由于未考慮到原型鏈,會認為變量a 作為數組類型仍擁有splice 方法。

圖3 單一的類型系統可能發生的語義錯誤
在檢測上,除了崩潰捕獲、運行時長和覆蓋率信息,還需要檢測JIT 優化是否成功執行。JIT 編譯后會在代碼中插入一些檢查來確保JIT 優化的正確執行,而當檢查不被通過時,JIT 優化將會被退出,返回到原本的字節碼進行逐步解釋執行。在這種情況下,該測試樣本并沒有對JIT 編譯器進行有效的模糊測試。
JIT 種子文件在生成后需要被執行一次,統計其運行時長、覆蓋率和JIT 執行情況,并以此決定其是否被設置優先級放入變異隊列中。如果生成的JIT 種子文件沒有觸發JIT 優化或退出JIT 優化,則認為對該種子文件的變異無法有效地對JIT 進行模糊測試,將會拋棄該種子文件。當一個JIT 種子文件抵達了新的樁,成功執行了JIT 優化,并且有較短的運行時長后,該種子文件將會被設置一個較高的優先級放入變異隊列中。
變異會選擇一個JIT 種子文件,在不改變其中間語言結構的情況下,重新生成中間語言中可替換的變量,產生新的測試樣本。使用該方法的原因是觸發JIT 優化每個階段的條件和每一條JIT 優化的JavaScript 語句都有較大的關系。如果如Fuzzilli 在每次變異進行插入、合并等細粒度較大的變異操作,將會大幅修改測試樣本結構,從而改變已觸發的JIT 優化階段,導致盲目地對JIT 優化階段進行模糊測試。而本方法保持了中間語言結構,并在此之上進行的變異能大概率確保觸發的測試樣本的JIT優化階段和JIT 種子文件的JIT 優化階段相同,從而能更深入地探索JIT優化階段,以達到更高的覆蓋率。
種子文件的能量分配規則類似主流的模糊測試工具[9,10]。但對于優化函數內部和外部的能量分配和變異方式卻是不同的。每次對一個種子文件進行變異會執行數次基于中間語言的變異操作,每次操作在優化函數內部的概率會大于在外部的概率。在JIT 優化函數內部會采用保留中間語言結構的變異方式,而在優化函數外部則會細粒度大的變異方式。
本節會分別敘述本模糊測試工具實現上較為核心的幾個部分。
中間語言是一種抽象的操作,本方法定義了如LoadProperty,StoreElement,CallMethod,BeginIf 等中間語言,中間語言需要盡可能涵蓋JavaScript 的各種語言特征,如變量聲明、賦值語句、一元操作符、二元操作符、函數調用、方法調用、創建對象、控制流等,如圖4 所示。

圖4 中間語言描述
在生成中間語言后,對其逐條分析其上下文環境,從已有的上下文容器中獲取合適的變量,然后再分析該條中間語言生成的返回值變量,將其存入上下文容器中,最終提升為JavaScript 語句。
系統需要知道每一條中間語言執行后,上下文變量的情況,來確保語義的正確性。通過維持一個容器,在分析中間語言的過程中實時地將上下文變量存儲在容器中。容器中存儲著JavaScript 內置的構造器對象,也會將全局變量和局部變量存儲其中。在逐條分析中間語言時,當遇到if-else、for 循環、函數聲明等會產生新作用域的中間語言,會生成一個新的域環境,在新作用域中生成的變量會被存儲在新域環境中。當退出該作用域時,會將該域環境以及其中的變量全部刪除。容器同時為系統提供獲取當前上下文環境下各種類型的變量接口。
每一個變量都繼承于Variable 類,該類包含了變量名、類型、方法、屬性、原型鏈和函數簽名。在JavaScript 中的內置類型有許多,本方法將其分類為基礎的Undefined、Int、Float、String、Object、Boolean、Function、Constructor。另外針對Object 對象再根據不同的構造器劃分,包含Symbol、Map、Set、ArrayBuffer 等。每一種類型都存儲了屬性名和方法名以及原型鏈的上一個對象指針。對于內置對象的方法或者自定義的函數,會存儲定義方法所需參數和返回值的類型作為函數簽名,以便在調用方法時準確地從上下文容器中取出可用的變量。值得注意的是,在調用方法的過程中,需要考慮原型鏈的規則,優先尋找當前對象的方法是否被定義,然后是構造器的方法是否被定義,再尋找內置的方法。如果沒有則去尋找原型鏈上一個對象的構造器方法直至到達原型鏈頭節點。如圖5 所示,定義了JavaScript 中Array 的內置方法,在該對象中,鍵名對應著方法名,值對應著參數列表的類型,數組最后一項為返回值類型。

圖5 系統對JavaScript 內置方法的描述
生成測試樣本算法如圖6 所示。

圖6 生成測試樣本算法
模糊測試對于運行效率有著很高的要求,所以系統應盡可能提高運行效率。系統的限速瓶頸在于JavaScript 代碼的執行時長,故參考了AFL[9]的思路,使用了Fork Server 來節約進程的創建開銷。系統架構如圖7 所示,模糊測試系統在選擇了一個種子文件后會進行變異并生成JavaScript 測試樣本,而在另一處,Fork Server 在解釋執行JavaScript 代碼前等待信號,接收到信號后fork 進程執行樣本,獲得結果并統計執行結果(崩潰、運行錯誤、超時、運行成功)。

圖7 系統運行模糊測試流程
本模糊測試工具JITFuzz 使用Python3 開發,實現生成種子文件和變異功能,Fork Server 和執行結果統計使用C 語言編寫。
采用成功率、超時率、JIT 成功率、JIT 覆蓋率作為評價本模糊測試系統的主要指標,描述如下。
成功率:執行成功的測試樣本數量/測試樣本總數。
超時率:執行超時的測試樣本數量/測試樣本總數。
JIT 成功率:執行成功并成功觸發JIT 優化的測試樣本數量/測試樣本總數。
JIT 覆蓋率:抵達的樁的數量/JIT 編譯器的樁的總數。
如圖8 所示,JITFuzz 和Fuzzilli 的測試樣本成功率和超時率較為接近,優于CodeAlchemist 和DIE的測試樣本成功率和超時率。說明本方法采用了基于中間語言的生成和變異算法,并考慮了上下文和變量特性,有效地提升了測試樣本的JavaScript 語義正確性,能夠生成正確性更高的測試樣本。

圖8 測試樣本成功率和超時率統計
考慮在所有測試樣本中,成功觸發JIT 優化的比例,統計結果如圖9 所示,JITFuzz 相比Code Alchemist、DIE、Fuzzilli 都有更高的JIT 成功率。這說明本方法采用生成JIT 種子文件和基于中間語言結構的變異方法所產生的樣本,有更高的成功執行JIT 優化的概率。
此外,JITFuzz 和Fuzzilli 分析比較了模糊測試的JIT 覆蓋率情況。通過對JavaScript 引擎中JIT 編譯器部分的插樁,統計覆蓋率情況如圖10 所示。

圖9 測試樣本JIT 成功率統計

圖10 JITFuzz 和Fuzzilli 的JIT 覆蓋率統計
相比于Fuzzilli,JITFuzz 抵達的JIT 覆蓋率是Fuzzilli 的1.75 倍,并且在相同的樣本數量下,JITFuzz 能達到更高的覆蓋率,說明單位樣本觸發覆蓋率的數量更高,體現了本方法所采用的變異方法在針對JIT 編譯器進行模糊測試時有更好的效果。
本文提出了一種針對JavaScript 引擎JIT 編譯器進行模糊測試的方法。該方法借鑒了前人的思路,采用了基于覆蓋率和中間語言的技術,為能夠順利對JavaScript引擎進行模糊測試打下了良好的基礎。本文在此之上針對JIT 編譯器提出了新穎的模糊測試方法,首先利用觸發JIT 優化的模板生成JIT 種子文件,并針對合適的種子文件進行保持中間語言結構的變異。這樣可以使得測試樣本有更高的JIT優化概率和對JIT 優化階段更為專注的探索,有利于覆蓋率的提升。實驗結果表明,相比于現今較為流行的模糊測試工具,基于本文所實現的模糊測試工具JITFuzz 有更高的JIT 成功率和JIT 覆蓋率,表明本方法針對JIT 編譯器的模糊測試具有更好的性能優越性,為進一步的漏洞挖掘提供了良好的基礎。