Automatic Exploit Generation:漏洞利用自动化
漏洞利用是二進(jìn)制安全的核心內(nèi)容之一。當(dāng)安全研究員挖掘到一個(gè)新的漏洞時(shí),首先要做的事情就是嘗試寫POC和exploit。所謂POC,一般來說就是一個(gè)能夠讓程序崩潰的輸入,且能夠證明控制寄存器或者其他違反安全規(guī)則的行為。Exploit則條件更嚴(yán)格一些,是對(duì)漏洞的完整利用,通常以彈出一個(gè)shell作為目標(biāo)。
從程序到漏洞再到利用,這個(gè)過程需要對(duì)二進(jìn)制程序(或者加上源代碼)及其運(yùn)行過程進(jìn)行非常透徹的分析,往往需要安全研究員花費(fèi)數(shù)日的心血去研究。漏洞挖掘的過程自動(dòng)化程度已經(jīng)較高,工業(yè)界利用fuzzer可以得到大量讓程序崩潰的輸入,但這些輸入及相關(guān)漏洞并不全都是可利用的。從崩潰輸入到利用的過程,需要大量人工分析,如果這個(gè)工作也可以自動(dòng)化,那么將大大節(jié)省人的工作量,從而提升安全研究與防護(hù)的效率。
漏洞利用自動(dòng)化在學(xué)術(shù)界已經(jīng)不是一個(gè)新鮮的話題。有一個(gè)專有的名詞來定義這個(gè)領(lǐng)域:Automatic Exploit Generation。這個(gè)詞來源于2011年NDSS上CMU的David Brumley的一篇論文,這篇論文也可以說是漏洞利用自動(dòng)化的開山之作。經(jīng)過了六年的發(fā)展,AEG已經(jīng)是一個(gè)比較成熟的課題,也產(chǎn)生了大量非常實(shí)用的解決方案,但仍然有很多問題沒有解決,所以依然是二進(jìn)制安全研究領(lǐng)域的一個(gè)熱門方向。
這篇文章簡(jiǎn)單介紹漏洞利用自動(dòng)化的基本理念和方法,主要依據(jù)David Brumley 2011年發(fā)表的論文。文中所介紹的,是最簡(jiǎn)化的條件下漏洞利用問題的建模和求解,因此很多實(shí)際問題都沒有考慮。無(wú)論如何,作為AEG提出的開山之作,用這篇文章來建立漏洞利用自動(dòng)化的基本思路,是再好不過的。
0x00 從實(shí)際出發(fā)
講起棧溢出利用,懂點(diǎn)二進(jìn)制安全的人肯定非常熟悉。這是所有漏洞利用教程的第一篇,一個(gè)簡(jiǎn)單的由strcpy引起的棧溢出漏洞,通過覆蓋返回地址,跳轉(zhuǎn)到shellcode,從而能夠彈出shell。
仔細(xì)回想一下手動(dòng)利用的過程,最關(guān)鍵的是以下幾個(gè)步驟:
1)定位溢出點(diǎn),通常用不同長(zhǎng)度的輸入去試驗(yàn),看什么時(shí)候會(huì)產(chǎn)生崩潰——嚴(yán)格來說,這是漏洞挖掘的過程。
2)定位返回地址的位置,在有源代碼的情況下可以自行分析棧幀的布局,從而計(jì)算出返回地址的偏移,或者在無(wú)源代碼的情況下用一些工具去試驗(yàn),比如metasploit的pattern_create.rb。
3)精心構(gòu)造shellcode布局,在精確的返回地址的位置上放置我們的shellcode的起始地址,然后把shellcode的位置也安排好。如果一切計(jì)算無(wú)誤,那么將構(gòu)造好的輸入傳給漏洞程序,就會(huì)觸發(fā)漏洞,返回到shellcode的地址并執(zhí)行shellcode,順利彈出shell。
那么這個(gè)過程中,有哪些信息是需要人工去獲取的呢?
1)漏洞位置定位,主要表現(xiàn)為什么樣的輸入會(huì)讓程序崩潰觸發(fā)漏洞。
2)返回地址定位,應(yīng)該覆蓋哪一部分為要跳轉(zhuǎn)的地址。
3)棧的布局,此信息將指導(dǎo)如何精心構(gòu)造shellcode布局。
4)安排最后的整個(gè)輸入的布局,每個(gè)部分放在什么位置,要基于上面的信息進(jìn)行推理。
所以,如果我們要進(jìn)行漏洞自動(dòng)利用,那么就必須能夠自動(dòng)生成上述所有進(jìn)行利用必需的信息。
0x01 問題定義
我們要做的是一件什么事情?給AEG系統(tǒng)輸入一個(gè)程序,經(jīng)過該系統(tǒng)內(nèi)部的各種計(jì)算和運(yùn)行,就能夠輸出一個(gè)有效的利用輸入。這里的“有效”,姑且定義為能夠彈出shell。2011年的AEG還不具備只依靠二進(jìn)制程序就能產(chǎn)生利用的功能,所以我們?cè)偌右粋€(gè)條件,就是擁有這個(gè)二進(jìn)制程序的源代碼。所以我們要做的是,在僅有源代碼的情況下(AEG系統(tǒng)可自行編譯為二進(jìn)制),不需要人做任何事情,AEG系統(tǒng)就可以自動(dòng)生成一個(gè)有效的利用。
是不是聽起來很誘人?當(dāng)然,2011年的AEG還是一個(gè)非常簡(jiǎn)化的模型,還要有以下幾個(gè)條件:①漏洞類型限制為棧溢出或者格式化字符串漏洞;②利用類型限制為覆蓋返回地址的控制流劫持導(dǎo)致彈出shell;③沒有考慮任何系統(tǒng)的安全防護(hù)措施,包括棧保護(hù)、NX、ASLR等。④必須有程序的源代碼;⑤系統(tǒng)環(huán)境限制為基于x86的Linux系統(tǒng)。
我們首先來看一個(gè)實(shí)際的例子,來描述這個(gè)問題。所用的例子是iwconfig程序,有大約3400行C代碼。截取其中的一小段:
我們可以看到,15行有一個(gè)經(jīng)典的strcpy造成的棧緩沖區(qū)溢出。如果我們要對(duì)這個(gè)漏洞進(jìn)行利用,就需要完成以下4個(gè)步驟。
1. 定位漏洞位置。
這一步主要是通過分析源代碼完成,也就是說,我們要找到一個(gè)輸入,能夠讓程序順利地執(zhí)行到漏洞存在的那個(gè)點(diǎn),也就是找到從輸入到漏洞點(diǎn)的路徑。AEG利用符號(hào)執(zhí)行技術(shù)完成這件事情。
具體說來,就是iwconfig的這段代碼中有多個(gè)條件和循環(huán)語(yǔ)句,如果畫出控制流圖,我們可以發(fā)現(xiàn)程序路徑是非常非常多的,但是能夠到達(dá)15行的strcpy的路徑卻不多。順著路徑main->print_info->get_info,就可以到達(dá)15行,檢測(cè)到越界內(nèi)存錯(cuò)誤,而且可以發(fā)現(xiàn)錯(cuò)誤發(fā)生在變量ifr.ifr_name上。這些信息就是我們要用符號(hào)執(zhí)行去檢測(cè)的。
2. 獲取程序?qū)嶋H運(yùn)行時(shí)的棧布局及其他信息。
這一步需要二進(jìn)制程序,就用上一步生成的輸入去運(yùn)行,并且要得到運(yùn)行時(shí)的信息,例如漏洞函數(shù)名稱、溢出點(diǎn)的地址、棧的布局等等,這些信息是生成利用必備的。AEG通過動(dòng)態(tài)二進(jìn)制分析完成這一過程。
如果我們手動(dòng)分析這個(gè)漏洞,很顯然會(huì)得到下圖中的執(zhí)行時(shí)棧布局,這個(gè)信息對(duì)于我們判斷如何利用起到非常大的作用。比如說,我們可以獲知漏洞函數(shù)get_info的返回地址位于棧中的第幾個(gè)字節(jié)的偏移上。
3. 根據(jù)上述信息生成利用。
這就要對(duì)利用進(jìn)行形式化描述,將漏洞利用編程一個(gè)形式化驗(yàn)證問題,用符號(hào)執(zhí)行產(chǎn)生生成利用的約束公式,然后用求解器進(jìn)行求解。通俗點(diǎn)說,就是我們要把一些條件轉(zhuǎn)化為形式化的描述,變成路徑條件的約束。
這些條件在例子中包括:1)ifr.ifr_name必須包含shellcode;2)覆蓋的返回地址必須包含shellcode地址。我們把這兩個(gè)條件加在路徑的約束上,那么約束求解器就會(huì)求解出滿足這兩個(gè)條件的答案,也就是能夠生成利用的答案。
4. 對(duì)利用進(jìn)行驗(yàn)證。
約束求解器求出來的滿足的答案就是我們要的利用輸入。最后AEG會(huì)用這個(gè)輸入去運(yùn)行以驗(yàn)證。如果求解不出來就重新去尋找另一個(gè)漏洞嘗試進(jìn)行利用。
0x02 符號(hào)執(zhí)行
在講解AEG問題的形式化定義之前,我們先來簡(jiǎn)單介紹一下符號(hào)執(zhí)行。整個(gè)AEG方案的基礎(chǔ)和關(guān)鍵就是符號(hào)執(zhí)行,正是因?yàn)橛辛诉@個(gè)技術(shù),我們才能夠?qū)EG變成一個(gè)形式化的問題去研究。
將程序的執(zhí)行抽象為很多條不同的路徑,其分支在于代碼中的條件跳轉(zhuǎn)。例如下面這段非常簡(jiǎn)單的C程序,在第3行就有一個(gè)分支條件,這里的條件會(huì)讓路徑分開為2條,一條是條件a>1為true,一條是a>1為false。符號(hào)執(zhí)行就是把程序的執(zhí)行用符號(hào)化的公式進(jìn)行表示,直到最后才求解出滿足約束的值,從而達(dá)到遍歷程序所有約束的目的。
比如說,這個(gè)示例程序中,符號(hào)執(zhí)行器會(huì)先將輸入a定義為一個(gè)符號(hào)化的int值,其大小為32字節(jié),這32字節(jié)的每一位都是符號(hào)化的。然后繼續(xù)運(yùn)行,會(huì)分支出兩個(gè)條件,一個(gè)是a>1為true的,這條路徑的條件約束就是“a>1”,走這條路徑的話,最后輸出的b的約束條件就是“a-1”。同理,另一條路徑的條件約束就是“a<=1”,輸出b的約束條件就是“a+1”。
如果我們想要程序能夠走到兩條路徑,達(dá)到100%執(zhí)行覆蓋率,那很簡(jiǎn)單,求解器會(huì)隨便取兩個(gè)值,一個(gè)是大于1的值,另一個(gè)是小于等于1的值,這兩個(gè)輸入就足夠覆蓋兩條路徑。
如果我們想要b輸出一個(gè)特定值,也很簡(jiǎn)單。假如我們想要b輸出一個(gè)值“2”,那么我們有兩條約束:1)a>1且a-1=2;2)a<=1且a+1=2。1)可計(jì)算出a=3,2)可計(jì)算出a=0,也就說約束求解器會(huì)給出兩個(gè)答案:a=0或a=3。又如我們想讓b輸出0,那么兩條約束:1)a>1且a-1=0;2)a<=1且a+1=0。這就只能算出一個(gè)答案a=-1。
有了符號(hào)執(zhí)行,我們可以把非常復(fù)雜的程序變成如同例子一樣的形式化的約束求解問題,于是可以解決很多以前解決不了的問題,只要我們能夠把這個(gè)問題轉(zhuǎn)化為約束求解問題,進(jìn)行形式化的建模。當(dāng)然,用于真實(shí)的、大型的程序的符號(hào)執(zhí)行是非常復(fù)雜的,而且求解器又是另外一個(gè)非常難的研究領(lǐng)域,具體的細(xì)節(jié)這里不表,知道這些就足夠了。
0x03 形式化建模
現(xiàn)在讓我們回到AEG問題的形式化建模上來。一個(gè)漏洞利用的生成,其實(shí)主要就是兩個(gè)條件:1)有漏洞;2)可以利用。這兩個(gè)條件看起來是廢話,但如果細(xì)究,其實(shí)是包含很多具體的約束條件的。我們先將可利用狀態(tài)用兩個(gè)布爾謂詞定義:有漏洞的執(zhí)行路徑謂詞和控制流劫持利用謂詞。你可以把這兩個(gè)謂詞理解為“兩個(gè)條件”的形式化定義。其中,定義了漏洞存在的條件,定義了控制流劫持的條件。那么,一個(gè)有效的利用exploit,就是一個(gè)能夠滿足下面這個(gè)布爾表達(dá)式的輸入:
其實(shí)這個(gè)式子很好理解。一個(gè)輸入,既能滿足觸發(fā)漏洞條件,又能滿足利用條件,當(dāng)然就是一個(gè)可用的利用。但是關(guān)鍵問題是,這兩個(gè)條件如何定義。
首先是不安全路徑謂詞。這個(gè)謂詞表示執(zhí)行的路徑違反了一個(gè)安全特性,我們用表示。這個(gè)是什么,要根據(jù)漏洞的類型去定義。比如說,C程序常見的一些安全問題包括越界讀寫、不安全的格式化字符串等等,所謂的安全特性就是一些安全規(guī)則的定義。所以,可以理解為對(duì)漏洞的定義和描述,那么找到什么樣的漏洞,取決于安全特性如何定義。在符號(hào)執(zhí)行過程中,就是定義了能夠到達(dá)漏洞所在路徑的條件。比如我們實(shí)例中展示的那一種緩沖區(qū)溢出漏洞,復(fù)制的輸入字符串大小顯然超過了緩沖區(qū)大小的界限,這就是一個(gè)明顯的漏洞條件。
然后是利用謂詞。定義了攻擊者的邏輯,也就是說如果能夠劫持EIP,攻擊者會(huì)做什么。例如,如果攻擊者只想讓程序崩潰,謂詞就可以是簡(jiǎn)單的“將eip設(shè)置為無(wú)效地址,在獲取控制之后”。然而我們的目的是彈出shell,那么就會(huì)定義:①shellcode必須存在于內(nèi)存中;②EIP必須要指向shellcode的起始位置。如果滿足了和之前的就是最終的結(jié)果。如果不滿足,說明漏洞是不可利用的。
這樣一來,我們就把問題轉(zhuǎn)換為了將漏洞類型建模為,和將利用類型建模為的過程。那么問題的關(guān)鍵就在于,如何定義這兩個(gè)條件,尤其是,需要對(duì)各種漏洞利用方式非常熟悉。當(dāng)然這里的利用方式是最簡(jiǎn)單的,如果涉及到非常復(fù)雜的利用條件,那么又是另外一種情況,這個(gè)的定義就不那么容易了。
很好,我們已經(jīng)將漏洞挖掘和利用的問題轉(zhuǎn)化為了一個(gè)形式化驗(yàn)證的問題,要做的說白了就是生成一大堆復(fù)雜的公式,然后去求解它就好了。那么這復(fù)雜的公式怎么生成?具體的約束應(yīng)該是什么?需要的信息怎么獲取?最終公式如何求解?接下來要做的,就是如何實(shí)現(xiàn)。
0x04 系統(tǒng)總覽
整個(gè)AEG系統(tǒng)的實(shí)現(xiàn),可以分為六大部分,如圖所示。
1)PRE-PROCESS
第一個(gè)步驟就是預(yù)先處理,可以用如下公式表示:
其實(shí)就是把源代碼分別用GCC和LLVM進(jìn)行編譯,得到二進(jìn)制和字節(jié)碼表示。其中,其實(shí)是一種中間代碼,用于符號(hào)執(zhí)行器進(jìn)行分析,以找到漏洞條件;而則是用于二進(jìn)制分析,用來獲取運(yùn)行時(shí)信息,以生成利用條件。
2)SRC-ANALYSIS
第二個(gè)步驟就是分析源代碼,可以用如下公式表示:
具體說來就是分析LLVM生成的中間代碼,這是一個(gè)靜態(tài)分析的過程,可以得到程序的一些靜態(tài)信息,比如說buffer創(chuàng)建的時(shí)候的最大長(zhǎng)度是什么,也就是說通過搜索最大的靜態(tài)分配buffer大小來定義max。這個(gè)max就是符號(hào)化輸入數(shù)據(jù)的最小長(zhǎng)度——這很好理解,要觸發(fā)漏洞,顯然必須要給定比buffer更長(zhǎng)的輸入,才有可能覆蓋棧中的其他數(shù)據(jù)造成崩潰。這其實(shí)是為下一步符號(hào)執(zhí)行器的分析做準(zhǔn)備,這樣可以提升符號(hào)執(zhí)行器的分析效率。
3)BUG-FIND
第三個(gè)步驟就是尋找漏洞并定位,可以用如下公式表示:
以作為輸入,以及一個(gè)安全特性,會(huì)輸出一個(gè)元組表示漏洞。是表示漏洞的路徑謂詞,V是源代碼級(jí)別的信息,這個(gè)信息主要包括關(guān)于被檢測(cè)到的漏洞的各種信息,例如要被覆蓋的對(duì)象的名稱,漏洞函數(shù)是什么等等。這些信息V對(duì)約束生成以及下一步的利用分析都是有用的。
4)DBA
第四個(gè)步驟就是動(dòng)態(tài)二進(jìn)制分析,可以用如下公式表示:
之前第三步其實(shí)我們就可以得到一個(gè)能夠造成程序崩潰的證明漏洞的輸入了,就用表示,實(shí)際上是一個(gè)符號(hào)執(zhí)行器生成并進(jìn)行求解的約束條件,崩潰輸入就是求出的結(jié)果。用這個(gè)崩潰輸入去運(yùn)行二進(jìn)制程序,并在這個(gè)運(yùn)行過程中進(jìn)行二進(jìn)制分析,得到提取出的運(yùn)行時(shí)信息R。這個(gè)R信息的獲取,要用到V中定義的源代碼級(jí)別信息,如漏洞函數(shù)。R信息包括一些利用時(shí)必須用到的信息,如有漏洞的緩沖區(qū)在棧上的地址,漏洞函數(shù)的返回地址,以及漏洞觸發(fā)前的棧內(nèi)存內(nèi)容等。最終生成利用要依據(jù)的主要就是R信息。
5)EXPLOIT-GEN
第五個(gè)步驟就是生成利用,可以用如下公式表示:
二進(jìn)制程序和利用表達(dá)式作為輸入,用約束求解器進(jìn)行求解。如果可以滿足有效利用的條件,就返回一個(gè)利用,否則返回表示利用不存在。而且作者還增加一步用去運(yùn)行二進(jìn)制,檢查是否達(dá)成條件,例如彈出shell,這個(gè)是否成功的驗(yàn)證將反饋給約束求解器,以供利用的生成和選擇。
以上就是整個(gè)AEG系統(tǒng)運(yùn)行的過程。這個(gè)算法思路其實(shí)非常清晰,其實(shí)就是兩步走:一是用LLVM的中間語(yǔ)言去進(jìn)行程序分析,找到漏洞,生成漏洞條件 ;二是用能夠觸發(fā)的漏洞輸入和GCC編譯的二進(jìn)制進(jìn)行二進(jìn)制程序分析,得到運(yùn)行時(shí)信息,再加上此前的中間語(yǔ)言程序分析,可以得到利用條件 。所以計(jì)算兩個(gè)條件都滿足的輸入就可以得到利用了。整個(gè)系統(tǒng)的關(guān)鍵在于兩個(gè)條件的定義,其實(shí)也就是把漏洞利用問題轉(zhuǎn)化為形式化驗(yàn)證問題。LLVM的分析屬于漏洞挖掘領(lǐng)域,的生成才是論文創(chuàng)新的重點(diǎn),關(guān)鍵在于利用條件的定義。在沒有任何保護(hù)的情況下,這個(gè)條件的定義相當(dāng)簡(jiǎn)單:①能夠控制一塊區(qū)域且把shellcode放在這里;②能夠控制eip跳到shellcode。當(dāng)然,這一切的實(shí)現(xiàn)都因?yàn)橛蟹?hào)執(zhí)行這個(gè)非常厲害的工具。
了解到AEG系統(tǒng)的整體原理之后,我們?cè)賮矸謩e單獨(dú)介紹一下漏洞挖掘和利用生成這兩部分的細(xì)節(jié)究竟是如何實(shí)現(xiàn)的。
0x05 基于符號(hào)執(zhí)行的漏洞挖掘
上述所謂生成約束條件的過程,本質(zhì)上就是利用符號(hào)執(zhí)行挖掘漏洞的過程。自動(dòng)化漏洞挖掘在工業(yè)界通常都是用fuzzer完成的,因?yàn)樾时容^高,較短的時(shí)間內(nèi)就能得到很多導(dǎo)致崩潰的輸入,然后再手動(dòng)分析驗(yàn)證是否可利用就好。但學(xué)術(shù)界更青睞符號(hào)執(zhí)行,因?yàn)橄啾萬(wàn)uzzing能夠得到更多的語(yǔ)義信息,能夠深入到fuzzer走不到的深層程序路徑,挖到質(zhì)量更好的漏洞。但是符號(hào)執(zhí)行最大的問題就在于效率,現(xiàn)實(shí)世界的程序是非常復(fù)雜的,有無(wú)數(shù)的分支。由于符號(hào)執(zhí)行器的實(shí)現(xiàn)通常是在遇到路徑分支的時(shí)候會(huì)復(fù)制出來一個(gè)interpreter進(jìn)行分析,那么在分支不斷產(chǎn)生的過程中,程序的開銷是以指數(shù)級(jí)增長(zhǎng)的,又稱為“路徑爆炸”問題。所以主流的符號(hào)執(zhí)行研究都是基于如何能夠減少路徑爆炸,提升符號(hào)執(zhí)行的可用性。
AEG的作者在這個(gè)問題上也提出了幾種面向利用生成的優(yōu)化方法,用來提升漏洞挖掘的效率。具體說來有這么幾種:①前置條件的符號(hào)執(zhí)行、②路徑優(yōu)先級(jí)選擇、③環(huán)境建模。
1)前置條件的符號(hào)執(zhí)行
給約束加上一個(gè)前置條件,就能夠縮小原來很大的一個(gè)搜索空間。在AEG中,這個(gè)前置條件具體說來,就是輸入的長(zhǎng)度必須足夠大道能夠觸發(fā)漏洞,覆蓋緩沖區(qū),才有可能產(chǎn)生利用。
在這個(gè)例子中,input最多42個(gè)字節(jié),如果沒有前置條件,那么在第3行的while語(yǔ)句塊中,由于循環(huán)產(chǎn)生的分支,完全符號(hào)執(zhí)行需要復(fù)制出42個(gè)interpreter去進(jìn)行分析,而且循環(huán)里面還套著循環(huán),這就會(huì)產(chǎn)生很大的路徑爆炸。
但是,如果加上前置條件,即buf長(zhǎng)度要超過20字節(jié)才有可能觸發(fā)漏洞并且產(chǎn)生利用,那么這樣就將復(fù)制的interpreter數(shù)量大大減小,能夠提升符號(hào)執(zhí)行的效率。
AEG中還有另一種條件,就是輸入前綴。輸入前綴要根據(jù)程序的具體功能來看,比如HTTPGET請(qǐng)求總是以GET開始,那么GET就是相關(guān)輸入的前綴;又比如是一個(gè)圖片處理程序,處理的格式是PNG,那么所有的PNG文件的標(biāo)準(zhǔn)是前8字節(jié)頭部以PNG_H開頭,這就是要給前綴。如果有這個(gè)條件,比將輸入所有字節(jié)都符號(hào)化,顯然效率高了很多。
2)路徑優(yōu)先級(jí)選擇
在上一個(gè)方法中,搜索空間縮小了,但仍然有一個(gè)路徑選擇的問題,就是復(fù)制了那么多條分支路徑,哪一條應(yīng)該先探索,哪一條應(yīng)該后探索呢?解決方法就是路徑優(yōu)先級(jí),所有的路徑都被插入一個(gè)優(yōu)先級(jí)隊(duì)列,基于路徑的排名進(jìn)行探索。這里有兩種排序方法。
第一是漏洞路徑優(yōu)先。這個(gè)方法基于的思想是,如果一條路徑上出現(xiàn)了一個(gè)小錯(cuò)誤,那么說明程序員很容易犯錯(cuò)誤,那么在這條路徑的后面就更有可能有別的漏洞。在作者的實(shí)驗(yàn)中,就發(fā)現(xiàn)一條路徑上,先是出現(xiàn)了一個(gè)off-by-one的漏洞,雖然沒法直接利用,但說明程序員對(duì)邊界并不是很在意。他們繼續(xù)分析,果然在這條路徑后面又發(fā)現(xiàn)了一個(gè)長(zhǎng)度相關(guān)的緩沖區(qū)溢出。
第二就是循環(huán)耗盡策略。傳統(tǒng)的符號(hào)執(zhí)行對(duì)于循環(huán)的處理多是把帶有循環(huán)的路徑優(yōu)先級(jí)降低,或者只循環(huán)一定次數(shù)。但是這種方法是在基于保證覆蓋率的前提下提出的,并不適用于漏洞挖掘。事實(shí)上,常出現(xiàn)漏洞的strcpy函數(shù)本質(zhì)上就是一個(gè)復(fù)制字節(jié)的循環(huán),只要沒遇到NULL字節(jié)就會(huì)一直循環(huán),所以要想發(fā)現(xiàn)漏洞,我們就必須保證每一個(gè)循環(huán)都能夠耗盡。當(dāng)然,這可能產(chǎn)生非常大的路徑爆炸,這個(gè)問題其實(shí)不用在意,因?yàn)榍懊娴那爸脳l件方法的使用已經(jīng)解決這個(gè)問題了——因?yàn)槲覀冎懒四軌蚋采w緩沖區(qū)的最小長(zhǎng)度,其實(shí)就是知道了strcpy的覆蓋長(zhǎng)度,也就是它的循環(huán)長(zhǎng)度。所謂的strcpy循環(huán),其實(shí)就是每個(gè)字節(jié)都判斷一下是不是0。當(dāng)我知道strcpy長(zhǎng)度的時(shí)候,就不需要判斷前面的字節(jié)是不是0了,所以實(shí)際上可以只用一個(gè)解釋器,這就不存在路徑爆炸問題了。
3)環(huán)境建模
程序在運(yùn)行中是不斷與運(yùn)行環(huán)境進(jìn)行交互的,所以就需要系統(tǒng)能夠自動(dòng)化地對(duì)環(huán)境進(jìn)行模擬。AEG對(duì)環(huán)境建模比較完全,包括文件系統(tǒng)、網(wǎng)絡(luò)包、標(biāo)準(zhǔn)輸入、程序參數(shù)、環(huán)境變量,而且還能夠處理常見的系統(tǒng)函數(shù)和庫(kù)函數(shù)調(diào)用。雖然文中沒有詳細(xì)解釋,但我想應(yīng)該是AEG對(duì)每個(gè)可能的環(huán)境變量進(jìn)行設(shè)置與解析,而庫(kù)函數(shù)則有相應(yīng)的建模和信息傳遞。
總而言之,上述的三種方法其實(shí)都是只有一個(gè)目的,就是加快符號(hào)執(zhí)行發(fā)現(xiàn)漏洞的效率。另外值得注意的是,采用前置條件的實(shí)現(xiàn)是對(duì)程序進(jìn)行了靜態(tài)分析,提取出相關(guān)的信息,才能夠定義程序的前置條件。
0x06 利用生成與驗(yàn)證
我們之前講過,利用生成模塊的公式是這樣的:
其中已經(jīng)在符號(hào)執(zhí)行的過程中得到了,那么下面就介紹如何獲取運(yùn)行時(shí)信息R。
利用生成步驟所需要的信息R是由二進(jìn)制動(dòng)態(tài)分析實(shí)現(xiàn)的,具體方法是插樁。在進(jìn)行分析前,首先需要輸入三個(gè)信息:1)目標(biāo)二進(jìn)制,2)導(dǎo)致漏洞的路徑約束,3)漏洞函數(shù)和緩沖buffer的名稱。這些信息是上一步的符號(hào)執(zhí)行步驟中獲取的。
經(jīng)過二進(jìn)制動(dòng)態(tài)分析之后,能夠輸出以下運(yùn)行時(shí)信息:1)覆蓋的地址&retaddr(我們的實(shí)現(xiàn)中就是函數(shù)的返回地址,也可以拓展為包括函數(shù)指針或者GOT表中的入口);2)要寫入的開始地址bufaddr;3)額外的約束 ,即在漏洞觸發(fā)之前的棧內(nèi)存內(nèi)容。這里有一個(gè)非常重要的內(nèi)容,就是?;謴?fù)。如果在漏洞函數(shù)返回之前,棧上的信息有被使用的話,就會(huì)造成程序崩潰或者已構(gòu)造的部分被覆蓋。舉個(gè)例子來說,在下面的代碼中,攻擊者想要利用strcpy的緩沖區(qū)溢出,但是ptr參數(shù)在返回地址和buf之間,ptr是在棧溢出之后被使用的,那么我們的棧溢出會(huì)導(dǎo)致原本的ptr被破壞,程序?qū)tr解引用時(shí)就會(huì)出錯(cuò)終止。所以一個(gè)復(fù)雜的攻擊必須考慮上述情況,不要覆蓋有效的內(nèi)存指針。AEG解決的辦法就是在動(dòng)態(tài)二進(jìn)制分析的時(shí)候檢查整個(gè)??臻g的內(nèi)容,把具體的信息μ傳遞給利用生成模塊,其實(shí)理解起來很簡(jiǎn)單,就是既然這個(gè)位置的數(shù)不能改,那我就記錄下來,在構(gòu)造輸入覆蓋的時(shí)候讓原本應(yīng)該是ptr的位置還放置ptr的值就可以了。
有了運(yùn)行時(shí)信息R,我們就可以生成路徑約束 了。具體的算法偽代碼如下:
我們可以看到,算法是對(duì)要構(gòu)造的棧空間內(nèi)容逐個(gè)進(jìn)行恢復(fù),第2行就是?;謴?fù),本質(zhì)上是對(duì)棧中內(nèi)容符號(hào)化。第4行的jmp_target =&retaddr - bufaddr + 8是最簡(jiǎn)單的棧溢出利用的套路,即確定返回地址跳轉(zhuǎn)到什么位置。1-5行的內(nèi)容是檢查是否可以劫持eip這個(gè)條件,具體說來就是exp_str[offset]代表返回地址的位置,jmp_target是shellcode放置的位置,要看返回地址能否跳到j(luò)mp_target。6-7行是檢查shellcode是否放置在從exp_str[offset]開始的位置。最后就是返回這個(gè)約束 的公式。現(xiàn)在我們有了約束,接下來要做的就是求解了。這就是VERIFY步驟,是利用約束求解器對(duì)符號(hào)公式進(jìn)行求解。具體如何求解是一個(gè)非常復(fù)雜的知識(shí),屬于形式化驗(yàn)證的范疇。目前安全研究使用約束求解器基本都是當(dāng)黑盒用,其內(nèi)在原理有一些在形式化驗(yàn)證方面更加專業(yè)的科學(xué)家在研究??偠灾?#xff0c;最后約束求解器產(chǎn)生的那個(gè)符合條件的輸入,就是能夠產(chǎn)生利用的輸入。VERIFY還會(huì)進(jìn)一步實(shí)際執(zhí)行一下這個(gè)輸入,看看是否真的能彈出shell。
0x07 總結(jié)
至此我們就介紹完了整個(gè)AEG系統(tǒng)的基本原理和實(shí)現(xiàn)。這里還需要說一下AEG的工程實(shí)現(xiàn):由C++和Python編寫,其中符號(hào)執(zhí)行器是在KLEE的基礎(chǔ)上加了大約5000行代碼以實(shí)現(xiàn)原創(chuàng)的技術(shù)和功能;動(dòng)態(tài)二進(jìn)制分析是用Python編寫,使用了GDB wrapper的接口實(shí)現(xiàn);約束求解器使用了STP。
總結(jié)來說,AEG是安全研究領(lǐng)域第一次將“利用自動(dòng)生成”提出來作為一個(gè)研究課題,漏洞利用自動(dòng)化的研究熱潮也自此拉開序幕。當(dāng)然,這畢竟是2011年的文章,其模型和條件都十分簡(jiǎn)單,有很多內(nèi)容是沒有考慮的。
我相信讀者讀了這篇文章,一定會(huì)想,如果有NX、ASLR、棧保護(hù)的話,可以自動(dòng)繞過嗎?其他漏洞類型和利用類型可以實(shí)現(xiàn)嗎?Windows平臺(tái)的利用也可以自動(dòng)化嗎?
可以說,經(jīng)過了短短六年的發(fā)展,AEG已經(jīng)相當(dāng)成熟,后續(xù)的研究包括Mayhem、Q、Angr、rex等系統(tǒng),多多少少解決了上述的一些問題,讓漏洞利用自動(dòng)化的程度越來越高。最近在安全界備受關(guān)注的美國(guó)DARPA CGC比賽,就是一次全自動(dòng)的漏洞攻防賽。這場(chǎng)比賽從2013年初賽到2016年8月決賽,取得第一名的正是David Brumley團(tuán)隊(duì)開發(fā)的Mayhem系統(tǒng)。關(guān)于CGC的團(tuán)隊(duì)和研究,又可以洋洋灑灑寫下許多,這里暫時(shí)按下不表。總而言之,AEG已經(jīng)成為二進(jìn)制安全領(lǐng)域的一個(gè)研究熱點(diǎn)。
本人才疏學(xué)淺,是剛剛接觸二進(jìn)制安全的一個(gè)小菜鳥,文章中若有錯(cuò)誤之處還請(qǐng)各位大牛多多包涵,且能不吝賜教,悉數(shù)指出。如果學(xué)有余力,未來可能將繼續(xù)介紹Mayhem、Q等更加完善的自動(dòng)利用系統(tǒng)。希望喜歡二進(jìn)制安全的朋友們能夠有所收獲。
參考文獻(xiàn):Avgerinos T, Sang K C, Hao B L T, et al. AEG: Automatic ExploitGeneration.[J]. Internet Society, 2011, 57(2).
https://zhuanlan.zhihu.com/p/26690230
總結(jié)
以上是生活随笔為你收集整理的Automatic Exploit Generation:漏洞利用自动化的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 区块链共识算法Proof-of-Stak
- 下一篇: 【译】Byzantine Fault T