本 研 究 採 用 的 開 發 環 境 為 Intel i3-550 3.2GHz 、 8GB Memory、 作 業 系 統 為 Ubuntu18.08 (64 位元)、gcc 版本為 7.5.0,並利用 angr8 作為符號執行引擎。我們的實驗 主要為探討本研究開發的原位符號執行引擎與傳統符號執行的效能差異,還有對於不同 過就算在已知變數 encrypted 的情況下,依舊難以利用簡易的推演得到使結果引向正確 的輸入變數 input,因此我們使用不同的符號執行進行求解,使得程序能夠到達目標位
16
圖 8 由上至下分別呈現原位符號執行時遇到中斷點、符號執行求解成功以及成功 同步回具體執行時的結果。
圖 8. 執行結果示意圖
本實驗比較了不同符號執行分別在不同輸入長度的變數 encrypted 下的平均求解時 間,也就是本系統原位符號執行與傳統符號執行 (angr) 的執行效能,對於每種輸入長
17
圖 9. 符號執行間的效能比較
經由上述實驗,我們可以發現在相同的輸入長度下,原位符號執行所花費的求解時 間比angr 還要少,代表原位符號執行相比於 angr 有較高的執行效能,且隨著輸入長度 的增加,求解的時間差愈加明顯,其原因可能為越長的輸入長度可以突顯原位符號執行 中輕量化的優點,減少多餘的額外函數影響。而目標程序若有大量的第三方函數則會造 成擷取程式碼的不易,導致效能降低。
真實程式原位符號化執行分析
在本節中,我們將針對不同大小的目標函數使用原位符號執行做符號化分析,證實 在 實 體環 境應 用的 可行 性 。本 實驗 比較 了不 同 的函 數呼 叫, 包括 bash5.0 [9] 的 set_shell_name()和 array_shift(),以及 gzip1.10 中的 huft_build()和 built_tree(),我們將對 其中的輸入字串做符號化並觀測,如表 2 和表 3 所示。
0 10 20 30 40 50 60 70 80
5 10 20 30 50 100 200
時間(s)
輸入長度(bytes) 原位符號執行 angr
18
表 2. bash5.0 中目標函數的符號化分析 分析對象
bash5.0 函數
總時間 記憶體設置時間 符號執行時間 指令數量
set_shell_name() 1.98 s 0.36 s 0.45 s 76 array_shift() 3.31 s 0.30 s 1.41 s 207
表 3. gzip1.10 中目標函數的符號化分析 分析對象
gzip1.10 函數 總時間 記憶體設置時間 符號執行時間 指令數量 huft_build() 4.05 s 0.40 s 1.98 s 298
built_tree() 5.51 s 0.31 s 3.43 s 154
set_shell_name()是針對使用者使用的殼程式做名稱設置,若配置為外部的殼程式名 稱,可能會導致程程序發生錯誤;array_shift()會將輸入的陣列依照相對應的參數做位移,
若 flags 參數被設置異常內容,會導致程序直接移除此一元素;huft_build()會利用給定 的代碼長度製作一組表,用來解碼該組代碼,若輸入超過預設長度的代碼則會回傳錯誤 訊息;build_tree()為一創建霍夫曼樹的演算法,我們試著改變樹記述子 (tree descriptor) 的內容值,使霍夫曼樹的高度可以縮限在一定範圍內。經過實測後皆有利用原位符號執 行至相對應位置,並同步回具體執行之中。其中 built_tree()使用較長的符號執行時間,
我們推測是結構較大的符號執行路徑較為複雜所導致。
19
應用實例
傳統符號執行大多難以在現實世界中運用,而我們將測試原位符號執行於汙染追蹤 分析及反制動作生成的應用實例。本節實驗採用的開發環境則為Intel i7-4770 3.2GHz、
32GB Memory。
符號執行結果影響之多路徑汙染源追蹤比較
汙點追蹤 (taint checking) [10] [11] [12] 可以避免使用者的資料如帳號密碼外洩至 系統其他部份,只要能被外部修改的資料就潛在著此一資安危機。本實驗將探討原位符 號執行對於汙點追蹤的可行性,基本原理是將欲標記為汙點處的資料先符號化,再經歷 符號執行引擎查找是否有其餘敏感變數被符號化,表示此一變數與汙點資料有所接觸。
如果有多條路徑皆會到達目標點,則可以觀察使用者在到達目標點前在不同路徑之下各 自會汙染哪些變數;如果某一變數只於特定路徑被汙染到,則開發者可以觀察在這一路 徑是否有資料外洩的問題或記憶體區段錯誤的產生,可運用於程式除錯方面。
與傳統符號執行的差別在於本研究可以跳過複雜的前置流程,直接利用起始位置的 狀態進行分析。我們以一個汙染追蹤為例,如圖 10 所示,在第 9 行的 while 迴圈之中,
我們將對變數a 分析處理得到兩變數 count_odd 與 count_even;有多種可能可以使程序 到達第19 行的目標位址,但這其中卻不是每種可能都會進入第 17 行的判斷條件;在此 我們將測試不同目標條件下被汙染的變數總位元數,如表 4 所示。
20
int main(int argc, char *argv[]){
int a;
if(count_odd > 5 && count_even < 7){
if(count_even == 10)
21
在本例中,汙染5bytes 與 9bytes 的差別在於是否有額外汙染到變數 x。若變數長度 為1 byte,所有可造成的分支數共有 256 種,其中可到達目標位置的分支數有 37 種,
而污染5 bytes 與 9 bytes 的分支數各占 36 與 1 種。在變數長度為 2bytes 的實驗中,理 論上應該有 65536 種分支被找尋出來,不過因為所需記憶體過於龐大所以只查找出 12148 種分支;其中可到達目標位置的分支數有 847 種,而污染 5 bytes 與 9 bytes 的分 支數各占792 與 55 種。值得注意的是,雖然長度只成長一倍,但符號執行時間卻從 16 秒大幅提升至511 秒,大約為 30 倍,可見其複雜度大量提升。若要將原位符號執行系 統運用於汙染追蹤,現階段僅適用於較小的變數長度。
反制動作生成
本研究除了可以自動查找漏洞外,還可利用已知的漏洞產生反制行為。若已知目標 程序於執行至符號執行起始位置時會產生數條分支,假設運行其中的某特定分支會使得 程序崩潰,則此時我們可以使用符號執行使程序強迫該分支永遠不成立,以達到阻斷特 定功能的目的。如下圖 11 所示,若已知目標程序於執行至符號執行起始位置時會產生 數條分支,假設運行其中的某特定分支會使得程序崩潰,則此時我們可以使用符號執行 使程序強迫該分支永遠不成立,以達到阻斷特定功能的目的。
圖 11. 反制動作示意圖
22
char* parse(char *input){
input = (char*) malloc(sizeof(char) * 256);
input = read();
execute(parse(input));
23