作者:騰訊科恩實驗室
原文鏈接:https://mp.weixin.qq.com/s/x6jNNvkWRJt1YcHMakWHEg

引言

為提升靜態分析在二進制文件漏洞檢測領域效率和可擴展性,科恩孵化并開源二進制文件靜態漏洞分析工具BinAbsInspector項目。
代碼倉庫地址:https://github.com/KeenSecurityLab/BinAbsInspector

背景

軟件漏洞檢測“兩板斧”

隨著信息產業的發展,網絡安全問題日益嚴峻,軟件漏洞對于互聯網威脅極大,是網絡安全中的核心問題。為了緩解漏洞所造成的危害,需要對軟件進行安全檢測,盡可能地發現并消除潛在漏洞。目前常見的自動化漏洞檢測手段可以分為兩類:動態分析測試靜態分析

動態分析測試方法(如fuzzing等)在過去五年里吸引了研究者的廣泛關注,相關系統在工業界中已經得到了大規模的部署和應用。相比于動態方法,靜態分析通常具有更高的覆蓋率,然而,現階段對于靜態分析的使用多依賴于人工經驗規則,且精度和效率之間尚未找到一個合適的平衡點,這導致其在現實場景中的落地不盡如人意。

靜態分析工具現狀

目前國際上較為成功的商業化分析工具有 Coverity[1] CodeSonar[2]VeraCode[3] 等,它們在代碼質量保障上發揮了重要作用,相關產品也在Google等公司的DevOps流程中得到了廣泛部署和使用。

包括開源及商業化產品在內,現有的靜態分析方案多為源碼級分析。面向源代碼進行掃描,盡管可以在一定程度上滿足軟件安全需要,然而在真實安全場景中,待分析對象多為二進制文件,如嵌入式系統固件,商業軟件等,研究人員難以獲得相應的源代碼,此時源碼級靜態分析方案不再適用。

值得一提的是,部分商業化產品(如CodeSonar等)也提供了對于二進制文件的分析能力,然而商業化路線所帶來的封閉性,在很大程度上限制了普通研究者的使用和二次開發。與此同時,在開源社區中也涌現出一批知名的二進制分析工具,如 angr[4]BAP[5]cwe_checker[6] 。其中,angr和BAP逐漸往通用分析框架發展,并非專注于二進制漏洞掃描,因此其內部的分析算法較為龐雜,不利于進一步擴展和優化;cwe_checker的定位相對清晰,專注于安全漏洞掃描,但其精度和效率卻不甚理想。目前業界亟需一種更為先進的二進制漏洞掃描工具,在開源的大前提下,其性能和可擴展性也要滿足真實場景的需要。為此,科恩實驗室基于自身在二進制領域豐富的研究與實踐經驗,同時結合業內相關優秀工具的設計理念,最終孵化出性能出色且自主可控的二進制漏洞靜態掃描工具—BinAbsInspector。

原理與實現

BinAbsInspector的設計思想來源于上世紀70年代誕生的經典程序分析理論“抽象解釋”,在具體實現上,BinAbsInspector的分析基于Ghidra[7]所提供的中間表示Pcode上。通過設計合適的抽象域,實現其上的多種運算,完成相關Pcode的操作語義,執行流敏感(flow-sensitive)和上下文敏感(context-sensitive)的過程間分析,同時加入靜態污點分析的能力,完成對程序運行時狀態的抽象估計。基于上述分析所得的抽象數據流信息對多種漏洞建模,最終實現對二進制漏洞的靜態掃描。

對于程序的抽象方法,我們主要參考了經典論文《WYSINWYX: What you see is not what you eXecute》[8] 中的做法并加以改良、簡化和提升。 具體來說,在BinAbsInspector中整個運行時環境被分為Local (抽象棧)、Heap(抽象堆)、Global(全局變量和數值)、Unique(對應Ghidra中產生的臨時變量區)和Register(寄存器區)五種region。在這些不同的抽象區域上加上偏移數值offset,便可以組成一個抽象變量ALoc(Abstract Location/Variable)。因為在二進制程序中,變量并非全部顯式表示,ALoc便是對實際程序中變量的一種估算和識別。 對應不同的程序點,需要記錄此處可能存活的抽象變量和其對應的抽象值,稱之為AbsEnv(Abstract Environment)。

因為是靜態的抽象,那么對于一個程序點的一個抽象變量,它可能會包含多個抽象值,這些抽象值組成了一個集合。雖然這個集合可能會包含無窮多個元素,但是為了保證整個計算過程實踐上可收斂,令此集合取一個上限K,這種集合稱之為KSet。一旦其中包含的元素超過K,便將其變為一個Top,即包含所有抽象值。此方法與前人重要相關工作 Jakstab[9] 中的KSet較為相似。KSet支持多種算數和邏輯運算。此外每一個KSet對象也會包含一個污點的位圖,用來跟蹤多個污點的同時傳播,從而實現靜態污點分析。這樣AbsEnv便可以認為是一個從ALoc到KSet的map。

由于BinAbsInspector的分析是上下文敏感的,對于被調用者的上下文 (Context),我們使用最近的call string(call site)來進行唯一標識。即對于同一個被調用者,不同的調用者會生成不同的Context,一般只記錄最近的幾個調用者。這樣我們便把程序點處的AbsEnv記錄在不同的Context中。

除此,對于過程內的不動點計算BinAbsInspector里使用了worklist算法,即把待處理的程序點不斷地放入worklist中,直到其空為止。過程間分析主要在于不同的Context之間的轉變,這是通過call/return指令的語義實現的。這樣通過對整個程序指令的迭代計算值并加以Context的轉換,Context及其附屬的worklist得到逐一處理,直到所有的worklist計算結束,最后達到不動點。

通過這整個的計算過程,便會得到所有可能的Context以及對應的每個程序點的AbsEnv。這樣相當于得到了一個對程序行為可靠的估算,有了這些抽象數據流的信息,我們便可以進行內存破壞漏洞、命令注入漏洞等多種漏洞的檢測了。

實例演示

下面我們通過一個包含Use-After-Free漏洞的簡單樣例來演示BinAbsInepector的運行情況和基本原理。

漏洞原理

CWE416_Use_After_Free__malloc_free_struct_01_bad函數中首先調用malloc函數分配內存用于存放100個twoIntsStruct對象—>依次對這部分對象進行初始化操作—>直接釋放內存—>釋放內存過后再次調用了printStructLine函數訪問已釋放內存中的地址—>造成Use-After-Free漏洞。

void CWE416_Use_After_Free__malloc_free_struct_01_bad()
{
    twoIntsStruct * data;
    /* Initialize data */
    data = NULL;
    data = (twoIntsStruct *)malloc(100*sizeof(twoIntsStruct));
    if (data == NULL) {exit(-1);}
    {
        size_t i;
        for(i = 0; i < 100; i++)
        {
            data[i].intOne = 1;
            data[i].intTwo = 2;
        }
    }
    /* POTENTIAL FLAW: Free data in the source - the bad sink attempts to use data */
    free(data);
    /* POTENTIAL FLAW: Use of data that may have been freed */
    printStructLine(&data[0]);
    /* POTENTIAL INCIDENTAL - Possible memory leak here if data was not freed */
}

int main(int argc, char * argv[])
{
    /* seed randomness */
    srand( (unsigned)time(NULL) );
    printLine("Calling bad()...");
    CWE416_Use_After_Free__malloc_free_struct_01_bad();
    printLine("Finished bad()");
    return 0;
}

安裝及導入

BinAbsInspector作為Ghidra Extension的形式進行開發,構建后安裝在Ghidra中,支持GUI和Headless模式運行,用戶也可以通過項目中提供的Dockerfile構建docker鏡像體驗功能,具體使用方法見 倉庫README 。在此我們以GUI模式為例介紹使用步驟。

首先將BinAbsInspector安裝在Ghidra,我們將編譯好的樣本程序(armv7)導入Ghidra,待Ghidra的分析完成,便可以運行我們的分析了。這時會彈出工具的分析選項框,將分析過程的配置參數暫時保持默認即可。

圖1-工具分析選項框

結果展現

分析顯示找到2處CWE告警,在下方命令行中會顯示具體告警的地址。
標記1:觸發告警的地址,即產生Use-After-Free訪問的指令地址;
標記2:上下文調用記錄,可以理解為函數的調用棧;

圖2-分析結果展現

分析溯源

雙擊命令行中的告警地址可以在匯編窗口跳轉到對應的指令,另外右邊的反編譯窗口也將同步展示對應的偽代碼,可以看到兩條告警的指令都位于“printStructLine” 函數的內部,都是訪問已釋放內存的LDR指令,結果符合預期。

圖3-雙擊告警地址結果顯示

原理上簡而言之,在第6行“malloc”處創建了一個Heap region,data的抽象值為此Heap及偏移值0,這一信息被加入當前的AbsEnv中并繼續向后傳播,經過第17行的“free”,data的抽象值數值保持不變,其中的Heap region變成了一個相同數值但是為無效狀態的新region,當前的AbsEnv也會同步更新這一變化并將這一改動向后傳播,最后在19行“printStructLine”內部的LDR指令時,通過查詢傳播到此的AbsEnv,檢測到data指向的是一個無效的Heap region,這樣便可以查找出這個Use-After-Free的問題。

性能評估

我們選取 Juliet[10] 這一較為權威的測試集,在x86、x64、armv7三個架構上進行測試,并與cwe_checker測試結果進行對照比較。 另外,BinAbsInspector也支持對aarch64架構樣本的檢測,這是cwe_checker目前不支持的。 下面表格展示的是在CWE415 (Double-Free), CWE416 (Use-After-Free),CWE476 (Null Poionter Deference), CWE78 (Command Injection) 4種核心漏洞檢測能力與cwe_checker的對比結果。 結果表明,BinAbsInspector的測試結果在所支持的CWE類型上均取得較大幅度優勢。

(BAI: BinAbsInspector, CC: cwe_checker, TP: True Positive正陽性, FP: False Positive假陽性, TN: True Negative正陰性, FN: False Negative假陰性)

圖4-x86下-核心檢測能力數據對比

圖5-x64下-核心檢測能力數據對比

圖6-armv7下-核心檢測能力數據對比

圖7-三種架構下核心漏洞檢測誤報率對比

圖8- 三種架構下核心漏洞檢測漏報率對比

引用

1.https://www.synopsys.com/software-integrity/security-testing/static-analysis-sast.html
2.https://www.grammatech.com/products/source-code-analysis
3.https://www.veracode.com/
4.https://angr.io/
5.https://github.com/BinaryAnalysisPlatform/bap/
6.https://github.com/fkie-cad/cwe_checker/
7.https://ghidra-sre.org/
8.Gogul Balakrishnan and Thomas Reps. 2010.《 WYSINWYX: What you see is not what you eXecute.》 ACM Trans. Program. Lang. Syst. 32, 6, Article 23 (August 2010), 84 pages https://dl.acm.org/doi/pdf/10.1145/1749608.1749612
9.http://www.jakstab.org/
10.https://samate.nist.gov/SRD/testsuite.php
11.https://www.binaryai.net/


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1887/