概述
緩衝區溢出問題日益嚴重,根據 CERT/CC的統計,在2005 年公布的漏洞中,有 2/3 屬於緩衝區溢出漏洞。現有緩衝區溢出靜態檢測方法包括:模式匹配,詞法分析,約束求解等。 FlawFinder 和 ITS4 採用模式匹配方法。 SPLINT使用輕量級數據流分析驗證程式中的錯誤,它要求程式設計師在源碼中添加注釋。Bugscam是一個用於檢測執行檔中緩衝區溢出問題的工具,以 IDA PRO 支持的 IDC 腳本形式實現。該工具採用模式匹配方法,定位不安全的 API 函式,並通過回溯暫存器的值來判定 Buffer 的大小和類型(堆或棧)。其分析沒有涉及控制流和數據流。 Bugscam 的另一個缺點是沒有通用性,只能對 x86 進行分析,無法用於對其他硬體體系結構的彙編。在很多情況下,類似 strcpy, memcpy 的函式調用在彙編層消失,此時採用硬編碼完成相應函式的功能。例如 MS03-020, MS04-011, MS06-055 等漏洞都屬於該類型,上述緩衝區溢出漏洞都發生在寫記憶體的循環中 。
緩衝區溢出原理分析
我們知道,電腦程式是由函式或過程組成的。 當程式運行時, 過程 /函式調用時需保存的信息用堆疊中一塊連續的存儲區來管理,這塊存儲區稱為活動記錄或幀(它在程式執行時在進程的棧中分配)。
由此可知當在程式中對活動記錄中的局部變數進行賦值時,若不對緩衝區的邊界進行檢測,那么對局部緩衝區的賦值操作就可能越過緩衝區邊界而覆蓋活動記錄中的 EBP 和返回地址,這樣就造成了緩衝區溢出漏洞,一般後果就會造成程式異常終止,嚴重的就會被一些惡意的攻擊者利用其漏洞, 從而使程式的執行路徑改變,轉至攻擊者精心設計的代碼, 達到攻擊者的目的,給系統造成非常嚴重的後果。
緩衝區溢出漏洞這方面的研究,國外的一些大學和公司在這方面做了大量的工作,但效果都不是很理想。比如:一些靜態檢測工具和靜態檢測方法, 比如佛羅理達科技學院 TerryBruce Gillette 提出的二進制代碼檢測技術和由加利福尼亞大學的 Eric Haugh 和 Matt Bishop 提出的 C 程式緩衝區漏洞測試方法; 以及動態檢測工具動態檢測方法,比如由哥倫比亞大學的 Stelios Sidiroglou 和 Angelos D.Keromytis 提出的利用執行處理從緩衝區溢出漏洞中恢復的方法。 這其中針對緩衝區溢出的動態檢測工具和方法,是通過對 C 編譯器進行改進之後來達到檢測的目的的,這樣源程式經過編譯之後會增加可執行程式的大小,同時對程式的運行效率有一定的影響; 而針對 C源程式的靜態檢測工具和方法需要程式設計師在源程式中添加注釋來輔助完成對緩衝區溢出漏洞的檢測,這樣就會給測試人員造成了諸多不便,同時這種檢測方法也是不完全的,尤其是針對大型的源程式而言,很容易造成遺漏 。
溢出檢測模型的實現
靜態檢測模型
針對緩衝區溢出原理,這裡提出一種對緩衝區溢出漏洞進行靜態檢測的模型。 利用該模型通過對 C源程式的掃描,檢測出其中對緩衝區的沒有進行邊界檢測而造成緩衝區越界的操作。
模型實現利用編譯技術中的詞法分析和語法分析, 使用flex 生成詞法分析器,使用 yacc 生成語法分析器。 在系統中使用兩張 hash 表來保存在掃描過程中檢測到的函式和變數,每個語法元素的綜合屬性和繼承屬性在語法分析過程中保存至記錄的不同屬性域中。 對於標準 C 函式館中需要檢測的函式,在系統初始化時將它們插入函式 hash 表中。 整個實現過程由以下步驟組成:
(1)利用 flex 生成詞法分析程式,識別出源程式中標記。 比如標準 C 語言保留的關鍵字、變數名、函式名等等標準 C 的語法單元。 然後將這些識別出的語法單元作為語法分析程式的輸入;
(2)由 yacc 生成語法分析程式,語法分析程式利用詞法分析的輸出作為輸入,根據標準 C 的語法識別出變數的聲明、定義以及對變數的操作;函式的聲明、定義以及函式調用;並在基本語句(比如條件語句、賦值語句等等)的語法中嵌入相應的動作;
(3)根據第二步中識別出的不同對象,如果識別出的是變數的定義單元,就在模型中生成一個該變數的記錄並將該變數記錄插入變數記錄 hash 表中;如果是函式的定義單元(記錄函式的定義信息), 就在模組中相應的生成一個函式定義記錄並將其加入函式定義記錄 hash 表中。 變數的屬性域中有專門記錄變數屬性值的域,通過這些屬性反應變數的狀態;
(4)對於一般的 C 語句,利用語法分析程式在對語句分析的過程中模擬其語句動作。這樣就可以反應變數和緩衝區的經過語句操作後的狀態, 從而檢測出源程式中對緩衝區越界的操作;
(5)對於函式的定義,需要分析其中語句的語義,根據不同的語句編寫不同的語義動作函式來實現對函式定義中語句的分析;
(6)對於每個識別的變數,在變數的屬性記錄域中用一個域來記錄變數的值,如果是緩衝區,根據其初始長度動態分配記憶體塊用來記錄緩衝區的每個值。
(7)對於函式的嵌套調用,通過生成函式的嵌套調用樹(程式執行的順序性), 在掃描時, 通過後序遍歷嵌套函式調用樹,根據函式定義時的形參的約定和函式調用時實參的具體值以及函式體內對部局部變數的操作的分析來得到結果。
本檢測模型可以檢測 C 源程式中的棧和堆溢出 。
基於中間彙編的檢測模型
這裡提出的檢測模型需要將彙編格式的可執行代碼翻譯成中間彙編形式,並對翻譯後的代碼進行檢測。採用中間彙編形式的優點如下: (1)算法對硬體平台透明,由於檢測算法建立在中間彙編形式上,因此針對各種不同硬體平台,只要完成語言翻譯,無須重寫算法; (2)代碼可閱讀性好,採用RISC 設計和 3-運算元體系,精簡了部分指令; (3)易於檢測,例如,為了方便溢出的檢測,對記憶體讀、寫指令進行區分 。
其實現步驟如下:
1.漏洞檢測模型
將執行檔輸入 IDA PRO中, IDA PRO 識別二進制檔案編譯的機器語言,將其反彙編成對應的 X86, SPARC 或 Alpha 格式。中間彙編翻譯模組將反彙編代碼轉換成統一形式的中間彙編語言。溢出檢測模組對不安全函式和寫記憶體循環進行定位,並對這些位置進行相應的檢測,產生分析結果報表 。
2.中間彙編語言的設計
由於不同 CPU架構採用不同彙編指令集, 因此需要採用中間彙編語言,否則就要針對不同硬體體系結構編寫不同檢測程式,不便於統一處理 。
3.設計原則與 X86 彙編語言的翻譯
中間彙編語言設計的主要原則如下: (1)精簡指令集的設計思想; (2)足夠多的暫存器數量以適應各種硬體體系結構;(3)儘量簡單的定址方式,去除不利於閱讀的複雜定址方式;(4)3-運算元的指令格式 。
4.定址方式的設計與翻譯
保留以下 4 種定址方式: (1)立即定址,運算元直接存放在指令中; (2)暫存器定址,運算元存放在暫存器中; (3)直接定址,運算元的有效地址直接存放在指令中; (4)暫存器間接定址,運算元的有效地址存放在暫存器中。取消基址變址定址,中間運算結果採用臨時暫存器來存儲 。
5.指令系統的設計
下文從指令格式、參數傳遞方式、訪存指令設計等方面對指令系統進行介紹。
(1)指令格式。由於 X86 彙編語言指令集是 CISC,不利於閱讀,因此這裡統一指令格式為“3-運算元”表示方式,即(op, arg1, arg2, result),其中,arg1, arg2 表示指令參數;result存儲指令運算結果。當 op 為一元或零元運算符(如無條件轉移)時,指令格式表示為(op, arg1, -, result)或(op, -, -, result)。
(2)暫存器傳遞參數。僅採用輸入/輸出暫存器傳遞函式調用的參數並返回函式值,此時存在函式參數識別問題。
(3)明晰的訪存指令。 X86 彙編在訪存指令上,對讀/寫的區分不夠明晰。例如, mov 指令可以表示讀記憶體或寫記憶體。而循環讀記憶體不會造成緩衝區溢出,循環寫記憶體則可能造成緩衝區溢出。ldm(load memory)指令表示讀記憶體, stm(store memory)指令表示寫記憶體。
(4)精簡部分指令。取消冗餘指令,例如 INC, DEC 可以用 ADD, SUB 指令代替。
(5)刪除重複的轉移指令。 無條件跳轉指令,採用 branchaddr, -, -表示。條件跳轉指令採用 br_cc addr, [%fxx], -格式表示,是否跳轉根據%fxx 暫存器的值確定。
(6)清晰的循環拷貝指令。 4.2.1 節給出的循環發現算法無法檢測到類似 rep movsd 記憶體拷貝指令的指令。因此,為了便於統一處理,需要對 rep movsd 做適當翻譯,使其在控制流圖上形成循環 。
6.緩衝區溢出漏洞的發現
根據字元串拷貝操作的不同,可以將緩衝區溢出漏洞分為以下 2 種情況: (1)調用不安全的函式(例如調用 strcpy 函式將源字元串拷貝到目的緩衝區)導致出錯; (2)循環寫記憶體出錯,指程式對目的緩衝區進行循環拷貝時,超出緩衝區邊界導致溢出 。
7.變數發現算法
本文直接利用 IDA PRO 的變數發現算法。該算法思想如下:記憶體的分布在編譯時已分配好,對程式變數的直接訪問通過絕對地址或棧幀指針的相對偏移來實現。通過分析代碼對記憶體的訪問,找出其絕對或相對地址的偏移,從而確定程式變數的起始地址和大小。
採用上述漏洞檢測模型,檢測微軟 MS03-020, MS04-011,MS06-055 等漏洞,能發現其緩衝區溢出問題。