劉麗艷 ,李 豐 ,鄒燕燕,5 ,周建華,5 ,樸愛花 ,劉 峰 ,霍 瑋,5
1 中國科學(xué)院信息工程研究所信息安全國家重點(diǎn)實(shí)驗(yàn)室,北京 中國 100093
2 中國科學(xué)院信息工程研究所,北京 中國 100093
3 中國科學(xué)院網(wǎng)絡(luò)測評技術(shù)重點(diǎn)實(shí)驗(yàn)室,北京 中國 100195
4 網(wǎng)絡(luò)安全防護(hù)技術(shù)北京市重點(diǎn)實(shí)驗(yàn)室,北京 中國 100195
5 中國科學(xué)院大學(xué)網(wǎng)絡(luò)空間安全學(xué)院,北京 中國 100049
模糊測試是目前發(fā)現(xiàn)軟件漏洞的最有效方法之一。它通過向目標(biāo)程序提供大量的經(jīng)過特殊構(gòu)造的測試用例,并在程序運(yùn)行過程中監(jiān)控程序的執(zhí)行行為,以程序是否發(fā)生異常行為為標(biāo)志,來發(fā)現(xiàn)目標(biāo)程序可能存在的安全漏洞。為了快速發(fā)現(xiàn)軟件潛在的漏洞,以AFL[1]為代表的模糊測試工具[2-4]通過跟蹤覆蓋率來指導(dǎo)測試用例的變異,力求變異生成的測試用例能夠覆蓋到目標(biāo)程序中更多的未測代碼,從而更有效的發(fā)現(xiàn)目標(biāo)程序中潛在的漏洞。上述模糊測試方法被稱為基于覆蓋率反饋的模糊測試方法。
給定目標(biāo)程序P 以及一系列種子輸入I,目前主流的基于覆蓋率反饋的模糊測試工具,其工作流程可以分為以下三個步驟:(1)變異:通過位翻轉(zhuǎn)、刪除、替換等一系列的策略對種子輸入進(jìn)行變異,生成大量新的輸入;(2)跟蹤與反饋:將新生成的輸入交由目標(biāo)程序執(zhí)行,通過程序插樁等技術(shù)跟蹤目標(biāo)程序在該輸入下的代碼覆蓋率信息(如:節(jié)點(diǎn)覆蓋率、邊覆蓋率等),并根據(jù)所反饋的覆蓋率變化情況,對于覆蓋了新路徑的輸入,在后續(xù)的變異過程中賦給其更高的優(yōu)先級;(3)監(jiān)控:監(jiān)控程序執(zhí)行過程中的異常行為(如:程序崩潰等)以及觸發(fā)異常的輸入,作為漏洞分析及定位的依據(jù)。由此可見,跟蹤測試用例的代碼覆蓋率是決定模糊測試效能的關(guān)鍵環(huán)節(jié)之一。
然而,跟蹤測試用例的代碼覆蓋率也會為模糊測試引入額外的性能開銷,甚至成為模糊測試性能開銷的主要來源。對于開源程序,可通過編譯時插樁插入用于在運(yùn)行時獲取覆蓋信息的代碼。但對于閉源程序,尤其是Windows 平臺的二進(jìn)制程序,需要通過動態(tài)插樁[5]、二進(jìn)制重寫[6]或硬件輔助[7-8]的方法來追蹤覆蓋率信息。其中,基于硬件輔助的方法幾乎不引入額外開銷,但依賴于平臺硬件支持,不易擴(kuò)展。最近的工作[9]表明,對于Linux 平臺上的閉源目標(biāo)程序,AFL 跟蹤代碼覆蓋率的開銷高達(dá)13 倍。本文的實(shí)驗(yàn)分析(詳見第2 章和第5 章)也表明,對于Windows 平臺上的二進(jìn)制程序,跟蹤覆蓋率的開銷是程序正常運(yùn)行的3~18.9 倍,平均占執(zhí)行單個測試用例時間的82%。
相關(guān)工作也就模糊測試過程中跟蹤覆蓋率開銷的緩解提出了改進(jìn)方案。代表性方案UnTracer[9]借助于輕量級檢測和采用基于靜態(tài)二進(jìn)制重寫的覆蓋率跟蹤技術(shù),在所選數(shù)據(jù)集上僅相對程序正常執(zhí)行時間引入了0.3%的額外開銷。其采用的輕量級檢測避免了對沒有價值的種子進(jìn)行覆蓋率跟蹤,但是對于有價值的種子,UnTracer 仍然面對跟蹤覆蓋率的開銷,而其使用的基于靜態(tài)二進(jìn)制重寫的覆蓋率跟蹤技術(shù)在 Windows 平臺上缺乏魯棒性,無法擴(kuò)展到Windows 平臺閉源軟件的模糊測試中。新進(jìn)提出的二進(jìn)制重寫技術(shù)RetroWrite[10],其速度是QEMU 的4.5 倍,但僅適用于64 位Linux 平臺上的地址無關(guān)代碼。采用二進(jìn)制動態(tài)跟蹤代碼覆蓋率雖然開銷較高,但確是目前最具普適性的方法。
本文針對閉源軟件(尤其是Windows 平臺上閉源軟件)模糊測試過程中因跟蹤代碼覆蓋率引入的額外時間開銷問題,提出了一種基于稀疏插樁跟蹤的模糊測試方法,在不影響覆蓋率計(jì)算精度的前提下,采用基于稀疏插樁的跟蹤策略,僅對目標(biāo)程序中覆蓋率不可推導(dǎo)的基本塊或分支進(jìn)行插樁跟蹤,并根據(jù)跟蹤結(jié)果推導(dǎo)其余基本塊或分支的被覆蓋情況,同時結(jié)合“預(yù)熱”優(yōu)化,避免因動態(tài)插樁平臺反復(fù)啟動以及對目標(biāo)程序代碼的重復(fù)翻譯所引入的時間開銷。通過剔除閉源軟件覆蓋率跟蹤過程中的冗余開銷,提高閉源程序模糊測試的效率,進(jìn)而提高針對閉源程序的漏洞發(fā)現(xiàn)效率。
其中,基于稀疏插樁的覆蓋率跟蹤策略以傳統(tǒng)程序分析技術(shù)的支配關(guān)系作為插樁位置選擇以及覆蓋率推導(dǎo)的基礎(chǔ)。目前主流的基于覆蓋率反饋的模糊測試工具默認(rèn)對目標(biāo)程序中的所有基本塊或分支進(jìn)行插樁。但并非所有插樁位置都是必要的。以圖1所示的程序片段及其對應(yīng)的控制流圖(CFG)為例。根據(jù)后支配關(guān)系,如果測試用例覆蓋了基本塊6,則必然也覆蓋了基本塊1。換而言之,基本塊1 是否被覆蓋可以通過對基本塊6 的插樁跟蹤結(jié)果推導(dǎo)出來。因此,在對基本塊6 進(jìn)行插樁的同時,再對基本塊1插樁是冗余的。文獻(xiàn)[11]所述的程序優(yōu)化技術(shù)也表明,可以找到目標(biāo)程序所有基本塊/分支的一個子集,該子集中的基本塊/分支可以推導(dǎo)出目標(biāo)程序控制流圖上的所有基本塊/分支。文獻(xiàn)[11]的實(shí)驗(yàn)也表明,對于選擇的8 個C 程序,只需要覆蓋目標(biāo)程序的特定的32%的分支,即可確保100%的分支覆蓋。本文第5章的實(shí)驗(yàn)也顯示,平均只需要對目標(biāo)程序的40.61%的分支進(jìn)行插樁,就可以推導(dǎo)出目標(biāo)程序中其他分支是否被覆蓋??梢?通過支配關(guān)系指導(dǎo)的稀疏插樁,可以有效的減少不必要的插樁與追蹤。本文在文獻(xiàn)[11]工作的基礎(chǔ)上,擴(kuò)展了對二進(jìn)制代碼分析以及動態(tài)運(yùn)行時插樁的支持,以適應(yīng)閉源程序的模糊測試需求。
如前所述,對閉源程序的覆蓋率跟蹤可以采取動態(tài)二進(jìn)制插樁或基于硬件輔助的技術(shù)。其中,動態(tài)二進(jìn)制插樁相對基于硬件輔助的技術(shù)可以適應(yīng)更多的平臺,因此具有更強(qiáng)的魯棒性。代表性的模糊測試工具AFL 和Vuzzer[12]分別使用QEMU 和Pin[13]對閉源程序進(jìn)行插樁。為使所提出的方法及對應(yīng)工具能夠適應(yīng)更多的平臺,尤其是支持對Windows 平臺上閉源程序的高效模糊測試,本文也采用動態(tài)二進(jìn)制插樁技術(shù)跟蹤代碼覆蓋率。但文獻(xiàn)[9]的統(tǒng)計(jì)顯示,QEMU 引入的額外開銷約為目標(biāo)程序正常執(zhí)行時間的10 倍。本文的實(shí)驗(yàn)結(jié)果也表明,Pin 空載所引入的額外開銷是程序正常運(yùn)行時間的2倍以上,其中52%的開銷來源于動態(tài)代碼翻譯。由于動態(tài)插樁平臺每執(zhí)行一個測試用例,都需要對目標(biāo)程序重新進(jìn)行動態(tài)代碼翻譯,由此帶來冗余的開銷。“預(yù)熱”優(yōu)化的引入可以使先前生成的目標(biāo)程序代碼被所有測試用例共享,從而避免上述冗余開銷。
基于上述方法實(shí)現(xiàn)的基于稀疏插樁的閉源軟件模糊測試原型SiCsFuzzer。對于Windows 平臺上的9個黑盒二進(jìn)制目標(biāo)程序,在不影響覆蓋率計(jì)算精度的前提下,SiCsFuzzer只需對目標(biāo)程序中40.61%的分支進(jìn)行插樁,最終SiCsFuzzer 的執(zhí)行效率比傳統(tǒng)的基于覆蓋率反饋的模糊測試工具快3 倍。
本文的主要貢獻(xiàn)如下:
1) 提出了一種面向閉源程序的高效模糊測試方法。該方法借助基于稀疏插樁的覆蓋率跟蹤以及針對流行的動態(tài)二進(jìn)制插樁工具(例如Pin)的“預(yù)熱”優(yōu)化,降低基于覆蓋率反饋的模糊測試過程中跟蹤覆蓋率的開銷,提高模糊測試效率。
2) 基于該解決方案實(shí)現(xiàn)的原型工具SiCsFuzzer,在Windows 平臺9 個規(guī)模在286KB~19.3MB,類型涉及圖片處理、視頻處理、文件壓縮、加密和文檔處理等類型應(yīng)用所組成的測試集上,跟蹤覆蓋率引入的額外開銷為程序正常執(zhí)行時間的1.1 倍,比傳統(tǒng)的基于覆蓋率反饋的模糊測試工具快3 倍,并發(fā)現(xiàn)PDFtk 和XnView 程序最新版本中的未知漏洞各1。
本文的組織結(jié)構(gòu)如下:第2 章介紹基于覆蓋率反饋的模糊測試方法的工作流程,剖析性能開銷分布,并提出本文的改進(jìn)思路;第3 章闡述基于稀疏插樁的閉源軟件模糊測試方法的系統(tǒng)設(shè)計(jì)及其中涉及的兩個關(guān)鍵技術(shù)的細(xì)節(jié);第4 章介紹基于稀疏插樁的閉源軟件模糊測試原型SiCsFuzzer 的實(shí)現(xiàn)細(xì)節(jié);第5 章分析實(shí)驗(yàn)數(shù)據(jù);第6 章介紹相關(guān)工作;第7 章總結(jié)全文。
本章將依次介紹基于覆蓋率反饋的模糊測試方法的工作流程,剖析性能開銷分布,并提出改進(jìn)思路。
圖2 展示了傳統(tǒng)的基于覆蓋率反饋的模糊測試(以下簡稱模糊測試)的工作流程,主要包括三個重要的部分:(1)測試用例的生成;(2)執(zhí)行和監(jiān)控目標(biāo)程序/跟蹤覆蓋率;(3)覆蓋率反饋和崩潰結(jié)果分析。在模糊測試運(yùn)行前,對于一個目標(biāo)程序,給定一個目標(biāo)程序和一系列初始種子,當(dāng)模糊測試運(yùn)行時,首先,模糊測試工具將初始種子加入到種子隊(duì)列中,按照特定的順序從種子隊(duì)列中選取一個種子并按照一系列的變異規(guī)則對種子進(jìn)行變異,生成大量新的測試用例,并依次提供給目標(biāo)程序。接著,目標(biāo)程序在執(zhí)行每一個測試用例的過程中,模糊測試工具通過靜態(tài)或動態(tài)插樁的方式對目標(biāo)程序進(jìn)行插樁,來跟蹤代碼覆蓋率。如果一個測試用例能夠探索到目標(biāo)程序新的代碼區(qū)域,比如新的基本塊、新的分支,則該測試用例被認(rèn)為是有價值的,并被保存到初始種子隊(duì)列中,以備將來之用。如果該測試用例是無價值的,則被丟棄??傊?基于覆蓋率反饋的模糊測試工具將目標(biāo)程序執(zhí)行測試用例時得到的覆蓋率作為反饋信息,來指導(dǎo)種子的選擇和變異。以這種方法得到的測試用例更有可能探索到未被發(fā)現(xiàn)的目標(biāo)程序代碼區(qū)域,并觸發(fā)其中的漏洞。因此,準(zhǔn)確的代碼覆蓋率信息是模糊測試工具發(fā)現(xiàn)有價值的種子并快速發(fā)現(xiàn)軟件漏洞的重要依據(jù)[14]。
當(dāng)前主流的模糊測試工具多使用分支覆蓋來計(jì)算測試用例對目標(biāo)程序的代碼覆蓋率。以AFL 為例。AFL 使用一個位圖結(jié)構(gòu)記錄分支覆蓋率,并為每個分支計(jì)算一個哈希值,作為其在位圖中的鍵值。該哈希值由分支的起始基本塊地址與結(jié)束基本塊地址通過移位及異或的方式計(jì)算得到。在不考慮哈希碰撞的前提下,每個分支對應(yīng)的哈希值是唯一的。由于本文重點(diǎn)關(guān)注閉源程序,尤其是以往工作普遍忽視的Windows 平臺上閉源程序模糊測試的效率優(yōu)化,本章的后續(xù)小節(jié)將針對Windows 程序基于分支覆蓋反饋的模糊測試時間開銷進(jìn)行剖析,并根據(jù)剖析結(jié)果闡述本文的效率改進(jìn)方案。
本節(jié)選取 Windows 平臺上規(guī)模在 286KB~19.3MB,類型涉及圖片處理、視頻處理和文件壓縮的9 個目標(biāo)程序,剖析模糊測試的性能構(gòu)成。實(shí)驗(yàn)選擇動態(tài)插樁工具Pin 作為追蹤和反饋分析覆蓋的基礎(chǔ)平臺。
圖3 所示為模糊測試的一次迭代[15]過程中,跟蹤覆蓋率的執(zhí)行時間的占比。通過對8 個目標(biāo)程序的統(tǒng)計(jì)可以看出,跟蹤覆蓋率(對應(yīng)圖2 的第2 部分)的平均執(zhí)行時間占模糊測試的一次迭代總執(zhí)行時間的82%,是模糊測試開銷的主要來源。圖4 進(jìn)一步剖析了跟蹤覆蓋率的時間開銷構(gòu)成。從中可以看出,跟蹤覆蓋率所引入的額外開銷為程序正常執(zhí)行時間的5.25 倍。上述額外的開銷主要來自兩個方面:(1)二進(jìn)制插樁平臺Pin 的開銷,包括啟動和初始化Pin、加載和啟動目標(biāo)程序、在JIT 模式下對目標(biāo)程序代碼進(jìn)行動態(tài)翻譯;(2)插樁開銷,包括用于確定在目標(biāo)程序的哪些位置插入跟蹤代碼的開銷和執(zhí)行跟蹤代碼的開銷。如圖4 所示,上述兩部分的開銷分別占跟蹤測試用例覆蓋率開銷的60%和24%。
圖5 進(jìn)一步剖析了二進(jìn)制插樁平臺Pin 的開銷構(gòu)成。從中可以看出,Pin 的空載執(zhí)行時間(即使用Pin 控制目標(biāo)程序運(yùn)行,但不對目標(biāo)程序進(jìn)行插樁的執(zhí)行時間)主要由三部分構(gòu)成:(1)平臺初始化和目標(biāo)程序加載的開銷;(2)動態(tài)翻譯開銷;(3)執(zhí)行翻譯后的代碼的開銷。本文將平臺初始化和加載目標(biāo)程序,以及對目標(biāo)程序代碼的動態(tài)翻譯這兩部分統(tǒng)稱為“預(yù)熱”階段。從圖5 可以看到,“預(yù)熱”階段的開銷是Pin執(zhí)行時間的主要組成部分,其中初始化和加載過程占Pin 執(zhí)行時間的33%,動態(tài)代碼生成占52%。
上述實(shí)驗(yàn)表明,跟蹤覆蓋率的時間開銷是模糊測試開銷的主要來源(82%),而跟蹤覆蓋率相對于程序正常執(zhí)行所引入的額外開銷主要源于Pin 的“預(yù)熱”階段的開銷以及插樁開銷。如何避免和緩解上述開銷是本文工作的重點(diǎn)。
基于以上實(shí)驗(yàn)分析,我們發(fā)現(xiàn)基于動態(tài)插樁的跟蹤覆蓋率是基于覆蓋率反饋的模糊測試的主要開銷來源。為了降低跟蹤覆蓋率的開銷,應(yīng)該考慮兩個關(guān)鍵因素:(1)插樁的開銷;(2)Pin 的“預(yù)熱”階段的開銷。接下來,本文將分別介紹本文降低跟蹤覆蓋率開銷的關(guān)鍵思路。
降低插樁的開銷。目前主流的基于覆蓋率反饋的模糊測試工具默認(rèn)對目標(biāo)程序中的所有基本塊或分支進(jìn)行插樁。但并非所有插樁位置都是必要的。以圖1 所示的程序片段及其對應(yīng)的控制流圖(CFG)為例,對于該程序,為了跟蹤覆蓋率,只需要對BB2(基本塊2)、BB4 和BB5 進(jìn)行插樁,就可以區(qū)分每一條執(zhí)行路徑,并且能夠推導(dǎo)出準(zhǔn)確的覆蓋率信息。文獻(xiàn)[11]所述的程序優(yōu)化技術(shù)也表明,可以找到目標(biāo)程序所有基本塊/分支的一個子集,該子集中的基本塊/分支可以推導(dǎo)出目標(biāo)程序控制流圖上的所有基本塊/分支。文獻(xiàn)[11]的實(shí)驗(yàn)也表明,對于選擇的8 個C 程序,只需要覆蓋目標(biāo)程序中特定的32%的分支,即可確保100%的分支覆蓋。受到該工作的啟發(fā),本文將插樁對象限定在目標(biāo)程序中的一部分基本塊或分支,這些基本塊或分支運(yùn)行與否無法通過程序中其它基本塊或分支的運(yùn)行信息進(jìn)行推斷。這樣,可以在不犧牲代碼覆蓋率準(zhǔn)確性的情況下減少插樁的開銷?;谠撍枷?我們擴(kuò)展文獻(xiàn)[11]中的工作,以支持閉源軟件代碼分析和動態(tài)插樁。在本文接下來的部分稱其為基于稀疏插樁的覆蓋率跟蹤技術(shù)。
降低Pin 的“預(yù)熱”階段的開銷。由于動態(tài)插樁平臺每執(zhí)行一個測試用例,都需要重新加載目標(biāo)程序并對目標(biāo)程序重新進(jìn)行動態(tài)代碼翻譯,由此帶來冗余的開銷。本文引入“預(yù)熱”優(yōu)化,通過避免對同一程序的重復(fù)加載提高對CodeCache 的復(fù)用率,從而避免重復(fù)加載和重復(fù)翻譯的開銷。
基于2.3 介紹的改進(jìn)方案,本文設(shè)計(jì)實(shí)現(xiàn)了SiCsFuzzer(A Sparse-instrumentation-based Fuzzing Platform for Closed Source Software)。SiCsFuzzer 系統(tǒng)設(shè)計(jì)如圖6 所示。與圖2 所示的傳統(tǒng)的基于覆蓋率反饋的模糊測試相比,SiCsFuzzer 對圖2 中的第2階段,即執(zhí)行和監(jiān)控目標(biāo)程序/跟蹤覆蓋率的階段進(jìn)行了以下兩方面改進(jìn)。
1) 提出基于稀疏插樁的覆蓋率跟蹤技術(shù),根據(jù)支配關(guān)系選擇必要的插樁位置,僅對覆蓋率無法推導(dǎo)的分支或基本塊進(jìn)行稀疏插樁,從而在不影響覆蓋率準(zhǔn)確性的情況下降低插樁的開銷?;舅悸肥窃趯﹂]源目標(biāo)程序進(jìn)行模糊測試之前,通過二進(jìn)制代碼逆向分析和控制流分析,構(gòu)建全局控制流圖以及全局超級塊支配圖,計(jì)算基本塊之間的前/后支配關(guān)系,從而在全局控制流圖上標(biāo)記出能夠區(qū)分不同路徑并且能夠推導(dǎo)出其它基本塊/邊的覆蓋情況的最小基本塊/邊的集合,用于指導(dǎo)動態(tài)二進(jìn)制插樁過程中位置的選擇以及模糊測試一次迭代過程中覆蓋率的推導(dǎo)和重建,由此達(dá)到降低插樁代價,提高模糊測試效率的目的。
2) 提出“預(yù)熱”優(yōu)化,避免二進(jìn)制插樁平臺重復(fù)啟動以及對目標(biāo)程序熱代碼的重復(fù)翻譯的開銷,從而進(jìn)一步提高模糊測試的效率。與圖2 所示的傳統(tǒng)模糊測試流程相比,SiCsFuzzer 將插樁平臺的“預(yù)熱階段”從模糊測試的循環(huán)迭代中分離出來,僅在模糊測試開始時進(jìn)行插樁平臺的初始化及被測目標(biāo)程序的加載。在剔除重復(fù)啟動、加載開銷的同時,使測試用例之間可以共享已被動態(tài)翻譯的代碼,以緩解冗余動態(tài)翻譯的開銷?;舅悸肥窃谀繕?biāo)程序加載后,執(zhí)行到輸入讀取位置之前的某一程序點(diǎn)p 時,保存內(nèi)存快照,當(dāng)目標(biāo)程序讀取輸入并執(zhí)行到指定的結(jié)束位置時,使用保存的快照將程序執(zhí)行狀態(tài)恢復(fù)到程序點(diǎn)p,并讀取新的輸入。
3.2.1 基于稀疏插樁的覆蓋率跟蹤技術(shù)
本節(jié)將介紹基于稀疏插樁的覆蓋率跟蹤技術(shù)的技術(shù)細(xì)節(jié)。
如前所述,稀疏插樁的基礎(chǔ)是標(biāo)記程序控制流圖上滿足具備路徑可區(qū)分性以及覆蓋率的推導(dǎo)重建能力的基本塊。路徑可區(qū)分性是指通過被標(biāo)記的基本塊可以區(qū)分每一條不同的執(zhí)行路徑。也就是說,對于任意兩條不同的路徑,由路徑上被標(biāo)記的基本塊構(gòu)成序列是不同的。如圖1 所示,如果被標(biāo)記的基本塊為BB2、BB4 和BB5,則經(jīng)過BB1→BB3→BB4→BB6 的執(zhí)行路徑將被表示成[BB4],經(jīng)過 BB1→BB3→BB5→BB6 的執(zhí)行路徑被表示為[BB5],經(jīng)過BB1→BB2→BB6 的執(zhí)行路徑被表示為[BB2]。換而言之,只需要對這三個基本塊進(jìn)行插樁,就可以區(qū)分出不同的執(zhí)行路徑。代碼覆蓋率的推導(dǎo)重建能力是指根據(jù)所跟蹤到的被標(biāo)記基本塊,能夠推導(dǎo)出當(dāng)前測試用例所執(zhí)行的路徑上其它未被標(biāo)記的基本塊,即重建整條路徑對應(yīng)的代碼覆蓋率。仍以圖1 為例,如果當(dāng)前測試用例覆蓋了被標(biāo)記的基本塊BB4,則可以推導(dǎo)出當(dāng)前測試用例覆蓋了圖1 代碼片段中的路徑BB1→BB3→BB4→BB6。
本文參照并擴(kuò)展了文獻(xiàn)[11]和文獻(xiàn)[16]所述方法,將稀疏插樁位置的選擇問題轉(zhuǎn)化為全局超級塊支配圖上的頂點(diǎn)(即基本塊)標(biāo)注問題。以圖7 所示的程序代碼為例。采用文獻(xiàn)[11]所述方法可以構(gòu)建出各個函數(shù)的超級塊支配圖。構(gòu)建流程如圖7~10 所示。即:首先構(gòu)建各個函數(shù)的控制流圖(圖7),并基于控制流圖計(jì)算前支配樹和后支配樹(圖8),然后合并前、后支配樹中相同的節(jié)點(diǎn),生成如圖9 所示的基本塊支配圖,再規(guī)約圖中的強(qiáng)聯(lián)通分量并去除冗余邊后,得到圖10 中所示的每個函數(shù)的超級塊支配圖。
超級塊支配圖具備如下性質(zhì):(1)如果一個測試用例覆蓋了超級塊支配圖中的某個超級塊,則該超級塊中包含的所有基本塊也都被覆蓋;(2)如果一個測試用例的執(zhí)行路徑覆蓋了超級塊U 的一個子超級塊V,則該測試用例也覆蓋了U。將上述性質(zhì)應(yīng)用在稀疏插樁的位置選擇上,即只需要標(biāo)記超級塊支配圖上子節(jié)點(diǎn)數(shù)量少于兩個的超級塊中包含的任意一個基本塊,如圖10,對于main 函數(shù),只需要對基本塊2、4、5 插樁,對于display 函數(shù),只需要對基本塊8、10 插樁。通過動態(tài)插樁記錄上述基本塊集合是否被覆蓋,則可推導(dǎo)出同一函數(shù)中其它基本塊的覆蓋情況。
但由于單個函數(shù)的超級塊支配圖并未考慮函數(shù)調(diào)用對支配關(guān)系的影響,基于目標(biāo)程序中每個函數(shù)的超級塊支配圖所標(biāo)記出的最小稀疏插樁位置集合有可能冗余,甚至是不正確的。以圖7 所示的程序?yàn)槔?如果一個測試用例覆蓋了display 函數(shù)的基本塊8,則可以推斷出該測試用例同時覆蓋了main 函數(shù)中的基本塊1,2,6,也就是說,如果對基本塊8 插樁,則不需要對基本塊2 插樁。另外,當(dāng)程序中出現(xiàn)abort、exit 等函數(shù)時,會影響支配關(guān)系的計(jì)算,例如,仍以圖7 為例,若基本塊8 是一個exit 函數(shù),當(dāng)一個測試用例覆蓋了基本塊8,由函數(shù)調(diào)用關(guān)系可知,該測試用例一定覆蓋了基本塊2,根據(jù)圖10 的main 函數(shù)的超級支配圖推導(dǎo)出,如果測試用例覆蓋了基本塊2,則該測試用例一定覆蓋了基本塊1,6。顯然,該測試用例不會覆蓋基本塊6。因此為了對程序的全局控制流進(jìn)行分析,本文實(shí)現(xiàn)了文獻(xiàn)[17]提出的全局超級塊支配圖理論,根據(jù)函數(shù)的調(diào)用關(guān)系,并結(jié)合每個函數(shù)的超級塊支配圖,得到全局超級塊支配圖,如圖11 所示。最后,本文將上述超級塊支配圖的性質(zhì)應(yīng)用到插樁位置選擇,根據(jù)全局超級塊支配圖(圖11),只需要對基本塊4、5、8 和10 進(jìn)行插樁。并且基于全局超級支配圖中,本文實(shí)現(xiàn)了文獻(xiàn)[17]中提出的方法解決了函數(shù)調(diào)用過程中不能正常返回的情況。
算法1.基于稀疏插樁的跟蹤算法.
輸入:目標(biāo)程序和被標(biāo)記的基本塊
輸出:無
如算法1,當(dāng)SiCsFuzzer 在跟蹤覆蓋率時,插樁工具取目標(biāo)程序?qū)⒁獔?zhí)行的一個順序指令序列(記做trace),并判斷trace 中每個基本塊是否被標(biāo)記。如果被標(biāo)記,則在該基本塊前插入跟蹤代碼。跟蹤代碼的功能是維護(hù)一個bitmap 數(shù)據(jù)結(jié)構(gòu)。為了重建代碼覆蓋率,在跟蹤代碼中,我們額外使用另外一個與bitmap 大小相同的bitmapAddr 數(shù)據(jù)結(jié)構(gòu)記錄被覆蓋的基本塊/分支的地址。比如如果覆蓋了基本塊1,則會根據(jù)該基本塊地址計(jì)算得到bitmap 數(shù)組的下標(biāo),相應(yīng)地,以該下標(biāo)的bitmap 的值代表對應(yīng)的基本/分支塊的被覆蓋次數(shù),同時以該下標(biāo)的bitmapAddr 的值為該基本塊/分支的地址。
如前所述,基于稀疏插樁可以推導(dǎo)出其它未被插樁的基本塊及邊的覆蓋率。算法2 為覆蓋率重建算法。雖然覆蓋率重建對本文方法而言并不是必須的,但考慮到與主流模糊測試工具之間的兼容性,重建覆蓋率可以更好的輔助主流模糊測試工具進(jìn)行變異策略的選擇。比如,AFL 會根據(jù)當(dāng)前測試用例覆蓋的基本塊或邊的數(shù)量選擇變異次數(shù),LibFuzzer 會給予覆蓋了更多新的基本塊的種子更高的優(yōu)先級。在重建覆蓋率的基礎(chǔ)上能夠更加直觀的計(jì)算出這些信息,從而提高本文方法的適用面。
算法2.代碼覆蓋率重建算法
輸入:bitmapAddr 和marked。
輸出:代碼覆蓋率
如算法2 所示,根據(jù)bitmapAddr 和marked 即可重建覆蓋率。根據(jù)目標(biāo)程序的全局超級塊支配圖,我們得到被標(biāo)記的基本塊。對于每一個標(biāo)記的基本塊,我們遞歸地記錄它的所有父節(jié)點(diǎn),也就是與該標(biāo)記的基本塊同時被覆蓋的未被標(biāo)記的基本塊,記為marked,以圖11 為例,其數(shù)據(jù)結(jié)構(gòu)為:{4:1,3,6;5:1,3,6;8:1,2,6,7,9,11;10:1,2,6,7,9,11}。另外,bitmapAddr 為算法1 中提到的記錄動態(tài)跟蹤時被覆蓋的被標(biāo)記的基本塊/分支的地址。在算法2 中,我們使用一個哈希表bbl_cover 存儲當(dāng)前測試用例覆蓋到的基本塊/分支的地址,首先遍歷bitmapAddr 中的每一個值,如果該值是當(dāng)前測試用例覆蓋的基本塊/分支地址,則覆蓋率增加1,并存儲到哈希表中(第5~7 行),然后遍歷該基本塊/分支能推導(dǎo)出的其他基本塊/分支,同樣如果該基本塊/分支的地址不在bbl_cover 哈希表中,則將其存儲到哈希表中,并將覆蓋率增加1(第8~13)。最終,我們通過稀疏插樁的覆蓋率跟蹤方法,實(shí)現(xiàn)了代碼覆蓋率重建。
3.2.2 “預(yù)熱”優(yōu)化技術(shù)
本節(jié)介紹“預(yù)熱”優(yōu)化技術(shù)的細(xì)節(jié)。以Pin 為代表的二進(jìn)制動態(tài)插樁平臺在執(zhí)行到目標(biāo)程序的一個新的基本塊時,會將該基本塊轉(zhuǎn)換成Pin 的可執(zhí)行指令序列存儲到CodeCache 當(dāng)中; 如果CodeCache 當(dāng)中已經(jīng)保存了該基本塊對應(yīng)的指令序列,則無需再次翻譯。一旦插樁平臺重啟或程序重新載入,則CodeCache 將被清空?!邦A(yù)熱”優(yōu)化的基本思路是通過避免對同一程序的重復(fù)加載提高對CodeCache 的復(fù)用率,從而避免重復(fù)加載和重復(fù)翻譯的開銷。該優(yōu)化得以實(shí)施的基礎(chǔ)是能夠在目標(biāo)程序加載后,執(zhí)行到輸入讀取位置之前的某一程序點(diǎn)p 時,保存內(nèi)存快照,當(dāng)目標(biāo)程序讀取輸入并執(zhí)行到指定的結(jié)束位置時,使用保存的快照將程序執(zhí)行狀態(tài)恢復(fù)到程序點(diǎn)p,并讀取新的輸入。
SiCsFuzzer 默認(rèn)選擇main 函數(shù)的開始位置作為快照的生成和恢復(fù)位置??煺毡4娴膬?nèi)容包括寄存器狀態(tài)、內(nèi)存狀態(tài)。如果測試用例是通過main 函數(shù)的參數(shù)讀入的,則額外監(jiān)控測試用例的存放位置L,并在每次恢復(fù)快照的同時將從種子隊(duì)列中讀取的新輸入復(fù)制到L。SiCsFuzzer 默認(rèn)選擇的結(jié)束位置為main 函數(shù)的結(jié)束位置;也可以選擇在調(diào)用exit 函數(shù)的位置;對于有明顯輸出信息的程序,可以通過動態(tài)調(diào)試并觀察輸出狀態(tài)選擇出結(jié)束位置。
這樣,我們可以只關(guān)注目標(biāo)程序的代碼部分,而不是花費(fèi)大量的時間用于初始化插樁工具和加載目標(biāo)程序。同時,“預(yù)熱”優(yōu)化可以充分發(fā)揮插樁工具的“Trace Linking”優(yōu)化機(jī)制,避免重復(fù)將相同的trace 生成新的指令代碼的開銷,使得之前生成的指令代碼存儲在CodeCache 中被執(zhí)行所有測試用例時共享。并且隨著模糊測試的進(jìn)行,被覆蓋到的新的trace 越來越少,其中大多數(shù)已經(jīng)存儲在CodeCache中,極大地降低了不必要的開銷。
本文基于上述設(shè)計(jì),在現(xiàn)有模糊測試平臺Puzzer[18]上實(shí)現(xiàn)了基于稀疏插樁的閉源軟件模糊測試原型SiCsFuzzer。本章將介紹原型中兩個關(guān)鍵技術(shù)的實(shí)現(xiàn)細(xì)節(jié)。
基于稀疏插樁的覆蓋率跟蹤實(shí)現(xiàn)分為基本塊標(biāo)記、插樁追蹤和覆蓋率重建三部分。
其中,基本塊標(biāo)記實(shí)現(xiàn)為IDA Pro 插件的形式,利用IDA Pro 提供的接口提取目標(biāo)程序的函數(shù)調(diào)用關(guān)系和每個函數(shù)的程序控制流圖;然后使用Boost 圖形庫函數(shù)建立程序的前支配樹和后支配樹。如果模糊測試使用基本塊覆蓋,則計(jì)算基本塊的前支配樹和后支配樹,如果模糊測試使用分支覆蓋,則計(jì)算分支的前支配樹和后支配樹;合并前、后支配樹中相同的節(jié)點(diǎn),構(gòu)成圖的形式,規(guī)約圖中的強(qiáng)聯(lián)通分量并去除冗余邊后,得到每個函數(shù)對應(yīng)的超級塊支配圖;最后根據(jù)函數(shù)調(diào)用關(guān)系合并每個函數(shù)的超級塊支配圖,得到全局超級塊支配圖。根據(jù)超級塊支配圖的性質(zhì)得到需要插樁的基本塊/分支和對應(yīng)的未插樁的基本塊/分支,記錄在文件中。該文件將在模糊測試開始時由模糊測試進(jìn)程加載到共享內(nèi)存。
如前所述,基本塊標(biāo)記的結(jié)果將用于指導(dǎo)模糊測試過程中的動態(tài)二進(jìn)制插樁位置的選擇,但插樁平臺Pin 與IDA Pro 生成的控制流圖對基本塊的劃分存在不一致的情況。圖12 所示為IDA Pro 生成的用于計(jì)算全局超級支配圖的控制流圖片段。根據(jù)支配關(guān)系,SiCsFuzzer 會將標(biāo)記圖12 中的基本塊2 和基本塊3 為需要動態(tài)插樁的基本塊。但在覆蓋率跟蹤過程中,如果執(zhí)行的路徑為基本塊1的假分支,Pin會將基本塊3 劃入基本塊2,進(jìn)而造成基本塊3 的覆蓋被重復(fù)計(jì)算。因此本文從IDA Pro 提取目標(biāo)程序的程序控制流圖時,對以mov 指令結(jié)束的基本塊進(jìn)行標(biāo)記,在分析覆蓋率時,遇到帶有標(biāo)記的基本塊時,則將其對應(yīng)覆蓋率減一,以調(diào)整覆蓋率計(jì)算。
對于本文工作而言,理想的方式是使用靜態(tài)重寫,其開銷較低,但是Windows 平臺上的二進(jìn)制重寫工具如Dyninst、McSema[19]等工具魯棒性較差。采用二進(jìn)制動態(tài)跟蹤代碼覆蓋率雖然開銷較高,但確是目前最具普適性的方法。因此本文實(shí)現(xiàn)了“預(yù)熱”優(yōu)化來降低二進(jìn)制動態(tài)跟蹤代碼覆蓋率的開銷。
為實(shí)現(xiàn)“預(yù)熱”優(yōu)化,我們設(shè)計(jì)并實(shí)現(xiàn)了fuzzer進(jìn)程和instrumentor 進(jìn)程之間的通信。如圖13,一共有以下四個主要對象:
1) fuzzer:fuzzer 是基于覆蓋率反饋的模糊測試的主進(jìn)程,主要負(fù)責(zé)初始化種子隊(duì)列、生成測試用例、分析代碼覆蓋率以及啟動instrumentor 進(jìn)程。
2) instrumentor:instrumentor 是由fuzzer 開啟的子進(jìn)程,該進(jìn)程是插樁進(jìn)程,主要負(fù)責(zé)控制目標(biāo)程序的執(zhí)行,并分析在目標(biāo)程序的哪些位置插入跟蹤代碼。
3)目標(biāo)程序:指的是目標(biāo)二進(jìn)制程序,由instrumentor 控制執(zhí)行。
4) 共享內(nèi)存:SiCsFuzzer 使用共享內(nèi)存存儲代碼覆蓋信息。在模糊測試運(yùn)行過程中,instrumentor首先初始化該共享內(nèi)存,目標(biāo)程序在插樁工具的控制下執(zhí)行并將覆蓋率信息記錄到共享內(nèi)存中,當(dāng)目標(biāo)程序運(yùn)行到結(jié)束位置后,fuzzer 進(jìn)程開始讀取共享內(nèi)存的數(shù)據(jù)并分析代碼覆蓋信息。
fuzzer 進(jìn)程與instrumentor 進(jìn)程的交互如下:
1) fuzzer 選擇一個測試用例并開始新一輪的模糊測試。首先fuzzer 向instrumentor 進(jìn)程發(fā)送一個begin信號。表示instrumentor 可以開始運(yùn)行。
2) instrumentor 收到begin信號后,首先對共享內(nèi)存做相關(guān)初始化工作,然后控制目標(biāo)二進(jìn)制程序開始運(yùn)行。同時保存目標(biāo)程序當(dāng)前的上下文信息。
3) 目標(biāo)二進(jìn)制程序首先讀取測試用例,并運(yùn)行,并在instrumentor 的控制下,將覆蓋率信息記錄到共享內(nèi)存中。
4) instrumentor 監(jiān)控目標(biāo)二進(jìn)制程序運(yùn)行到指定結(jié)束指令位置后,instrumentor 發(fā)送end信號給fuzzer進(jìn)程,表示目標(biāo)程序運(yùn)行結(jié)束,同時instrumentor 恢復(fù)2)中保存的上下文,并返回到程序開始指令位置。
5) fuzzer 接收到end信號后,開始讀取共享內(nèi)存的數(shù)據(jù)并分析程序運(yùn)行時的覆蓋率等信息。
6) 返回到1)并重復(fù)。
本文實(shí)現(xiàn)的“預(yù)熱”優(yōu)化基于插樁工具Pin。為了實(shí)現(xiàn)在指定的開始指令位置獲取目標(biāo)程序的上下文信息,并在結(jié)束指令位置恢復(fù)保存的上下文信息,傳統(tǒng)的方法可使用Pin 進(jìn)行指令插樁,記錄寫內(nèi)存的
指令操作和內(nèi)存的狀態(tài),當(dāng)程序運(yùn)行到結(jié)束指令的位置時,恢復(fù)內(nèi)存狀態(tài)。但是該方法開銷很大,而本文采用的方法是在程序運(yùn)行到開始位置時,使用PyDbg 獲取目標(biāo)程序的上下文信息,包括寄存器狀態(tài),內(nèi)存狀態(tài),在程序運(yùn)行到結(jié)束指令的位置時,恢復(fù)保存的寄存器、內(nèi)存狀態(tài)。
AFL 實(shí)現(xiàn)了fork-server 機(jī)制[20]來避免“預(yù)熱”階段的開銷,但是fork-server 的實(shí)現(xiàn)思想主要依賴Linux 的系統(tǒng)函數(shù)fork,在每一輪模糊測試時,使用fork函數(shù)復(fù)制一個新的目標(biāo)程序進(jìn)程并執(zhí)行,避免了載入目標(biāo)程序的重復(fù)性工作。fork函數(shù)使用寫時復(fù)制(copy on write)的機(jī)制,開銷非常低,但是 Windows 平臺沒有類似的函數(shù),因此在Windows 平臺實(shí)現(xiàn)fork-server 機(jī)制具有很大的挑戰(zhàn)。
本文在現(xiàn)有模糊測試平臺Puzzer[18]上實(shí)現(xiàn)了基于稀疏插樁的閉源軟件模糊測試原型SiCsFuzzer。本章將SiCsFuzzer 與Puzzer 和WinAFL 進(jìn)行對比,本文的實(shí)驗(yàn)主要回答了以下5 個問題:
Q1:采用稀疏插樁的覆蓋率跟蹤技術(shù)使需要插樁的基本塊或分支的數(shù)量降低了多少?
Q2:基于稀疏插樁的覆蓋率跟蹤技術(shù)對模糊測試的效率改進(jìn)情況如何?
Q3:“預(yù)熱”優(yōu)化對模糊測試效率的改進(jìn)情況如何?
Q4:在執(zhí)行時間相同的前提下,SiCsFuzzer 是否能夠比Puzzer 達(dá)到更高的覆蓋率?
Q5:SiCsFuzzer與WinAFL的效能對比結(jié)果如何?
本章以表1 所示的9 個Windows 平臺閉源二進(jìn)制軟件作為實(shí)驗(yàn)分析的目標(biāo)程序。程序涉及圖片處理、視頻處理、文件壓縮、加解密和文檔處理5 類,軟件規(guī)模從 266KB~19.3MB 不等。實(shí)驗(yàn)均運(yùn)行在Windows 7-32、2 核Intel i7-87700 處理器的虛擬機(jī)上。
表1 二進(jìn)制目標(biāo)程序信息Table 1 Binary target program information
為了避免隨機(jī)因素的影響,確保對比實(shí)驗(yàn)的公平性,我們先使用Puzzer 工具分別對每一個目標(biāo)程序測試了24h,將變異生成的測試用例保存下來,作為對比實(shí)驗(yàn)的測試用例集。再對工具 Puzzer 和SiCsFuzzer 在不執(zhí)行變異的前提下,分別執(zhí)行上述測試用例集,并記錄每個測試用例跟蹤覆蓋率的時間開銷。在統(tǒng)計(jì)和對比過程中,使用“切尾均值降噪”的方法[21],刪除了執(zhí)行時間中最高和最低的30%,以減少其他因素對程序執(zhí)行時間的影響,更好的展示中位數(shù)的趨勢。最后,以目標(biāo)程序正常執(zhí)行所需的時間為“基準(zhǔn)”,將所有執(zhí)行時間換算為相對于基準(zhǔn)時間的“相對執(zhí)行時間”。
Q1:圖14 所示為SiCsFuzzer 對表1 所示的9 個程序采用稀疏插樁后,每個目標(biāo)程序中需要插樁的分支數(shù)量占傳統(tǒng)方法(對所有基本塊/分支插樁)插樁數(shù)量的比例。與傳統(tǒng)模糊測試默認(rèn)對所有基本塊插樁的方法相比,目標(biāo)程序需要插樁的分支數(shù)量平均減少了59.39%,最高了減少了67%。
Q2:接下來,本文分析了采用基于稀疏插樁跟蹤技術(shù)后SiCsFuzzer 的性能。本文記錄了對于每一個目標(biāo)程序,采用基于稀疏插樁的覆蓋率跟蹤技術(shù)的SiCsFuzzer 和改進(jìn)前的模糊測試工具Puzzer 的相對執(zhí)行時間。如圖15 所示,采用基于稀疏插樁跟蹤技術(shù)后,SiCsFuzzer 的性能相比Puzzer 提升了16.8%,圖中的折線代表將目標(biāo)程序運(yùn)行在二進(jìn)制插樁平臺Pin 上,但不對目標(biāo)程序進(jìn)行插樁的時間開銷,相對于程序正常執(zhí)行時間開銷進(jìn)行標(biāo)準(zhǔn)化后的比值,簡稱“Pin 空載”的相對執(zhí)行時間。該折線也是采用稀疏插樁跟蹤技術(shù)后,SiCsFuzzer 能達(dá)到的性能上限。實(shí)驗(yàn)結(jié)果表明,對于所有被測目標(biāo)程序,Pin 空載的平均相對執(zhí)行時間為6.1,Puzzer 的平均相對執(zhí)行時間為8.3,而SiCsFuzzer 的平均相對執(zhí)行時間為6.9。換而言之,SiCsFuzzer 相對于對所有基本塊進(jìn)行插樁的Puzzer,消除了將近64%的插樁開銷(計(jì)算方法:(8.3–6.9)/(8.3–6.1))。
對于flashplayer這類需要交互的目標(biāo)程序,程序解析完文件后,無法自動結(jié)束,因此對于程序正常運(yùn)行時間、Pin 空載時間和模糊測試時間的獲取方法如下:本文將其開始顯示視頻內(nèi)容的時間其作為結(jié)束時間,然后根據(jù)統(tǒng)計(jì)將它的正常執(zhí)行時間設(shè)為2 s,Pin 空載時間設(shè)為3 s。在執(zhí)行模糊測試之前,通過逆向確定其解析文件結(jié)束的位置,在該位置插入結(jié)束運(yùn)行的代碼,以此方法獲取交互類軟件的執(zhí)行模糊測試的時間。
Q3:圖 16 所示為使用“預(yù)熱”優(yōu)化后的SiCsFuzzer 與Puzzer 的性能差異。實(shí)驗(yàn)結(jié)果表明,加入“預(yù)熱”優(yōu)化后,SiCsFuzzer 的平均相對執(zhí)行時間是2.1,也就是說SiCsFuzzer 的平均執(zhí)行時間是程序正常運(yùn)行時間的2.1 倍。而改進(jìn)前工具Puzzer 的平均相對執(zhí)行時間是8.3。換而言之,SiCsFuzzer 的模糊測試效率平均比Puzzer 快3 倍(1.24~8.25 倍)。
對于選定的9 個目標(biāo)軟件,gpg和flashplayer的模糊測試效率甚至分別優(yōu)于這兩個程序的正常執(zhí)行效率。這是由于引入“預(yù)熱”優(yōu)化后,和目標(biāo)程序正常運(yùn)行相比,SiCsFuzzer 避免了加載和啟動目標(biāo)程序的開銷,并且只執(zhí)行目標(biāo)程序中選定的部分,即從讀取輸入到程序退出之間的部分。尤其是對于flashplayer這類圖形界面軟件,加載和啟動目標(biāo)程序的開銷本身在程序正常執(zhí)行時間中占比較高,更能發(fā)揮“預(yù)熱”優(yōu)化的優(yōu)勢。
Q4:圖17 所示為相同時間(24 h)內(nèi),SiCsFuzzer與Puzzer 的覆蓋率增長情況。根據(jù)實(shí)驗(yàn)結(jié)果統(tǒng)計(jì),SiCsFuzzer 平均只用了6.3 h 就達(dá)到了Puzzer 執(zhí)行24 h 才能達(dá)到的覆蓋率。
對于部分目標(biāo)軟件(magick,Rar,pdf2html,iview),SiCsFuzzer 在24 h 內(nèi)達(dá)到的覆蓋率明顯高于Puzzer。對于7zip和gpg兩個目標(biāo)軟件,在模糊測試執(zhí)行一段時間后,覆蓋率不再變化。由此可見,在達(dá)到相同覆蓋率時,SiCsFuzzer 所需時間明顯少于Puzzer,不過為了更好的提升模糊測試的效率,仍需要結(jié)合通過生成有價值的種子來提升覆蓋率的方法。
Q5:WinAFL是AFL的擴(kuò)展版本,其使用二進(jìn)制插樁工具 DynamoRIO[22]跟蹤覆蓋率,以支持Windows 平臺上目標(biāo)軟件的模糊測試使用。此外,WinAFL 默認(rèn)開啟了與SiCsFuzzer 類似的“預(yù)熱”優(yōu)化機(jī)制,但只保存并恢復(fù)了指定程序點(diǎn)的寄存器狀態(tài),故在實(shí)驗(yàn)過程,對本章選定的9 個目標(biāo)程序,只能成功測試其中的4 個目標(biāo)程序。圖18 對比了SiCsFuzzer 和WinAFL 相對執(zhí)行時間。實(shí)驗(yàn)結(jié)果表明,對于WinAFL 能成功測試的4 個目標(biāo)軟件,其模糊測試效率平均比SiCsFuzzer 快18%。但上述性能差異主要來自二進(jìn)制插樁平臺Pin 和DynamoRIO 之間的性能差異[23],且Pin 更加穩(wěn)定,能夠測試更多的目標(biāo)程序[13]。
綜上,SiCsFuzzer 在Windows 平臺9 個規(guī)模在286KB~19.3MB,類型涉及圖片處理、視頻處理、文件壓縮、加密和文檔處理的應(yīng)用所組成的測試集上,平均只需對40.61%的分支進(jìn)行插樁,模糊測試引入的額外時間開銷為程序正常運(yùn)行時間的1.1 倍,執(zhí)行效率比傳統(tǒng)的基于覆蓋率反饋的模糊測試方法快3倍。
此外,借助SiCsFuzzer,新發(fā)現(xiàn)了PDFtk 最新版本(v2.02)和XnView 最新版本系列軟件Nconvert(v7.32)中的未知漏洞各1個;前者是在文件解析過程中由于棧溢出導(dǎo)致的程序崩潰;后者是由于內(nèi)存破壞導(dǎo)致軟件崩潰,已獲廠商確認(rèn)。上述未知漏洞的發(fā)現(xiàn)也得益于模糊測試效率的提升。以XnView 為例,SiCsFuzzer 運(yùn)行6.5 h 后觸發(fā)該漏洞,漏洞觸發(fā)時的覆蓋率為9.97%;而Puzzer 在運(yùn)行到相同時間時的覆蓋率為8.2%,再運(yùn)行18 h 后,才觸發(fā)了漏洞,觸發(fā)漏洞時的覆蓋率為9.99%。對于PDFtk,SiCsFuzzer 運(yùn)行4 h 后觸發(fā)該漏洞,漏洞觸發(fā)時覆蓋率為26.15%;而Puzzer 在運(yùn)行到相同時間時的覆蓋率為26.03%,再運(yùn)行9 h 后,才觸發(fā)了漏洞,觸發(fā)漏洞時的覆蓋率為26.18%。
當(dāng)前針對提升模糊測試性能提升的相關(guān)工作可以分為兩大類[15]:(1)通過生成有價值的種子降低模糊測試整體的開銷。因?yàn)橛袃r值的種子更有可能觸發(fā)軟件漏洞,也就是通過生成有價值的種子來縮短發(fā)現(xiàn)軟件漏洞的時間,從而降低模糊測試整體的開銷;(2)通過降低執(zhí)行單個測試用例的時間來降低模糊測試的開銷。也就是說,在給定時間內(nèi),模糊測試能夠測試更多個測試用例,達(dá)到更高的代碼覆蓋率。目前現(xiàn)有的工作中,大多屬于第一種。而本文通過降低執(zhí)行單個測試用例時跟蹤測試用例代碼覆蓋率的開銷,屬于第二種方法。兩個方向的相關(guān)工作如下:
生成有價值的種子。生成有價值的種子主要與以下因素有關(guān):(1)種子選擇策略;(2)種子變異策略。一些研究[3-4]表明種子選擇策略對模糊測試非常重要,像libFuzzer[24]對提升代碼覆蓋率的種子給予更大的優(yōu)先權(quán),因?yàn)檫@樣的種子更有可能探索到目標(biāo)程序未被執(zhí)行到的代碼區(qū)域,VUzzer[12]對執(zhí)行路徑更深的種子給予更大的優(yōu)先權(quán),因?yàn)閂Uzzer 旨在發(fā)現(xiàn)更深的路徑??傊?良好的種子選擇策略能夠加速模糊測試工具探索到目標(biāo)程序不同的代碼區(qū)域和發(fā)現(xiàn)軟件漏洞。
AFL 和libFuzzer 等模糊測試工具采用一組確定性和隨機(jī)的變異策略對種子進(jìn)行變異,生成大量的測試用例,這種方法雖然簡單,但是在模糊測試進(jìn)行一段時間后,很難提升代碼覆蓋率。一些更加先進(jìn)的模糊測試工具試圖使用一種更聰明的方法對種子進(jìn)行變異。Driller[25]將模糊測試與符號執(zhí)行結(jié)合在一起,當(dāng)覆蓋率不再提升時,通過符號執(zhí)行計(jì)算有效輸入。但是,由于符號執(zhí)行不適用于實(shí)際程序,因此應(yīng)用符號執(zhí)行來輔助測試大型軟件是不切實(shí)際的。VUzzer 使用控制流和數(shù)據(jù)流特征來識別要變異的字節(jié)。Skyfire[26]利用大量現(xiàn)有樣本中的知識來生成分布良好的種子輸入。
盡管這些模糊工具可以有效地生成更多有價值的種子,但它們?nèi)匀幻媾R著執(zhí)行所有測試用例的開銷。因此,降低執(zhí)行單個測試用例的開銷,也會帶來極大的好處。
降低執(zhí)行單個測試用例的開銷。文獻(xiàn)[9]就降低執(zhí)行單個測試用例的開銷提出了改進(jìn)方案UnTracer,其在跟蹤代碼覆蓋率之前,先通過一輪輕量級的檢測,判斷變異生成的測試用例是否是有價值的測試用例,即是否能夠觸發(fā)新的基本塊或分支。對于沒有價值的測試用例,直接丟棄;而針對有價值的測試用例,UnTracer 仍然面對繼續(xù)跟蹤其覆蓋率的開銷;近期的相關(guān)工作[10]提出了二進(jìn)制重寫技術(shù)RetroWrite,其速度是QEMU 的4.5 倍,但該工作針對的目標(biāo)程序?yàn)?4 位的地址無關(guān)代碼的Linux 閉源程序。并且,文獻(xiàn)[9]的統(tǒng)計(jì)顯示,QEMU 引入的額外開銷約為目標(biāo)程序正常執(zhí)行時間的10 倍,因此得出,RetroWrite 的執(zhí)行速度約為程序正常運(yùn)行的2.4 倍,而本文的SiCsFuzzer 的執(zhí)行速度為程序正常運(yùn)行的2.1 倍。
文獻(xiàn)[15]提出為模糊測試工具設(shè)計(jì)三個操作原語,降低了在多核計(jì)算機(jī)進(jìn)行模糊測試的性能開銷。還有一些其他工作,提出使用硬件輔助的方法,如kAFL[7]、PTfuzz[8]和honggFuzz[2]使用Intel Processor Tracking(IPT)[27]進(jìn)行跟蹤測試用例的代碼覆蓋。但是這些工作對硬件有一定的要求,不能較好地應(yīng)用于大部分主流的應(yīng)用平臺。另外,文獻(xiàn)[28]提出了一種插樁方法,使用程序控制流圖的前支配樹信息來減少插樁位置的數(shù)量,但是由于只使用了前支配樹信息,缺乏后支配樹信息,所以這種插樁策略仍存在冗余。文獻(xiàn)[29]提出結(jié)合前支配樹和后支配樹信息來減少插樁位置的數(shù)量,但是該工作是針對具有源代碼的目標(biāo)程序。針對具有源代碼的目標(biāo)程序,這些工作根據(jù)程序的控制流圖信息,通過前支配樹信息或結(jié)合后支配樹信息得到每個函數(shù)需要插樁的基本塊,然后通過靜態(tài)插樁的方法即可對目標(biāo)程序進(jìn)行測試。而本文是針對閉源程序(尤其是Windows 平臺),閉源程序的插樁多使用動態(tài)插樁。因此本文詳細(xì)地剖析了動態(tài)跟蹤覆蓋率的開銷,基于分析結(jié)果,本文提出稀疏插樁的覆蓋率跟蹤技術(shù)和“預(yù)熱”優(yōu)化。本文提出的基于稀疏插樁的覆蓋率跟蹤技術(shù)對全局控制流進(jìn)行分析,處理了程序中含有exit、abort 等函數(shù)的情況,根據(jù)動態(tài)插樁的特點(diǎn),設(shè)計(jì)并實(shí)現(xiàn)了覆蓋率重建算法。結(jié)合“預(yù)熱”優(yōu)化,更有效的降低了模糊測試的開銷。
本文提出了一種面向閉源程序的高效模糊測試方法。該方法借助基于稀疏插樁的覆蓋率跟蹤以及針對流行的動態(tài)二進(jìn)制插樁工具(例如Pin)的“預(yù)熱”優(yōu)化機(jī)制,降低基于覆蓋率反饋的模糊測試過程中跟蹤覆蓋率的開銷,提高模糊測試效率?;谠摻鉀Q方案實(shí)現(xiàn)的原型工具SiCsFuzzer,在Windows 平臺9個規(guī)模在286KB~19.3MB、類型涉及圖片處理、視頻處理、文件壓縮、加密和文檔處理等類型應(yīng)用所組成的測試集上,引入的額外時間開銷為程序正常運(yùn)行時間的1.1 倍,執(zhí)行效率比傳統(tǒng)的基于覆蓋率反饋的模糊測試工具快3倍。此外,使用SiCsFuzzer,還發(fā)現(xiàn)了PDFtk和XnView軟件最新版本中的未知漏洞各1 個。
本文的下一步工作包括以下兩個方面。首先,本文提出的基于稀疏插樁的覆蓋率跟蹤技術(shù)需要借助靜態(tài)分析技術(shù)得到目標(biāo)程序中各基本塊間的支配關(guān)系。靜態(tài)分析的精度,尤其是對間接跳轉(zhuǎn)目標(biāo)的識別精度,將直接影響超級支配圖的精度,甚至造成有價值的種子被丟棄。在后續(xù)工作中,將嘗試通過模糊測試過程中記錄的動態(tài)執(zhí)行軌跡,反過來指導(dǎo)并補(bǔ)充靜態(tài)分析未識別出的間接跳轉(zhuǎn)目標(biāo),完善控制流圖。此外,目前有很多工作[30-32]致力于提升靜態(tài)分析技術(shù)的準(zhǔn)確性,隨著該技術(shù)的完善,本文提出的算法也將得到更好的效果。其次,如4.1 節(jié)所述,本文方法在實(shí)現(xiàn)過程中遇到動態(tài)插樁工具Pin 與靜態(tài)分析工具IDA Pro 生成的控制流圖對基本塊的劃分不一致的情況,雖然就目前分析的實(shí)例而言,識別以mov 指令結(jié)尾的基本塊能在一定程度緩解該問題,但仍然不夠精準(zhǔn)。在后續(xù)的工作中,將通過對更多實(shí)例的分析總結(jié),歸納并識別出所有導(dǎo)致基本塊劃分不一致的情況,提高覆蓋率計(jì)算的精度。