李 超 胡建偉 崔艷鵬
(西安電子科技大學網(wǎng)絡(luò)與信息安全學院 陜西 西安 710071)
信息技術(shù)的快速發(fā)展使得計算機軟件在社會活動與工業(yè)生產(chǎn)中起著越來越重要的作用。同時,軟件規(guī)模與數(shù)量的快速增長給信息安全帶來了嚴峻的挑戰(zhàn)。信息系統(tǒng)中存在軟件漏洞是導致信息安全問題的重要原因。軟件漏洞通常指軟件系統(tǒng)在設(shè)計、實現(xiàn)、配置、運行等過程中,由操作實體有意或無意產(chǎn)生的缺陷、瑕疵或錯誤,它們以不同形式存在于信息系統(tǒng)的各個層次與環(huán)節(jié)中。為確保信息系統(tǒng)的安全,眾多研究人員對漏洞分析與防護問題進行了大量的研究工作。然而,由于馮·諾依曼計算機體系自身的缺陷,以及當前軟件系統(tǒng)的代碼規(guī)模和技術(shù)復(fù)雜度的急劇提升,并且在軟件生命周期的每個階段都需要人工參與,難免會引入一些錯誤,導致無法徹底清除軟件中存在的漏洞。
在不能完全杜絕漏洞存在的情況下,需對其進行分析與研究,以最小化漏洞所帶來的損失。由于大多數(shù)商業(yè)軟件都不公開源碼,并且二進制代碼是軟件的最終表現(xiàn)形式,分析二進制代碼可以更加全面和直接地找到軟件中存在的漏洞,因此,二進制漏洞分析技術(shù)更具有實用性。文獻[1]的數(shù)據(jù)顯示,2017年新增漏洞中,緩沖區(qū)溢出漏洞為數(shù)量最多的漏洞類型,占新增漏洞總量的18.06%,遠遠高于其他漏洞類型。因此,研究緩沖區(qū)溢出漏洞具有重要意義。
面對各類信息系統(tǒng)中存在的大量漏洞,CNNVD[2]等組織對漏洞進行統(tǒng)一的分類管理,評估漏洞的危害性并將其標記為不同危害等級,指導軟件廠商采取相應(yīng)的修復(fù)措施,從而減少漏洞帶來的威脅和損失。通常可利用的軟件漏洞具有很高的危害性,攻擊者往往通過這些漏洞控制目標系統(tǒng)。因此,在漏洞響應(yīng)過程中,需要快速甄別大量軟件錯誤中的可利用漏洞。雖然使用模糊測試技術(shù)發(fā)現(xiàn)軟件錯誤具有很好的效果,但由于漏洞類型的多樣性和漏洞形成機理的復(fù)雜性,漏洞可利用性的評估和利用數(shù)據(jù)的構(gòu)造通常需進行動態(tài)調(diào)試分析漏洞形成的細節(jié),這個過程由分析人員以手工方式完成,并且要求分析人員熟悉匯編語言等計算機底層原理。隨著軟硬件產(chǎn)品和應(yīng)用的快速增長,漏洞數(shù)量急劇攀升,2017年共發(fā)布漏洞信息13 417條,漏洞數(shù)量達到2016年的近2倍[1]。因此,傳統(tǒng)漏洞分析方式已難以應(yīng)對上述挑戰(zhàn)。
為提高軟件漏洞風險評估的效率,本文研究緩沖區(qū)溢出漏洞,提出一種面向二進制程序的自動化漏洞利用方法,通過構(gòu)建exploit證明漏洞的危害性。該方法首先使用符號執(zhí)行檢測漏洞,然后構(gòu)建路徑約束表達式和利用約束表達式,最后通過約束求解器求解得到exploit。
目前已有學者對自動化漏洞利用進行了研究,并取得了一定的進展。由Brumley等[3]提出的APEG基于補丁比對的方法定位程序中已修補的漏洞,通過分析補丁中添加的過濾條件,構(gòu)造不滿足過濾條件的輸入觸發(fā)漏洞。該方法無法適用于補丁中沒有添加過濾條件的情況,并且構(gòu)造的輸入只能進行拒絕服務(wù)攻擊。相對于APEG對補丁的分析,Avgerinos等[4]提出了基于源碼的漏洞自動挖掘和利用方法AEG,AEG使用預(yù)置條件的符號執(zhí)行找到程序漏洞,利用動態(tài)二進制插樁獲取程序運行時信息,構(gòu)建約束表達式,并求解得到可實現(xiàn)控制流劫持攻擊的利用數(shù)據(jù)。
為了能夠在無法獲取程序源碼的情況下自動構(gòu)造利用數(shù)據(jù),Heelan[5]提出了基于二進制程序的漏洞自動利用方法。該方法以可觸發(fā)漏洞的樣例作為輸入,通過代碼插樁定位到漏洞,并使用污點分析找到可用于存放攻擊代碼的可控內(nèi)存,構(gòu)建生成利用數(shù)據(jù)所需的約束表達式,最后求解得到控制流劫持攻擊利用數(shù)據(jù)。Cha等[6]提出的漏洞自動利用生成方法Mayhem使用混合符號執(zhí)行技術(shù),分析過程中符號執(zhí)行引擎在離線符號執(zhí)行與在線符號執(zhí)行間不斷切換,以減少內(nèi)存消耗,緩解狀態(tài)爆炸問題。此外,該方法使用基于索引的內(nèi)存模型優(yōu)化符號化內(nèi)存的加載提高系統(tǒng)效率。Wang等[7]提出自動化生成多樣性漏洞利用的方法PolyAEG,該方法以崩潰樣例為輸入,通過動態(tài)污點分析獲得程序執(zhí)行的相關(guān)信息,構(gòu)建污點傳播流圖和全局污點狀態(tài)記錄獲取程序中所有可能被控制的劫持點、跳板指令和內(nèi)存區(qū)域,最后利用不同的跳轉(zhuǎn)指令和可控制內(nèi)存區(qū)域構(gòu)造多樣性的利用樣本。Huang等[8]提出的CRAX同樣以崩潰樣例為輸入對程序進行全系統(tǒng)模擬的符號執(zhí)行分析,分析過程中對漏洞利用不相關(guān)的庫函數(shù)或內(nèi)核函數(shù)進行具體執(zhí)行,以優(yōu)化符號執(zhí)行,提高處理速度。該方法可適用于Microsoft office word等規(guī)模較大的應(yīng)用程序。
相對于上述面向控制流的利用方法,Hu等[9]提出了面向數(shù)據(jù)流的自動利用方法FlowStitch,利用內(nèi)存錯誤修改程序數(shù)據(jù)流中的關(guān)鍵變量,可達到敏感信息泄露或提權(quán)的攻擊效果。該方法可實現(xiàn)敏感信息泄露,因此實用性較強。其缺點是需要能觸發(fā)內(nèi)存錯誤的輸入。由于堆管理機制的復(fù)雜性,導致堆漏洞利用的難度相對較大,Revery[10]對堆漏洞自動化利用問題進行了探索,在19個測試程序中可成功對9個程序生成利用。此外,NAVEX[11]對Web應(yīng)用漏洞自動構(gòu)造利用數(shù)據(jù),可成功利用SQL注入和XSS漏洞,該方法與二進制漏洞利用有較大的差別。
綜上所述,APEG和AEG分別依賴于補丁和源碼檢測漏洞;Mayhem使用符號執(zhí)行檢測漏洞,采用具體化部分符號變量的方法減少搜索空間,但可能導致漏洞不可利用;文獻[5]和文獻[7-9]均依賴于已知的崩潰輸入,無法自動檢測程序中存在的漏洞。此外,上述方法未考慮進程中不存在空間足以容納shellcode的可控內(nèi)存塊的情況,構(gòu)造利用的適用性較差。本文所提方法使用符號執(zhí)行檢測漏洞,通過切片減少狀態(tài)數(shù)量,并改進漏洞利用時shellcode存放方式,可提高系統(tǒng)適用性。
緩沖區(qū)溢出漏洞產(chǎn)生的原因是程序未正確檢查用戶輸入數(shù)據(jù)的長度是否超過目標緩沖區(qū)的大小,向緩沖區(qū)寫入過多數(shù)據(jù)覆蓋了內(nèi)存中其他數(shù)據(jù),可能導致控制流劫持。通過緩沖區(qū)溢出劫持控制流的常見方法包括覆蓋棧中函數(shù)返回地址和覆蓋函數(shù)指針。利用代碼注入或代碼復(fù)用[12]可實現(xiàn)執(zhí)行任意代碼。代碼注入將一段攻擊代碼寫入進程空間,之后劫持控制流到攻擊代碼執(zhí)行;代碼復(fù)用將內(nèi)存中已有的代碼片段拼接成可實現(xiàn)特定功能的攻擊鏈進行攻擊。本文主要研究代碼注入攻擊的自動化。
本文基于二進制分析框架angr[13]設(shè)計并實現(xiàn)緩沖區(qū)溢出漏洞自動利用原型系統(tǒng)AutoExp(Automatic Exploitation),該系統(tǒng)以漏洞程序為輸入,使用符號執(zhí)行[14]檢測漏洞,通過構(gòu)建約束表達式和約束求解生成exploit。以exploit作為程序輸入可觸發(fā)漏洞,并利用漏洞達到獲取系統(tǒng)控制權(quán)、運行任意代碼或竊取數(shù)據(jù)等目的。
如圖1所示,自動化生成exploit包括4個步驟:1) 預(yù)處理。為了減小漏洞檢測過程中符號執(zhí)行的狀態(tài)空間,首先掃描目標程序中危險函數(shù)調(diào)用位置,然后通過程序切片技術(shù)獲取危險函數(shù)調(diào)用位置到程序入口點的代碼切片。2) 漏洞檢測。針對上一步得到的切片進行符號執(zhí)行,記錄每個狀態(tài)的路徑約束、寄存器和符號內(nèi)存信息。同時,每運行一步均檢測是否存在包含漏洞的狀態(tài)。3) 構(gòu)建利用約束。找到漏洞后,判斷漏洞的可利用性,通過構(gòu)建shellcode約束將可控內(nèi)存區(qū)域的值約束為shellcode以實現(xiàn)攻擊代碼注入,構(gòu)建EIP約束將EIP寄存器的值約束為shellcode存放地址以實現(xiàn)控制流劫持。4) 約束求解。使用約束求解器求解路徑約束和利用約束,若有解則成功生成exploit。
圖1 漏洞自動利用系統(tǒng)設(shè)計
漏洞自動化利用的前提條件為找到程序中存在的漏洞,本文采用符號執(zhí)行檢測漏洞。符號執(zhí)行以符號變量代替具體值作為程序輸入,并動態(tài)模擬執(zhí)行程序中的指令,在執(zhí)行過程中記錄寄存器和內(nèi)存狀態(tài)。當遇到分支語句時,復(fù)制程序狀態(tài)以便繼續(xù)分析所有分支,并構(gòu)建路徑約束表達式記錄到達不同分支的路徑信息。符號執(zhí)行過程中,根據(jù)不同漏洞模型設(shè)置違例斷言可檢測程序中存在的漏洞。
由于符號執(zhí)行分析過程中每一個分支語句都可能導致新增一條路徑,所以路徑數(shù)量可能按指數(shù)級別增長,即存在狀態(tài)爆炸問題。為了緩解狀態(tài)爆炸問題,并且使分析過程更具有針對性,本文提出基于危險函數(shù)切片的方法獲取包含危險函數(shù)調(diào)用的程序切片,符號執(zhí)行時根據(jù)切片剔除無關(guān)路徑。
2.1.1預(yù)處理
控制流劫持漏洞的利用主要關(guān)注漏洞脆弱點和控制流劫持點[15],漏洞脆弱點指導致漏洞產(chǎn)生的函數(shù)或指令,而控制流劫持點指程序控制流被輸入數(shù)據(jù)控制的指令。緩沖區(qū)溢出漏洞多是由于程序中使用了危險函數(shù),并且未對用戶輸入數(shù)據(jù)進行嚴格的檢查所導致的。因此,緩沖區(qū)溢出漏洞的脆弱點往往為危險函數(shù)調(diào)用位置。常見危險函數(shù)如表1所示。
表1 危險函數(shù)列表
預(yù)處理過程如算法1所示,首先通過靜態(tài)分析獲取程序中的脆弱點位置。具體方法為,根據(jù)預(yù)先定義的危險函數(shù)名列表unsafe_func_name查找程序鏈接表PLT(Procedure Linkage Table)得到危險函數(shù)地址unsafe_func_addr;根據(jù)地址查找控制流圖CFG(Control Flow Graph)得到所有危險函數(shù)節(jié)點unsafe_nodes,獲取危險函數(shù)節(jié)點的前驅(qū)節(jié)點即可得到危險函數(shù)調(diào)用點地址列表unsafe_callsites。接著對危險函數(shù)調(diào)用點進行程序切片[16],得到從程序入口點到危險函數(shù)調(diào)用點的切片。具體方法為,分析程序數(shù)據(jù)依賴關(guān)系和控制依賴關(guān)系構(gòu)建數(shù)據(jù)依賴圖ddg(Data Dependence Graph,DDG)和控制依賴圖cdg(Control Dependence Graph,CDG),根據(jù)ddg和cdg使用輕量級污點分析[17]從危險函數(shù)調(diào)用點target進行后向切片得到切片bk_slice。
算法1預(yù)處理
輸入:目標程序program, 危險函數(shù)名列表unsafe_func_name
輸出:程序切片bk_slice
1 plt = get_plt(program)
// 獲取程序 plt 信息
2 cfg = create_cfg(program)
// 構(gòu)建程序CFG
/* 獲取每個危險函數(shù)的調(diào)用點 */
3 for fname in unsafe_func_name:
4 unsafe_func_addr = plt[fname]
// 根據(jù)CFG得到程序中所有危險函數(shù)節(jié)點
5 unsafe_nodes = cfg.get_all_nodes(unsafe_func_addr)
// 獲取危險函數(shù)節(jié)點的前驅(qū)節(jié)點得到危險函數(shù)調(diào)用點
6 for node in unsafe_nodes:
7 unsafe_callsites.append(node.predecessors.addr)
/* 根據(jù)危險函數(shù)調(diào)用點進行切片 */
8 ddg = create_ddg(cfg)
// 構(gòu)建DDG
9 cdg = create_cdg(cfg)
// 構(gòu)建CDG
10 for target in unsafe_callsites:
11 bs=create_backward_slice(cfg, ddg, cdg, target)
//切片
12 bk_slice.append({‘target’: addr, ‘slice’: bs})
13 return bk_slice
2.1.2漏洞檢測
符號執(zhí)行引擎[13]在模擬運行程序時,以狀態(tài)(state)表示程序的執(zhí)行過程,其中記錄了程序的執(zhí)行路徑和內(nèi)存、寄存器等運行時信息;使用模擬管理器(SimulationManager)控制符號執(zhí)行過程,可管理不同類型的狀態(tài)和使用搜索策略探索程序狀態(tài)空間。SimulationManager通過stash管理active、found、unconstrained等不同類型的狀態(tài),active state為當前正執(zhí)行的狀態(tài),found state為通過設(shè)定探索目標所找到的狀態(tài)unconstrained state為不受約束的狀態(tài)。
符號執(zhí)行以符號值替換用戶輸入,如果程序中存在緩沖區(qū)溢出漏洞,當程序運行到漏洞劫持點時EIP寄存器將被符號化,由于符號化變量不是具體值,符號執(zhí)行引擎不能確定下一步需執(zhí)行的指令,導致無法繼續(xù)運行,此時狀態(tài)類型為unconstrained。因此,通過判斷符號執(zhí)行過程中是否存在unconstrained狀態(tài)即可檢測緩沖區(qū)溢出漏洞。
算法2描述了漏洞檢測方法。符號執(zhí)行過程中,在程序入口點與脆弱點間運行時根據(jù)預(yù)處理得到的切片進行狀態(tài)修剪,剔除切片之外的路徑,以減少狀態(tài)數(shù)量。具體方法為,符號化用戶輸入并創(chuàng)建模擬管理器simgr,接著獲取切片bk_slice中脆弱點地址(即危險函數(shù)調(diào)用點)target作為符號執(zhí)行的探索目標。同時設(shè)定狀態(tài)修剪策略函數(shù)drop_states_not_in_slice,該函數(shù)判斷active stash中的狀態(tài)是否在切片范圍內(nèi),若是則返回False,即保留該狀態(tài);否則返回True,丟棄該狀態(tài)。當找到脆弱點狀態(tài)后,丟棄active中所有狀態(tài),并把脆弱點狀態(tài)從found stash移動到active stash以便從脆弱點繼續(xù)運行;找到脆弱點之后繼續(xù)執(zhí)行,當unconstrained stash非空時則表明存在控制流劫持點,即找到漏洞狀態(tài)vul_state。
算法2漏洞檢測
輸入:目標程序program, 切片bk_slice
輸出:漏洞狀態(tài)vul_state
1 for slice in bk_slice:
/*程序入口點到脆弱點間運行時根據(jù)切片進行狀態(tài)修剪*/
2 sym_input = symbolic(input)
// 符號化用戶輸入
3 init_state = entry_state(program, sym_input)
//創(chuàng)建初始狀態(tài)
4 simgr = simulation_manager(init_state)
// 創(chuàng)建模擬管理器
5 target = slice[‘target’]
// 獲取脆弱點地址
// 以脆弱點為目標進行符號執(zhí)行,并設(shè)定狀態(tài)修剪策略
6 simgr.explore(find=target, filter=drop_states_not_in_slice)
// 若找到脆弱點狀態(tài),則使active stash中只包含該狀態(tài)
7 if simgr.found not NULL:
8 simgr.drop(stash=′active′)
9 simgr.move(from_stash=″found″, to_stash=″active″)
/* 從脆弱點繼續(xù)探索,直到找到unconstrained狀態(tài) */
10 while simgr.unconstrained is NULL:
11 simgr.step()
// 向前執(zhí)行一步
12 vul_state = simgr.unconstrained
13 return vul_state
2.2.1利用約束構(gòu)建
進程空間中存在可控內(nèi)存塊是進行代碼注入攻擊的必要條件。進程中可控內(nèi)存區(qū)域并非都是連續(xù)的,為了找到能存放shellcode的可控內(nèi)存塊,需獲取可控內(nèi)存塊信息,包括內(nèi)存塊的起始地址和大小。獲取可控內(nèi)存塊信息的方法如算法3所示,首先獲取漏洞狀態(tài)vul_state中符號化內(nèi)存地址列表sym_addrs;然后根據(jù)地址是否連續(xù)來統(tǒng)計內(nèi)存塊的大小size,并記錄內(nèi)存起始地址buf_start;最后將內(nèi)存塊按空間從大到小的順序排序。
算法3獲取可控內(nèi)存塊信息
輸入:漏洞狀態(tài)vul_state
輸出:符號化內(nèi)存塊sym_bufs
// 獲取符號化內(nèi)存地址列表
1 sym_addrs = find_symbolic_addr(vul_state)
2 while sym_addrs not NULL:
3 size = 0
// 設(shè)定內(nèi)存塊初始大小
4 buf_start = sym_addrs[0]
// 記錄內(nèi)存塊起始地址
/* 統(tǒng)計連續(xù)內(nèi)存地址組成的內(nèi)存塊大小 */
5 while True:
6 if not buf_start + size in sym_addrs:
7 break
8 sym_addrs.remove(buf_start + size)
//刪除已處理地址
9 size += 1
10 sym_bufs.append({‘a(chǎn)ddr’: buf_start, ‘size’: size})
11 sorted_by_size(sym_bufs)
// 根據(jù)內(nèi)存塊大小排序
12 return sym_bufs
當進程空間中不存在足以容納shellcode的可控內(nèi)存塊時,現(xiàn)有方法將無法成功構(gòu)建exploit。如圖2所示,為提高漏洞自動利用系統(tǒng)的適用性,AutoExp把shellcode分段存放在多個可控內(nèi)存塊,并使用跳轉(zhuǎn)指令連接不同內(nèi)存塊中的攻擊代碼,從而完成攻擊過程。
圖2 shellcode分段存放
對shellcode分段時應(yīng)確保指令的完整性,本文將機器碼形式的shellcode反匯編為匯編指令,分段時以指令為基本單位。算法4具體描述了分段的方法,首先反匯編shellcode為匯編指令asm,根據(jù)可控內(nèi)存塊信息與shellcode大小確定分段數(shù)量和每個片段的長度segs_len;接著根據(jù)片段長度對shellcode進行分段;最后在除末尾片段外的所有片段后添加跳轉(zhuǎn)指令jmp_ins。
算法4shellcode分段
輸入:符號化內(nèi)存塊sym_bufs, 攻擊代碼shellcode
輸出:shellcode片段sc_segments
1 asm = disassemble(shellcode)
// 反匯編shellcode
/* 確定每個片段的長度 */
2 length = 0, n = 0
// 初始化片段長度length和內(nèi)存塊序號n
3 for ins in asm:
// 若當前內(nèi)存塊還能容納指令ins,則劃分在該內(nèi)存塊
4 if length + ins.size <= sym_bufs[n].size-len(jmp_ins):
5 length += ins.size
6 else:
// 否則,當前內(nèi)存塊已存滿,考慮下一個內(nèi)存塊
7 segs_len.append(length)
8 length = 0, n += 1
/* 在除末尾片段外的所有片段后添加跳轉(zhuǎn)指令 */
9 for len in segs_len not last:
10 sc_segments.append(shellcode[:len] + jmp_ins)
11 shellcode = shellcode[len:]
// 刪除已處理的數(shù)據(jù)
12 sc_segments.append(shellcode)
13 return sc_segments
利用緩沖區(qū)溢出漏洞進行代碼注入攻擊需要兩個步驟,分別是把攻擊代碼寫入進程空間和劫持程序控制流到攻擊代碼處,該過程可通過構(gòu)建shellcode約束表達式和EIP約束表達式的方法實現(xiàn)自動化。如算法5所示,首先需把shellcode片段寫入進程中對應(yīng)的可控內(nèi)存塊,實現(xiàn)方法為依次加載可控內(nèi)存塊sym_bufs[n],并構(gòu)建約束表達式將內(nèi)存塊中數(shù)據(jù)約束為對應(yīng)的shellcode片段sc_segments[n];接著構(gòu)建約束表達式將漏洞狀態(tài)的EIP寄存器值約束為shellcode存放內(nèi)存的起始地址sym_bufs[0].addr。
算法5構(gòu)建利用約束
輸入:漏洞狀態(tài)vul_state, 符號化內(nèi)存塊sym_bufs, shellcode片段sc_segments
輸出:約束表達式constraints
/* 依次約束可控內(nèi)存塊中數(shù)據(jù)為對應(yīng)shellcode片段的值 */
1 for n in range(len(sc_segments)):
// 加載可控內(nèi)存塊
2 memory = vul_state.load_mem(sym_bufs[n].addr)
// 將可控內(nèi)存塊中數(shù)據(jù)約束為shellcode
3 vul_state.add_constraints(memory == sc_segments[n])
4 constraints.append(memory == sc_segments[n])
/* 約束EIP寄存器的值為shellcode起始地址 */
5 vul_state.add_constraints(vul_state.eip == sym_bufs[0].addr)
6 constraints.append(vul_state.eip == sym_bufs[0].addr)
7 return constraints
2.2.2約束求解
對于上述構(gòu)建的路徑約束表達式和利用約束表達式,使用支持SMT求解理論的Z3求解器[18]進行求解。若有解,則得到一個可觸發(fā)漏洞并進行代碼注入攻擊的exploit;若無解,則表明檢測到的漏洞無法利用。
實驗運行環(huán)境為Intel Core i7-7700HQ CPU,主頻2.8 GHz,4 GB內(nèi)存, Ubuntu 16.04 64 bits系統(tǒng),測試程序使用gcc 5.4.0 編譯。本文不考慮漏洞緩解機制的繞過,編譯時不啟用NX和Stack Canary保護,同時關(guān)閉系統(tǒng)ASLR保護[19]。為驗證系統(tǒng)的有效性,本文設(shè)置兩組實驗,分別用于驗證漏洞檢測效果和測試自動生成利用數(shù)據(jù)的有效性。
實驗選取以下3個已披露漏洞作為測試樣本,分別使用angr和本文實現(xiàn)的AutoExp檢測目標程序中存在的漏洞,并記錄兩種方法檢測漏洞所需時間。實驗結(jié)果如表2所示,表中第三列和第四列分別為目標程序的基本塊數(shù)量和使用AutoExp進行預(yù)處理所得切片的基本塊數(shù)量。從實驗數(shù)據(jù)可知,使用切片技術(shù)對程序進行預(yù)處理可減少待分析程序的基本塊數(shù)量,從而有效減小符號執(zhí)行分析的復(fù)雜度。
表2 漏洞檢測結(jié)果
表2使用兩種方法檢測漏洞所需時間。實驗數(shù)據(jù)表明,當程序結(jié)構(gòu)較簡單且基本塊數(shù)量較少時,直接使用angr進行符號執(zhí)行分析能更快地檢測到漏洞;而隨著程序基本塊數(shù)量的增大,angr檢測漏洞所需時間遠遠多于AutoExp。具體原因如圖3所示,AutoExp在預(yù)處理階段構(gòu)建CFG需花費較多的時間,但是預(yù)處理可避免分析與漏洞無關(guān)的路徑,從而在符號執(zhí)行階段花費的時間相對angr要少,且消耗的內(nèi)存也隨之減少。測試結(jié)果中,AutoExp檢測PSUtils中漏洞所需時間為127.05 s,而angr所需時間是AutoExp的15倍,分析代碼發(fā)現(xiàn)程序中switch語句會導致狀態(tài)爆炸問題,該語句與漏洞路徑無關(guān),進行代碼切片能避免分析該語句,使得符號執(zhí)行效率得以提高。由此可見,對于結(jié)構(gòu)較復(fù)雜的程序而言,本文所提方法可極大提高漏洞檢測的效率。
圖3 漏洞檢測時間對比
為驗證系統(tǒng)自動生成exploit的適用性,以圖4中漏洞程序memo和上述3個漏洞程序進行測試,測試時選取長度為25 bytes的shellcode利用漏洞。memo程序第14行調(diào)用read函數(shù)獲取用戶輸入到緩沖區(qū)buf中,由于第15行strcpy函數(shù)往數(shù)組title中寫入過多的數(shù)據(jù)而導致緩沖區(qū)溢出漏洞,從而覆蓋相鄰內(nèi)存中函數(shù)指針func_ptr,利用該漏洞可劫持控制流進行代碼注入攻擊。
1typedefstructmemo{2charcontent[22];3time_ttime;4chartitle[15];5int(?func_ptr)();6}memorandum;7memorandummemo;8intmessage(){9printf(″%s″,memo.title);10}11intmain(){12charbuf[22];13memo.func_ptr=message;14read(0,buf,sizeof(buf));15strcpy(memo.title,buf);16read(0,buf,sizeof(buf));17strcpy(memo.content,buf);18time(&memo.time);19memo.func_ptr();20}
圖4 緩沖區(qū)溢出漏洞程序memo
實驗結(jié)果如表3所示,AutoExp可成功利用4個漏洞,而Mayhem[7]由于未考慮可控內(nèi)存塊不足以容納shellcode的情況,因此無法成功利用memo中漏洞。實驗結(jié)果表明,本文所提方法相對Mayhem具有更好的適用性。
表3 漏洞自動利用結(jié)果對比
下面具體分析AutoExp自動生成exploit的效果。程序memo中存在content[22] 和title[15] 兩塊連續(xù)可控內(nèi)存,利用過程中選取不同長度的shellcode,AutoExp可根據(jù)內(nèi)存塊能否容納shellcode采取不同的exploit構(gòu)造方法。如圖5所示,當選取長度為21 bytes的shellcode利用漏洞時,由于存在能容納shellcode的可控內(nèi)存塊,故選擇可控內(nèi)存塊content[22]注入shellcode構(gòu)造利用。
圖5 shellcode連續(xù)存放
如圖6所示,當選取長度為25 bytes的shellcode利用漏洞時,由于不存在可容納shellcode的內(nèi)存塊,故將shellcode分段存放在content[22]和title[15]中,并用指令“eb 07”實現(xiàn)不同分段間的跳轉(zhuǎn)。
圖6 shellcode分段存放
上述結(jié)果表明,本文所提方法可根據(jù)漏洞程序中可控內(nèi)存塊的大小和所選取的shellcode調(diào)整exploit構(gòu)造方法,具有更好的適用性。
本文對漏洞自動化利用方法進行了總結(jié),提出一種基于符號執(zhí)行的緩沖區(qū)溢出漏洞自動化利用方法。該方法采用危險函數(shù)切片減少漏洞檢測中符號執(zhí)行的狀態(tài)數(shù)量,可有效緩解狀態(tài)爆炸問題,提高符號執(zhí)行的效率。在漏洞利用階段,當進程中不存在空間足夠的可控內(nèi)存塊時,將shellcode進行分段存放,具有更好的適用性。本文實現(xiàn)了緩沖區(qū)溢出漏洞利用的自動化,后續(xù)工作可進一步研究其他類型漏洞的自動化利用,以及自動繞過程序和系統(tǒng)中部署的漏洞緩解機制。