劉思琦,王一鳴
(北京交通大學(xué) 計算機與信息技術(shù)學(xué)院,北京100044)
在時間維度上,漏洞都會經(jīng)歷產(chǎn)生、發(fā)現(xiàn)、公開和消亡等過程,不同的時間段,漏洞有不同的名稱和表現(xiàn)形式。1day漏洞是指在廠商發(fā)布安全補丁之后,大部分用戶還未打補丁的漏洞,此類漏洞依然具有可利用性。在各類型軟件中,許多漏洞的壽命超過12個月,針對此類漏洞的通用應(yīng)用修復(fù)辦法是使用代碼匹配[1],但是往往通過補丁做出的修補都是一些細微的變化,這會導(dǎo)致許多代碼匹配的方法不精確且不通用,造成結(jié)果高誤報。
基于以上問題和背景,本文設(shè)計了一種由源代碼到二進制的基于補丁特性的漏洞掃描模型Bin-Scan;基于現(xiàn)存算法設(shè)計了一種源代碼漏洞檢測算法,構(gòu)建了基于公開網(wǎng)站的漏洞信息數(shù)據(jù)庫,并得到了基于補丁源代碼檢測漏洞情況的初步結(jié)果;提出了一種利用補丁前后文件形成漏洞庫,基于CFG和代碼相似性的二進制漏洞檢測算法;實現(xiàn)了通過識別二進制文件補丁是否存在以檢測漏洞。
漏洞掃描包括靜態(tài)漏洞掃描和動態(tài)漏洞掃描,靜態(tài)漏洞掃描主要包括已知漏洞掃描和未知漏洞掃描,已知漏洞掃描又根據(jù)研究對象不同分為源代碼掃描和二進制代碼掃描。為提高漏洞檢測的準(zhǔn)確率并降低誤檢率,確保檢全率與正確率的平衡,源代碼漏洞掃描檢測代碼復(fù)用,主要使用方法有利用正則化窗口匹配的Redebug[2],利用簽名匹配的VUDDY[3]、VulPecker[4]以及基于深度學(xué)習(xí)的漏洞檢測系統(tǒng)VulDeePecker[5]等。關(guān)于二進制代碼的漏洞掃描方法包括bug簽名、樹編輯距離、控制流圖和過程間控制流分析等。2016年CCS發(fā)布Genius[6],它將CFG轉(zhuǎn)換為數(shù)字特征向量用哈希技術(shù)實現(xiàn)搜索,可針對不同平臺的二進制代碼檢測,但檢測精度卻只有28%;2017年CCS發(fā)布Gemini[7],其在Genius的基礎(chǔ)上進行改進,使用神經(jīng)網(wǎng)絡(luò)嵌入算法大大減少嵌入生成時間和培訓(xùn)時間;2016年NDSS發(fā)布discovRE[8],使用控制流圖計算相似度,識別其他架構(gòu)的類似函數(shù),可跨架構(gòu)進行二進制代碼的漏洞檢索;2019年S&P提出Asm2Vec[9],可通過提取出函數(shù)特征之間的關(guān)系設(shè)計針對匯編代碼語法以及控制流圖的表示學(xué)習(xí)模型。
漏洞掃描中,一方面,設(shè)計的漏洞檢測方法需要無視與漏洞無關(guān)的代碼更改部分,如函數(shù)升級和編譯器優(yōu)化的情況;另一方面,檢測方法也需要足夠的檢測精度才能過濾掉那些已經(jīng)進行過補丁修復(fù)而不存在漏洞的函數(shù)?;谏鲜銮闆r,這就需要利用補丁特征檢測代碼中存在的已知漏洞。將源代碼漏洞檢測與二進制掃描結(jié)合在一起,可以將源代碼級的漏洞檢查能力應(yīng)用到二進制代碼中。2014年USENIX發(fā)布BLEX[10],這是最先利用基于補丁的漏洞產(chǎn)生工具。2018年USENIX發(fā)布FIBER[11],其從補丁生成二進制簽名,盡可能保留源碼補丁信息查找二進制漏洞。
1.2.1 例子
在之前的實驗中筆者發(fā)現(xiàn)源代碼檢測經(jīng)常會出現(xiàn)諸如模糊匹配及單純結(jié)構(gòu)匹配不精確的問題,如圖1的CVE-2013-2852,它的目的是解決Linux內(nèi)核中利用格式字符串說明符獲取用戶訪問權(quán)限的漏洞,在文件中體現(xiàn)為main.c文件的b43_request_firmware函數(shù)存在格式字符串漏洞,補丁文件在代碼層面只是增加了參數(shù)的類型,改動非常細微,若單純結(jié)構(gòu)匹配發(fā)現(xiàn)不了此漏洞。
圖1 CVE-2013-2852的補丁
1.2.2 代碼重用
代碼重用在軟件中非常普遍,所以對代碼進行檢測其實很多情況下都是對重用的代碼進行漏洞的檢測。
如圖2所示,如果S1部分包含漏洞,S部分是S1的代碼重用,那么代碼文件P1包含漏洞,P也包含漏洞。
圖2 檢測代碼重用漏洞的定義
二進制檢測時無法使用類似源代碼的補丁文本匹配,二進制差異性分析可以找到待檢測二進制文件P與P1、P2哪個更相似,來判斷是否有漏洞。
圖3為BinScan方法概述,以源代碼、CVE漏洞的補丁diff文件、二進制待檢測代碼為輸入,輸出為二進制代碼是否含有CVE。此方法的核心有三部分:基于補丁的源代碼漏洞掃描方法、基于CFG的漏洞特征庫生成方法和基于代碼相似性匹配的二進制漏洞掃描方法。
圖3 BinScan方法概述
由于NVD等漏洞信息存在不一致的問題[12-13],并不確定源代碼是否已經(jīng)進行了補丁修補。因此采用源代碼漏洞掃描方法對給定版本的代碼進行掃描,以確定給定版本的源代碼存在哪些漏洞,排除那些源代碼中不存在的漏洞,縮小對二級制代碼漏洞的檢測范圍,提高檢測效率。
基于補丁的源代碼漏洞掃描方法:BinScan在輸入待測Linux Kernel源代碼后,首先利用漏洞信息庫使用檢測算法進行細粒度匹配,其次利用影響版本號庫進行粗粒度篩選,最后生成源代碼所存CVE漏洞結(jié)果。
基于CFG的漏洞特征庫生成方法:利用源代碼檢測CVE結(jié)果生成補丁前后二進制文件,提取結(jié)構(gòu)信息形成特征。
基于相似性匹配的二進制漏洞掃描方法:計算待測二進制文件與漏洞補丁前后二進制文件相似度,檢測補丁是否存在。
系統(tǒng)中找到所有含有漏洞、未修補的重用代碼是很困難的,這需要綜合考慮很多問題。比如語句的順序是否會改變匹配的精確度、算法設(shè)計考慮語義還是語法等。
針對以上問題,結(jié)合對現(xiàn)存的源代碼漏洞掃描方法的研究,本文提出的BinScan漏洞檢測工具應(yīng)滿足以下幾點需求:(1)本文雖主要針對Linux Kernel的源代碼及漏洞信息進行實驗,但BinScan也應(yīng)具備一定范圍的通用性。其他應(yīng)用工具代碼按所述步驟操作后也能適用本節(jié)設(shè)計的算法,其中MySQL、OpenSSL、Firefox等工具已經(jīng)通過實驗驗證;(2)要最大限度地減少誤報率和漏報率;(3)盡可能集成化,簡化使用者的操作步驟,減少參數(shù)配置,生成清晰簡潔的檢測報告;(4)可遷移性強,適應(yīng)多種環(huán)境配置。
本文采用的源代碼漏洞掃描方法基于補丁代碼的函數(shù)定位與漏洞匹配,具體步驟如下:
首先數(shù)據(jù)預(yù)處理標(biāo)準(zhǔn)化每個文件,包括刪除語言注釋和所有與代碼邏輯無關(guān)的語句,尋找漏洞影響路徑,根據(jù)函數(shù)進行定位,將待檢測原始代碼的片段與補丁代碼的片段相互匹配。根據(jù)diff文件特性,可利用更改函數(shù)中的改變位置前三句和后三句共六句定位語句進行精確定位。
根據(jù)補丁文件的“+”“-”前綴語句,可以判斷一個文件是否含有此CVE漏洞。通過代碼定位可找到目標(biāo)更改函數(shù),查看函數(shù)中的補丁定位語句前三句,針對每一句修改語句進行源代碼檢索,匹配源代碼中此條語句查看是否已經(jīng)進行修改,若整個文件“+”語句并未添加,“-”語句沒有刪除,也就說明此版本還存在這個漏洞未修復(fù)。以此類推,逐行檢測,將搜索到函數(shù)后三句定位語句作為結(jié)束的標(biāo)志。
最后利用漏洞影響版本庫中最大的漏洞影響版本號與待測文件版本進行比較,加入版本限制。
基于2.1節(jié)所述源代碼已知漏洞掃描方法,既能夠構(gòu)建完整的源代碼漏洞檢測系統(tǒng),獲取源代碼漏洞檢測結(jié)果;也能夠?qū)⒃创a漏洞檢測結(jié)果作為標(biāo)準(zhǔn)值,進一步用于二進制漏洞檢測方法中。
在源代碼補丁前后分別進行make編譯,之所以不用gcc操作是因為Linux Kernel引用的頭文件太多,在gcc的過程中很可能出現(xiàn)各種故障,使二進制文件的bin文件不可讀。使用xxd語句形成hex文件,用UltraEdit查看、用diff語句得到差值并用compare初步分析可發(fā)現(xiàn)補丁修復(fù)變化過于復(fù)雜,于是用objdump-d語句將二進制文件轉(zhuǎn)換為可讀的反匯編文本文件。
由于編譯的復(fù)雜性及地址的隨機性,二進制代碼檢測無法使用類似2.1節(jié)所述源代碼的補丁文本檢測方法。如圖4所示,file.c文件函數(shù)中加入語句inode_dio_wait(inode),即使只更改函數(shù)中的一條語句,二進制文件中都會有很大的改動。在這種情況下,利用CFG和操作碼可以解決這個問題。
利用IDA Pro處理待測二進制文件及補丁前后二進制代碼,生成包含上下文信息的控制流圖(Control-Flow Graph,CFG)和基本塊的特征向量,每個基本塊特征信息包括兩部分,分別是生成標(biāo)記嵌入和生成特征向量。生成標(biāo)記嵌入時曾嘗試使用ACFG(Attributed Control Flow Graph)方法,它是一種基于神經(jīng)網(wǎng)絡(luò)的嵌入,但是本文主要針對的還是補丁變化,將圖表示作為整體進行評估會忽略細節(jié)。本文在實現(xiàn)時首先對二進制文件執(zhí)行預(yù)處理,然后應(yīng)用DeepBindiff[14]算法,該方法運用了Word2Vec算法[15]生成詞向量的思想以及deepwalk隨機游走技術(shù)生成了網(wǎng)絡(luò)節(jié)點的表示。通過在圖上隨機游走生成文章,每個游走序列都會包含一些基本塊。然后,通過標(biāo)準(zhǔn)化和模型訓(xùn)練得到標(biāo)記嵌入模型并生成標(biāo)記嵌入,這里的標(biāo)記指的是操作碼或者操作數(shù)。由圖4可知每個基本塊都包含了多行指令,每個指令都包含了多個操作數(shù)及MOV、CMP等操作碼,操作碼與操作數(shù)是一對多的關(guān)系,于是指令的嵌入就可以用操作碼的嵌入與操作數(shù)的平均嵌入值來連接獲得。式(1)[14]具體解釋了區(qū)塊特征向量的計算方法。其中,b表示一個塊,p是操作碼,embedpi是操作碼嵌入,weightpi是上述模型中的TF-IDF權(quán)重,setti是操作數(shù),embedtin是操作數(shù)嵌入的平均值。
圖4 更改函數(shù)語句所做改變
特征生成結(jié)果如圖5所示,將源碼的一個版本整體編譯成功后,再進行補丁操作,使用補丁前后編譯形成的二進制文件,通過建立包含漏洞的文件數(shù)據(jù)庫和修補過的文件數(shù)據(jù)庫形成二進制漏洞數(shù)據(jù)庫,進一步區(qū)分修補過和未修補的函數(shù)。將漏洞數(shù)據(jù)庫和待測文件輸入算法即可得到特征信息。
圖5 生成特征
相對于直接從源代碼補丁形成漏洞簽名,利用源代碼漏洞結(jié)果信息標(biāo)定初步漏洞范圍,再將待測文件與二進制文件打補丁前后文件進行相似性檢測得到塊匹配結(jié)果可以更好地獲取與使用在二進制代碼層面的漏洞信息。當(dāng)一個新的Linux Kernel二進制文件需要檢測是否存在漏洞時,需要利用文件特征向量,用TADW算法和k跳貪婪匹配將兩文件結(jié)合,進行塊嵌入生成和塊匹配操作,得到補丁前后兩次二進制文件所有塊的匹配結(jié)果。根據(jù)匹配結(jié)果,即可判斷待測文件與補丁前還是補丁后的二進制文件更為相似。若目標(biāo)二進制文件與補丁前的文件匹配對更多,則此文件未打補丁,存在該檢測漏洞;若目標(biāo)二進制文件與補丁后的文件匹配對更多,則此文件已打補丁,不存在該漏洞。
本文使用Python3.6在Linux平臺和Windows平臺上實現(xiàn)BinScan工具。為了構(gòu)建較全的已知漏洞補丁庫,首先分析了各大漏洞披露網(wǎng)站的信息,最后選取NVD網(wǎng)站作為數(shù)據(jù)來源。本文定義了20個關(guān)鍵詞用于漏洞補丁信息的爬取,如Python、Linux Kernel、Wget等,以這些關(guān)鍵詞為檢索詞條獲取相關(guān)漏洞披露網(wǎng)站的URL。抓取的軟件信息OSS表字段包括軟件名稱(Name)、關(guān)鍵詞(Key Word)、軟件描述(Description)以及NVD記錄信息的總數(shù)量(NVD Records Count)。根據(jù)表中信息,可利用Python的bs4庫定制爬蟲,從NVD網(wǎng)站上批量爬取每條軟件數(shù)據(jù)的詳細漏洞補丁信息,比如軟件的CVE Number、Vulnerability Type、Base Score(包括V3與V2)等信息,以供后續(xù)步驟分析和使用;通過CVE參考鏈接統(tǒng)計工具,將參考鏈接的網(wǎng)站主站點URL進行計數(shù),并逐一驗證是否可以查閱到補丁代碼,根據(jù)這些信息將Hyperlink信息提取進行排序可以得到前三位有用且占比最大的網(wǎng)站,它們的數(shù)量及占比如表1所示。分別編寫這三個網(wǎng)站的爬蟲,爬取網(wǎng)站結(jié)果形成Linux Kernel文件夾,并獲取補丁前代碼(bm)、補丁后代碼(am)、補丁diff文件、危險等級信息(score.txt)以及來源網(wǎng)站(source.txt),這里結(jié)果數(shù)據(jù)庫的網(wǎng)站占比如表2所示。最后篩選后Linux Kernel數(shù)據(jù)庫中有2 700條漏洞數(shù)據(jù),15 496個patch文件。在此步驟中,本文也使用了其他工具,如OpenSSL、OpenSSH、Firefox、Python等,每換一個工具都需要利用漏洞Hyperlink信息排序,重新編寫適合爬取這個工具補丁的網(wǎng)站爬蟲。
表1 網(wǎng)站篩選
表2 結(jié)果數(shù)據(jù)庫網(wǎng)站占比
3.2.1 源代碼漏洞檢測
源代碼漏洞掃描方法實現(xiàn)流程如圖6所示,循環(huán)查看補丁文件是單文件還是多文件以及patch文件中存在@@定位語句的數(shù)目,以Linux Kernel3.10版本為例在算法未優(yōu)化時運行結(jié)果共存在CVE 290條,優(yōu)化后運行結(jié)果共存在CVE 261條。圖7為算法粗粒度篩選前后的檢測結(jié)果對比圖,其中第一列CVE_number深色指的是代碼不包含但在檢測中卻檢測出的及優(yōu)化后漏報的漏洞,第二列patch_file深色指在算法優(yōu)化后去除的錯報和存疑漏洞。為呈現(xiàn)清晰,本圖忽略了project_file和level內(nèi)容。經(jīng)驗證,優(yōu)化后算法準(zhǔn)確率明顯提高,目前此算法的準(zhǔn)確率為93%。
圖6 2.1節(jié)算法實現(xiàn)流程
圖7 2.1節(jié)算法約束前后實現(xiàn)結(jié)果對比
3.2.2 二進制代碼漏洞檢測
由于整個Linux內(nèi)核數(shù)據(jù)量巨大,下文主要以Linux Kernel 4.2為例,在已經(jīng)進行版本4.2的源代碼漏洞檢測基礎(chǔ)上,對二進制代碼漏洞檢測流程進行說明。首先此版本源代碼檢測出漏洞CVE-2015-8785,存在文件路徑為fsfusefile.c,于是在Linux環(huán)境中make編譯4.2版本的Linux Kernel代碼,用此漏洞的diff文件對目標(biāo)路徑文件進行補丁操作,然后進行第二次make編譯,將編譯前后的obj文件分別存放到補丁前文件夾“before”和補丁后文件夾“after”中,最后將補丁前后文件與待測文件作為input1和input2,共得到3 373節(jié)點數(shù)及3 307個公共節(jié)點數(shù),整個數(shù)據(jù)大小為26 456,將結(jié)果存儲到結(jié)果特征中并可得到基本塊索引。因為此漏洞Linux Kernel最后一個影響版本號是4.3.3,于是再編譯4.3版本進行匹配檢驗,得到此版本的塊匹配結(jié)果,發(fā)現(xiàn)亦存在此漏洞,驗證了方法的準(zhǔn)確性。
以Linux Kernel的4.19版本為例進行方法驗證,將Linux Kernel的vmlinux文件以section形式分開,分開檢測代碼函數(shù)相似度,最后檢測結(jié)果如表3所示。
表3 驗證檢測部分結(jié)果
為檢驗算法的有效性,將BinScan與Redebug等現(xiàn)有源代碼檢測算法對比,結(jié)果表明BinScan可有效提升漏洞檢測準(zhǔn)確率。使用Redebug工具對源代碼進行漏洞檢測時會產(chǎn)生誤報現(xiàn)象,如檢測Linux Kernel4.13版本時,會檢測出漏洞CVE-2011-2497,但是實際上這個漏洞并不存在,本文提出的方法可解決此誤報問題。除此之外,本文算法還能系統(tǒng)化地生成整個待測源代碼包含的所有漏洞信息,而不是針對一個漏洞的一條CVE進行檢測。
與利用補丁代碼信息形成補丁簽名Fiber相比,BinScan還使用了漏洞信息對源代碼進行補丁操作,生成補丁前后編譯的文件,此工具在對補丁代碼本身分析的基礎(chǔ)上,聯(lián)結(jié)了上下文信息,增加了比較準(zhǔn)確率。本文提出的基于補丁的漏洞檢測算法既可以解決二進制代碼沒有源代碼支撐做漏洞檢測的問題,也可以利用補丁的精確特性降低漏洞檢測誤報率。結(jié)合上述實驗,不僅證明了BinScan工具對Linux Kernel的可用性,而且驗證了其他軟件工具的可用性,因此可以將本方法遷移到Linux系統(tǒng)的應(yīng)用工具中,如OpenSSL、OpenSSH等。
本文提出了一種針對代碼重用的利用源代碼檢測結(jié)果和二進制代碼補丁前后的二進制文件進行漏洞檢測的方法,設(shè)計并實現(xiàn)了由源代碼到二進制的漏洞檢測模型BinScan工具。它可以實現(xiàn)源代碼檢測并且將其結(jié)果運用到二進制代碼的檢測中,以及將源代碼級的漏洞檢測應(yīng)用于二進制級相似性檢測來檢測補丁存在性。本文對Linux Kernel的漏洞檢測進行評估,結(jié)果表明,本文提出的檢測算法在一定程度上克服了二進制代碼檢測不精確且不通用的問題;對于未得到漏洞特征標(biāo)記的二進制文件,利用源代碼檢測信息比傳統(tǒng)方法更有優(yōu)勢。