蔡昊君,牛少彰
(北京郵電大學 計算機學院,北京 100876)
隨著互聯網的飛速發(fā)展,網絡與人們的生活越來越息息相關,各種社交、學習、購物等軟件逐漸滲透到我們生活中,給我們的生活帶來了很多便利的同時也帶來了很多網絡攻擊。軟件漏洞是網絡攻擊盛行的一個根本原因,在過去的十幾年中,軟件漏洞數量增長迅速,NVD的統計數據顯示,2002年到2019年共有121 279條漏洞出現,其中未知漏洞類型就多達38 868條。因此,及時發(fā)現漏洞是我們必須大力研究的內容,漏洞檢測可以幫助安全工作人員在軟件發(fā)放到市場前及時發(fā)現并修補漏洞。
本文研究的是與內存有關的漏洞,在 CWE最近兩年的最危險軟件漏洞榜單中,對內存緩沖區(qū)范圍內操作不當和“越界寫入”引起的漏洞在榜單中排名非??壳?,在官方統計中跟內存相關的缺陷所引發(fā)的漏洞威脅程度還是很高的。軟件漏洞分析大致分為源代碼分析和二進制代碼分析,源代碼的漏洞分析大部分是基于靜態(tài)的,Kim等人[1]提出了基于代碼相似性的方法,Yamaguchi等人[2-4]提出基于模式的方法,Ramos等人提出[5-6]符號執(zhí)行的方法,而動態(tài)分析技術包括污點分析[7-8]和模糊測試[9-10]。但大部分商業(yè)軟件的發(fā)布并不包含自身的源代碼,而是以二進制的形式發(fā)布,所以面向二進制程序的漏洞分析具有極其重要的意義?,F有的面向二進制程序的漏洞分析方法主要是靜態(tài)分析方法[11-12]和動態(tài)分析方法[13-14],靜態(tài)分析通過利用軟件工具收集程序的語法和語義等信息,從而達到軟件分析的目的。動態(tài)分析通過實際運行程序,觀察程序的狀態(tài)信息來檢測程序中存在的漏洞。
近年來機器學習在漏洞檢測領域也有了一定的發(fā)展,使用機器學習的二進制代碼檢測模型有著檢測速度快、數據處理量大、檢測成本低的優(yōu)勢,但由于二進制程序不能像源程序那樣直接的表達程序信息,從中提取有效的特征集非常困難,導致現有的基于機器學習的漏洞檢測方法存在主觀性強,檢測粒度粗,誤報率和漏報率高等問題。
對于靜態(tài)分析技術存在的分析效率低、誤報率較高的問題和動態(tài)分析技術存在的路徑覆蓋率低的問題,本文提出了一種基于機器學習的二進制代碼漏洞檢測方案,引入動靜結合的思想,同時提取二進制程序的靜態(tài)特征和動態(tài)特征,構建面向二進制程序的漏洞自動檢測模型。
隨著機器學習的迅速發(fā)展,成功在圖像、文字、語音等領域有所應用,近期機器學習也被用到了漏洞檢測中,基于機器學習方法的軟件漏洞檢測研究也取得了一定的進展,根據研究對象的不同主要分為源程序漏洞檢測和二進制程序漏洞檢測。
BM 等人[15]對二進制程序進行研究,首先識別潛在的易受攻擊的語句構造,根據提出的表征方案為每個構造提取靜態(tài)特征,捕獲代碼中采用的緩沖區(qū)使用模式和防御機制,對這些收集到的屬性使用數據挖掘的方法預測緩沖區(qū)溢出。Lee等人[16]提出了一種改進的機器靜態(tài)二進制分析技術——Instruction2vec,在word2vec的基礎上,使用 Instruction2vec對匯編代碼進行建模,利用Text-CNN學習并提取軟件缺陷代碼的特征,文章使用Juliet Test Suite中的CWE-121作為數據集,最后實驗證明該方法的準確率可達到 91%。Liu等人[17]為解決二進制軟件漏洞自動檢測問題提出了一種基于深度學習的方法,分為兩個階段:使用 IDA Pro從二進制代碼中提取函數;利用帶注意力機制的雙向長短時記憶網絡構建預測模型。
J Ren等人[18]針對緩沖區(qū)溢出,提出了一個基于軟件度量方法—BOVP算法,對程序源代碼進行預處理,然后在功能層采用動態(tài)數據流分析的方法,通過分析軟件代碼的特點和不同類型的緩沖區(qū)溢出漏洞的特征,建立了基于功能層面的多類型緩沖區(qū)溢出漏洞模型的決策樹算法,該算法降低了測量的維度,減少了實驗的開銷,并與SVM、Bayes、Adaboost和隨機森林算法進行對比,該文章提出的算法預測結果更準確。Bilgin等人[19]開發(fā)了一種源代碼中間表示方法,可以對源代碼的抽象語法樹形式進行智能分析,使用機器學習算法去檢測脆弱性代碼。文中與code2vec方法進行了對比,code2vec使用了神經網絡生成代碼片段的向量表示,在 CWE數據集上進行比較,該文章提出的方法效果更好。Li等人[20]提出了一種基于混合神經網絡的源代碼漏洞自動檢測框架,利用低層虛擬機中間表示和逆向程序切片將輸入轉換為具有顯式結構信息的中間表示,通過卷積神經網絡學習漏洞的局部特征,循環(huán)神經網絡學習漏洞的全局特征,局部和全局特征的融合提高了檢測的準確性。
從相關學者的研究可以看出,機器學習應用在程序源代碼的研究居多,通過抽象語法樹、數據流圖等提取源代碼的結構特征,再利用機器學習模型進行預測,而對二進制程序的研究相對較少,并且基于機器學習的漏洞檢測大多是提取程序的靜態(tài)特征,很少有動態(tài)特征和靜態(tài)特征相結合的。本文提出采用動靜結合的方法,提取二進制程序的動態(tài)特征和靜態(tài)特征,利用分片方式精確定位漏洞位置,并結合機器學習的算法去檢測二進制程序漏洞,提出一種新的檢測方式的同時也豐富了對二進制程序的研究。
本文用Juliet Test Suite作為研究的數據集,Juliet套件中包含 CWE多種漏洞類型的樣例文件,漏洞類型豐富、涵蓋范圍廣,將測試用例編譯后即可得到可執(zhí)行文件,之后的研究將在可執(zhí)行文件上進行。本文選用 Pin作為提取二進制程序動態(tài)特征和靜態(tài)特征的工具,首先獲取程序的特征日志文件,經過預處理,切片生成數據集,通過分詞轉換成詞向量,輸入到模型中進行訓練。
通過Pin的特定API可以獲取動態(tài)特征和靜態(tài)特征,利用 Addr2line判斷其與漏洞位置的距離。由于一個函數的指令數量范圍不定,直接預測出現漏洞的函數粒度也不定。為了能更加精確的定位到漏洞的位置并且保證預測的準確率,本文對函數進行分片,分片標簽是通過判斷其代碼中是否有和漏洞位置較為接近的指令,然后通過本文提出的向量轉換方法進行特征向量化。
各類機器學習算法有不同的優(yōu)缺點和適應性條件,由于漏洞的多樣性,其特征也存在多樣性,所以不同漏洞適用的分類算法也不同。本文通過對同一漏洞進行多種分類算法建模,比較各模型的準確率、召回率等評價指標來選擇最適合此漏洞類型的算法。具體方案如圖1所示。
圖1 方案設計Fig.1 schematic design
本文采用 Pin作為提取程序動態(tài)特征和靜態(tài)特征的工具。Pin可以認為是一個JIT編譯器,他的輸入是可執(zhí)行文件。本文首先對二進制程序進行函數粒度的插樁分析,通過Pin特定的API獲取運行中函數的相關信息,然后對函數中的指令進行插樁。獲取運行中指令的行為狀態(tài)信息,包括指令的特性、內存和寄存器的讀寫信息等。靜態(tài)特征指的是可以反應程序語義和語法信息的特征。例如指令的操作碼和操作數、是否為轉移指令、是否為分支指令等;動態(tài)特征為程序運行時的行為狀態(tài)信息。例如指令操作數大小、寄存器值等。
相關的 API見表 1,內存相關的漏洞可能跟操作數和寄存器的值有關。操作數規(guī)定了指令中進行數字運算量,操作數可以有三種方式獲得:立即數、寄存器、內存。對于雙操作數指令,操作數可以是寄存器操作數、內存操作數和立即數。本文 Pin插樁讀取操作數值和寄存器值,解析日志中3個內存操作數和4個寄存器值。
表1 APITab.1 API
插樁后的 log文件中會包含大量的函數和指令。為了提高實驗精度和速度,篩選掉與測試樣例無關的函數,例如 malloc庫函數。通過 RTN所屬的 IMAGE是否為測試樣例文件來判斷該RTN是否需要被過濾。
一個RTN包含多條指令,少則幾十條,多則上萬條。如果按照RTN的粒度對二進制程序進行漏洞檢測,還需要對有漏洞的函數進行排查,極其耗時耗力。如果按照INS的粒度進行檢測,檢測效果大概率較差,因為一行源碼可能會對應多條指令。所以本文采用切片的方法,將長度不等的RTN切分成長度相等的代碼片段,代碼片段構成了樣本集。統計所有RTN包含的指令數量,打破原有的函數框架,根據粒度和精度的原則選擇較為合適的分片長度,對每個RTN按照指定數量進行切片。
Pin插樁二進制程序可以得到指令的地址,利用 Addr2line工具從指令地址中獲取到指令所對應源碼所處的文件名、函數名及代碼文件中的行號。數據集中的 manifest.xml文件記錄了缺陷樣例的文件名、漏洞類型和導致漏洞的行號,以此可以判斷該指令是否在漏洞附近,并標注為有/無漏洞(1/0)。
本文在漏洞定位的粒度上選擇分片粒度,將函數中的指令進行分片。本文針對二進制程序進行漏洞檢測及定位,由于二進制程序可讀性低,為方便研發(fā)人員進行修改,需要定位二進制程序漏洞在源程序的位置。如果進行指令級的漏洞定位,很難在源程序中找到相應的語句,源程序的每條語句對應多條指令,而且很多時候漏洞的出現不僅是單個指令的問題,所以指令級的漏洞定位粒度太小,預測準確率較低。如果進行函數粒度的漏洞定位,輸出源程序中可能會出現漏洞的函數名稱,若函數過長,研發(fā)人員需要花大量的時間核對代碼。
針對以上問題,本文的切片、代碼段的粒度,既可以在指令層面學習到細粒度的信息,有利于漏洞的定位,也可以在多指令的粗粒度上學習到更豐富的結構信息和漏洞整體特征。如果出現函數的調用關系,漏洞定位可定位到最內層函數,例如函數A調用函數B,若B中存在漏洞,則漏洞定位會直接定位到函數B中,省去了檢查函數A中其他語句的成本。
本文利用機器學習的算法,在獲取到程序運行的動態(tài)特征和靜態(tài)特征之后,經過特征分析和向量轉換,通過模型自動檢測程序漏洞。本文采用的算法有 ID3、C4.5、隨機森林、改進的隨機森林(Extra-trees)和CNN。ID3和C4.5是經典的決策樹算法,產生的分類規(guī)則易于理解,可解釋性強,準確率較高;隨機森林算法不容易過擬合,具有較好的抗噪聲能力,支持并行化;CNN可以捕捉局部相關性,在文本分類任務中可以提取語句中的關鍵信息。
傳統的靜態(tài)分析方法和動態(tài)分析方法在提取特征后需要大量人工分析,耗時耗力,且很難解釋特征之間的內在聯系。而機器學習算法具有自動化、精確、迅速、可自定義和規(guī)模化等優(yōu)點,可自動學習新的模式,在新數據輸入的情況下可迅速做出反應。對于二進制程序的漏洞檢測任務,可以歸為二分類問題,即將目標程序分為有漏洞和無漏洞兩種,通過機器學習模型學習程序的動態(tài)和靜態(tài)特征向量,判斷樣本有無漏洞。
3.1.1 數據集預處理
本文選用 NIST的 Juliet測試套件,其中包含CWE許多條目測試用例。CWE是開發(fā)常用的軟件安全漏洞列表,是軟件安全工具的重要衡量標準。本文研究的漏洞類型有內存泄漏、內存溢出和內存訪問越界。具體選擇的 CWE條目見表2。
表2 數據集條目Tab.2 data set entry
將數據集中 C/C++源程序編譯鏈接成可執(zhí)行文件,即為二進制文件,具體實驗將在此二進制文件上進行。數據集中有可單獨運行的文件,有包含數據流或控制流的文件,漏洞形式豐富。測試用例可能會包含多個測試文件,編譯鏈接得到的可執(zhí)行文件名是以“a”結尾的。編譯后得到各個漏洞類型所對應的可執(zhí)行文件數量如表 3所示。
表3 二進制程序數量表Tab.3 table of the number of binary programs
3.1.2 特征提取
本文借助動態(tài)二進制插樁工具 Pin提取二進制程序的動態(tài)特征和靜態(tài)特征,編寫 Pintools對函數和函數中的指令進行插樁分析,獲取RTN的信息以及 RTN包含的 INS的信息,具體調用的API見章節(jié)2.1。Pin插樁二進制程序得到的特征信息保存在log文件中,如圖2所示,第一行為函數名、SECTION名、IMAGE名。IMAGE為函數所在的代碼文件。為減少無效數據干擾,本文通過判斷 IMAGE字段是否為代碼文件名來刪除不匹配的樣例文件中的函數,并將 SECTION中非.text,即非代碼段的函數刪除。
圖2 插樁日志Fig.2 log of Pin
得到日志后,通過addr2line獲得每條指令的地址。addr2line是一個可以將指令的地址和可執(zhí)行映像轉換為文件名、函數名和源代碼行數的工具。在 Pin插樁完成之后,插樁日志中保存了每條指令的地址,通過地址找到指令所對應的源碼行號,由此生成一個新日志,如圖3所示。
圖3 解析后的日志Fig.3 parsed logs
3.1.3 標簽生成
日志解析完成之后,就提取到了程序中各函數的動態(tài)特征和靜態(tài)特征,向量化之前首先需要進行切片。程序切片是為了更加細粒度的定位到漏洞出現的位置,在保證漏洞檢測準確率高的基礎上使漏洞范圍更加精確,從而減少排查漏洞所耗費的成本。為盡可能的縮小定位范圍,本文設置分片長度slice_length=50。slice_length是RTN切分時每個分片所包含的 INS數量,長度不足slice_length的部分補0。
對于本身無漏洞的函數,切片片段標簽為“0”;對于存在漏洞的函數,切片后若片段中指令所對應的源碼與漏洞位置在5行范圍內,則該片段的標簽為“1”,否則為“0”。
在特征向量化之前將按此規(guī)則得到的數據集進行劃分,隨機提取數據集中的80%作為訓練集,20%作為測試集。由于內存泄漏中有漏洞的樣本僅有1 758個,為了保證樣本量充足盡可能使得正負樣本均衡,內存泄漏中正樣本取12 000個,內存溢出和內存訪問越界的正負樣本數各取 7 500個。訓練集和測試集中正負樣本的具體數量見表4,Good表示有漏洞,Bad表示無漏洞。
表4 數據集Tab.4 table of the number of binary programs
3.1.4 向量化
由于特征中存在匯編指令等非數值特征無法被模型直接理解,在輸入到模型訓練之前需要先將特征向量化。首先將特征進行分詞,通過word2vec模型,將分詞映射成向量。
常見的語料庫詞維度是100~200維,本文中需要轉換成向量的是匯編指令,操作數操作碼等,詞表大小相比一般的文本分類小,每條指令轉換成176維的向量,每個樣本的維度是50×176。
向量化結果如圖 4所示,行號和指令地址不作為模型訓練的特征(前8個非數值特征均轉換成20維的向量)。
圖4 向量化Fig.4 vectorization
3.1.5 漏洞檢測及定位
將向量化后的特征輸入模型中進行訓練。多次調整學習率、Batch_size、優(yōu)化器等參數優(yōu)化模型,保存最優(yōu)模型的訓練結果。
將待檢測的二進制程序,選擇要檢測的漏洞類型及檢測模型,加載此漏洞類型最優(yōu)模型進行預測,輸出是否存在漏洞,若有漏洞存在則輸出存在漏洞的函數名稱,并通過指令的地址范圍來確定漏洞位置。
3.2.1 評估指標
常用的模型評價指標是準確率,但是單用準確率評判模型效果說服力較低,所以本文選擇了更多的指標來共同衡量模型訓練的有效性。
TP表示正類中被預測為正類的樣本數量,TN表示負類中被預測為負類的樣本數量,FP表示負類中被預測為正類的樣本數量,FN表示正類中被預測為負類的樣本數量。
Accuracy是準確率,是所有實例中,被分類正確的比例,Precision是精確度,衡量的是查準率,也就是模型預測的準不準;Recall是召回率,衡量的是查全率,就是有沒有把正例全部找出來,F1 score是精確度和召回率的調和。
本文從以上所列指標對不同漏洞類型的不同模型的預測結果進行比較。
3.2.2 漏洞檢測結果
本實驗中,對二進制程序進行有無漏洞的檢測,預測結果為“1”則有漏洞,“0”則無漏洞。找到最適合每一種漏洞類型的檢測模型,不同漏洞模型的檢測結果如表5所示,表中的精確度、召回率和F1 score都是針對負樣本的。
從表 5中可以看出,在內存泄漏和內存訪問越界兩種漏洞的實驗中,改進的隨機森林模型檢測效果最好,而內存溢出中隨機森林模型檢測效果最好,預測準確率都高達 97%。對于內存泄漏,準確率和精確度都比較高,都超過了96%,但精確度比準確率稍低一些,說明模型存在少許誤判;召回率和精確度相差4%,說明有漏檢的情況,一些有漏洞的樣本沒有檢測出來,可能是因為內存泄漏的數據集中負樣本的數量少,所以沒有提取到豐富的負樣本特征。對于內存溢出和內存訪問越界,四類指標值都較為均衡,說明模型能很好地檢測到有漏洞的樣本,誤判率和漏檢率都比較低。
表5 漏洞檢測實驗結果Tab.5 experimental results of vulnerability detection
3.2.3 漏洞定位結果
本文是針對二進制程序進行漏洞檢測的研究,對于待檢測的二進制程序,通過本文提出的特征處理方法和模型,可以輸出程序中可能存在漏洞的函數及位置。圖5是漏洞檢測定位結果,可以直觀的看到存在漏洞的函數,源程序可以通過源碼中漏洞存在的位置直接定位并檢查代碼;若只有二進制程序,可以通過二進制程序漏洞存在的地址向后檢查,極大的縮小了檢查代碼的范圍,節(jié)省了大量的時間和人力成本。由于本實驗的數據集只提供了真實漏洞在源碼中的位置,所以可以使用預測漏洞在源碼的位置來檢驗本文方法的有效性。
圖5 漏洞定位結果Fig.5 vulnerability location results
選擇三種漏洞類型中各自表現最好的模型,檢測他們在測試集上的定位效果。本文通過兩步來檢驗漏洞定位的準確性:(1)預測的漏洞位置是否包含了真實漏洞位置;(2)預測的漏洞位置范圍和真實漏洞位置的距離。
表6統計了三種漏洞類型的測試集中漏洞定位結果不包含真實漏洞位置的比率,可以看出,每種類型都是超過99%的預測結果包含了真實漏洞的位置,程序員檢查此范圍內的代碼即可定位到漏洞。
表6 不包含真實漏洞位置的樣本數占比Tab.6 proportion of samples that do not contain the actual vulnerability location
第一步的檢驗完成,但預測結果包含真實漏洞位置并不代表定位精確,是否能真正地縮小排查范圍,還需第二步的檢驗。
圖6表示的是預測的漏洞位置和真實漏洞位置的距離統計,距離以行為單位,可以看到和真實漏洞位置的距離不超過 10行的占整個測試集的90%左右,預測結果比較精確。
圖6 預測位置誤差統計Fig.6 prediction position error statistics
以上兩步的檢驗證明了本文提出的方法在漏洞定位上有比較好的表現效果。
本文提出了一種基于機器學習的二進制漏洞檢測方法,利用動態(tài)二進制插樁工具 Pin提取程序的靜態(tài)特征和動態(tài)特征,通過本文提出的切片以及向量化方法得到訓練模型的數據集,訓練漏洞檢測模型。實驗結果表明,本文提出的方法在漏洞檢測和定位上都取得了較好的效果,由于實驗中的數據集是基于真實的項目,所以該方法在網絡安全實際應用中也適用,并且這一課題的研究為二進制漏洞檢測提供了新的思路。實驗結果較好,但該實驗仍有很多可以繼續(xù)嘗試優(yōu)化的方向,例如分詞方法、標簽生成的規(guī)則,都會在一定程度上影響模型的檢測效果。未來我們也會嘗試構建多種漏洞的統一檢測模型,提升檢測模型的自動化能力和泛化能力,這是在本文中還沒有實現的,還有繼續(xù)探索的空間。