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