李昆侖,鞏春景,李尚然,王 琳,張德智
(河北大學 電子信息工程學院,河北 保定 071000)
隨著互聯(lián)網的普及,各種攻擊方式也趨于多樣化,最常見的攻擊方式是利用緩沖區(qū)溢出將控制流導向將要執(zhí)行的惡意代碼.在這種類型的攻擊中,第一步需要找到一種方法覆蓋內存中的指針,比較常見的方法是利用緩沖區(qū)溢出和格式化字符串漏洞.攻擊者一旦獲取了程序的控制流,下一步就是控制其去執(zhí)行一些惡意程序.比如注入攻擊、return-into-libc攻擊、ROP攻擊和JIT-ROP攻擊等.其中return-into-libc是一種不依賴注入代碼并且可以繞過內存不可執(zhí)行技術的攻擊方式,攻擊者在棧中合適位置預先填入庫函數入口的地址,將控制流導向庫函數的入口實施攻擊[1].比如libc庫中提供了大量的API函數,攻擊者便可通過執(zhí)行若干庫函數來進行實質性的攻擊.ROP攻擊與return-into-libc相比,其復用的代碼不再是整個函數,而是代碼庫中某些以ret結尾的短小指令.JIT-ROP攻擊動態(tài)獲取進程中的資源構造gadgets實施攻擊[2].
隨后也出現了一些保護機制,比如控制流完整性(Control-Flow Integrity,CFI) 通過阻止原計劃中沒有的控制流,有效的阻止了代碼復用攻擊[3]; 地址空間隨機化方法 (Address space layout randomization,ASLR)通過將庫函數的代碼段和數據段的起始地址進行隨機化處理,使得攻擊者不能得到正確的地址而無法進行攻擊[4];但是通過暴力破解的方法可以得到真正的地址,又可以通過內存泄漏繞過傳統(tǒng)的地址空間隨機化方法[5].隨后出現的細粒度隨機化方法,進一步增加了程序的多樣性,并限制內存泄露的有效性[6,7].Pappas 等人提出in-place 代碼隨機化技術,通過指令重排列、等價指令替換和寄存器重賦值阻止 ROP 攻擊.然而,最近的研究表明,系統(tǒng)中存在的漏洞使得攻擊者可以在任何地方讀取內存并繞過細粒度的隨機化方法[8].另外還有DEP技術和W⊕X技術(W的含義是writable,X的含義是executable,W⊕X代表可寫區(qū)域不可執(zhí)行,可執(zhí)行區(qū)域不可寫入)等.
2007年,Shacham將return-into-libc這種攻擊方式進一步演化為ROP[9-11](Return-Oriented Programming).ROP攻擊的出現表明了攻擊者無需再向程序中注入任何新的代碼便可實現圖靈機完全性.
ROP攻擊的思想很簡單,與return-into-libc相比,ROP攻擊的跳轉位置不再是libc庫中的API函數,而是函數中的某些指令,而且這些指令都是以ret結尾的且指令最多不超過6條的短指令序列,一般將其稱之為gadget.ROP攻擊可以繞過Windows的數據執(zhí)行保護技術DEP(Data Execution Prevention).針對棧溢出的ROP攻擊是當前研究的熱點,通過修改函數指針可以很容易發(fā)起ROP攻擊,本文主要研究這種攻擊的機理與的檢測方法.
PointGuard是針對函數指針攻擊檢測方面一種有效的檢測方法,將函數指針經過加密之后保存在內存中,當進行函數調用時再進行動態(tài)解密[12].控制流安全 (Control Flow Integrity,CFI)也是一種經典的基于控制流的保護思想[13].它們所用到的關鍵技術是靜態(tài)分析,對于間接跳轉不起作用.對于目前發(fā)起的ROP攻擊大多數是利用緩沖區(qū)溢出后覆蓋函數的返回地址執(zhí)行gadget,而對于通過覆蓋函數指針發(fā)起ROP攻擊的研究很少.本文深入分析了函數指針攻擊形成機理,實現了這種攻擊,并且提出一種動靜結合的方法來檢測通過修改函數指針發(fā)起的ROP攻擊,核心思想是檢查某些間接跳轉指令是否連續(xù)執(zhí)行有效的gadget.將fpDetect檢測方法應用于正常程序與含有ROP攻擊的程序中,實驗表明這種檢測方法的誤報率低.可以同時應用在Linux與Windows操作系統(tǒng)中.
對于具有可變大小指令的x86系統(tǒng)來說,攻擊者可以在已有程序或者動態(tài)庫中找到gadgets.具體的說,這些gadgets是從一個多字節(jié)指令當中的某個字節(jié)開始進行編譯的指令.例如代碼字節(jié)0b 00 00 83 c4 2c c3 66從第一個字節(jié)開始反匯編得到以下指令序列:
oreax,dwordptr[eax]
addbyteptr[ebx+0x66c32cc4],al
如果從第二個字節(jié)開始反匯編則得到以下指令序列:
addbyteptr[eax],al
addesp,0x2c
ret
圖1 unintended指令序列Fig.1 Unintended instruction sequence
如圖1所示攻擊者可以利用x86指令密集型的特點在庫中尋找大量以ret結尾的gadget來實施攻擊.
圖2描述本文所做實驗的緩沖區(qū)中布局情況,圖的左邊是緩沖區(qū)中填入的數據(每條數據對應著地址),右邊是每條地址所對應的指令.當執(zhí)行第一條指令時,首先會將地址0x0806ea3a出棧執(zhí)行pop edx,將數據0x080ea060出棧并存儲到edx中(0x080ea060為data段首的地址).然后在執(zhí)行ret時,0x080bb7f6的地址出棧,并執(zhí)行pop eax指令,此時會將字符串“/bin”的ASCII碼存入到eax中.接下來將地址0x0809a79d出棧并執(zhí)行mov dword ptr [edx],eax,這條指令的執(zhí)行會將字符串“/bin”存放到data段中.最后將執(zhí)行execve函數.只要庫足夠大就可以找到各種類型的指令,完成各種功能,實現圖靈機完全性.圖靈機完全性是指能夠實現數據的移動存儲、算數加減、邏輯運算操作、控制流操作、函數調用、系統(tǒng)調用操作的具備通用圖靈機計算能力的gadget集合[14].
圖2 ROP攻擊指令執(zhí)行順序Fig.2 ROP attack instruction execution order
指令密集型的特點使得攻擊者更容易尋找gadget,利用其指令字節(jié)數不規(guī)整的特點,當我們在一個多字節(jié)指令中的某個字節(jié)處開始進行反匯編時會得到意想不到的結果.
gadget由兩部分組成:功能指令和控制轉移指令.在ROP中控制轉移指令采用ret,目的是改變控制流跳轉到下一個gadget.功能指令可以實現的功能可概括為以下:
1)加載常數到寄存器:將堆棧中的常量加載到指定的寄存器中,如pop ecx; ret.執(zhí)行此語句后,存儲在堆棧中的值將加載到ecx寄存器中.
2)從內存中加載數據:如mov ecx,[eax]; ret指令.執(zhí)行這條指令后會將eax中的地址指向的值加載到ecx中.
3)將寄存器中的值寫到內存中:如mov [eax],ecx; ret指令.執(zhí)行這條指令后會將寄存器ecx中存放的值寫入到eax地址指向的內存區(qū)域中.
4)算數運算:包括加、減、乘、除、異或等.
5)實現內核中斷:int 0x80; ret和call gs:[0x10];ret.
6)盡量避免使用的gadgets:不要使用以leave結尾的gadgets,會污染棧幀;不要使用包含pop ebp的gadget,同樣也會污染棧幀.
在windows操作系統(tǒng)中本文采取IDA Pro反匯編可執(zhí)行二進制文件,編寫idc腳本對可執(zhí)行二進制文件進行分析.IDA Pro是一種交互式、可編程的、可擴展的、多處理器的交叉Windows或Linux WinCE MacOS平臺主機分析程序反匯編工具.是一個常用的靜態(tài)反編譯軟件,對于0day世界的許多成員和shellcode安全分析師來說是不可或缺的工具.
在Linux操作系統(tǒng)中采用pin動態(tài)二進制插樁工具來獲取函數信息進行分析.pin是一種動態(tài)二進制檢測框架,適用于x86、x64構架,一般用于程序動態(tài)分析,支持windows、Linux以及OSX.在計算機安全領域有重要的應用.
近些年來已經有成熟的保護機制來預防通過覆蓋函數的返回地址進行的攻擊.Cowan提出的金絲雀方法(canary),通過在棧中插入關鍵字canary,當函數返回時動態(tài)檢測canary的值是否被修改,以此來檢測函數的返回地址是否被修改.文獻[15]以軟件和硬件的方式建立影子棧來存儲函數的返回地址,并在函數返回時檢測返回地址的值是否被篡改,以此來檢測覆蓋函數返回地址的攻擊.
C語言是計算機編程語言,C語言的學習與研究對于操作系統(tǒng)的安全至關重要[16].由于針對函數的返回地址進行攻擊的保護機制日趨完善,所以攻擊者把目光投向函數指針.在c/c++中被定義的函數指針是一個指向被調用函數的地址,攻擊者可以重寫函數指針指向代碼段中的任意地址來執(zhí)行,進而控制程序的執(zhí)行流.如圖3所示,是一段代碼以及??臻g中的示意圖.
圖3 函數代碼段及棧中布局Fig.3 Function code segment and layout in stack
由圖3中的代碼可知,程序在??臻g中開辟了156個字節(jié)的緩沖區(qū),當func中傳遞的參數p的字節(jié)數大于156個字節(jié)時將會覆蓋函數指針fp,但不一定會覆蓋函數的返地址ret.攻擊者可以精心構造參數p的值使其發(fā)生緩沖區(qū)溢出.使覆蓋函數指針fp的地址為代碼段的地址(return-into-libc和ROP),因為代碼段為可執(zhí)行權限,而DEP無法防御這種攻擊.
fpDetect檢測方案雖然實現簡單,但效率高,檢測的誤報漏報率低.
應用程序中只包含兩種函數,本地函數與動態(tài)庫函數(簡稱庫函數),本地函數調用本地函數時使用直接跳轉指令如call xxx(xxx為本地函數的地址),因為本地函數的地址固定,而庫函數的地址采取運行時重定位機制,只有在運行時庫函數的地址才可以確定,故在調用庫函數時采取間接跳轉指令,如call eax(其中eax為調用的庫函數的地址).另外,在通過使用函數指針調用函數時也是間接調用的,例如可以使用call eax來調用函數指針,eax中地址便為被調用的本地函數的入口點地址.
本文提出的檢測方法fpDetect是一種動靜結合的方法,首先使用IDA Pro對二進制文件進行反匯編,編寫idc腳本取得程序中除調用交叉引用函數以外的間接跳轉指令的地址,然后分析這些間接跳轉指令將要執(zhí)行的指令,若將要執(zhí)行的指令能夠形成連續(xù)的gadgets,則判斷為函數指針攻擊.
核心思想就是檢查部分間接跳轉的目標地址中所執(zhí)行的指令是否為連續(xù)的gadgets.因而這是一種動靜結合的技術.
圖4 fpDetect檢測流程Fig.4 fpDetect detection process
gadget需要滿足下面的幾個條件:1、指令數不超過6;2、組成gadget的指令都為副作用無關指令.副作用指令是在指令運行時修改EFLAGS寄存器的指令.為使得gadget的執(zhí)行更具有靈活性,一般組成gadget的指令數目為2到3條.由于正常程序執(zhí)行時存在大量的副作用指令,故判斷執(zhí)行的指令中是否全為副作用無關指令也是判斷其是否為gadget的一個重要條件.
本文所做基于函數指針的ROP攻擊是在Linux操作系統(tǒng)下進行的.gadget 1覆蓋原函數指針fp,gadget 2~gadget n依次存放于buf中,此時的gadget 1相當于一個跳板將執(zhí)行流引入到gadget 2上,然后依次執(zhí)行gadgets.
DBI的檢測思想:如果函數調用不是從庫函數或者本地函數的入口點調用,則將其判斷為非法調用,即檢測到ROP攻擊[17].而這種檢測方法忽略了正常函數調用庫函數時,也會存在非入口點跳轉這種情況.正常的非入口點跳轉是由編譯器決定的,非入口點跳轉的存在可以使程序的執(zhí)行變得更加簡便.非正常的非入口點跳轉發(fā)生時,當距離ret較近時便有可能形成gadgets.
DROR的檢測思想:判斷程序運行時連續(xù)使用的ret指令的數量是否超出檢測閾值,若超出這個檢測閾值則判斷其為ROP攻擊[18].同樣這種檢測方法也忽略了正常的程序執(zhí)行時也會有連續(xù)執(zhí)行ret指令的情況,會增加了檢測的誤報與漏報率,針對這種情況,本文提出了fpDetect檢測方法,來提高檢測的準確性.
本文所做實驗均在操作系統(tǒng)Linux ubuntu 14.04.5 32/64位、windows 10和windows XP中進行.fpDetect檢測方法在Linux與windows中同樣適用.
在Linux操作系統(tǒng)下本文實驗中注入攻擊部分所使用的弱點程序為圖3中所示的程序,被注入的shellcode為shellcode database中真實的shellcode,如下所示:
execve(“/bin/sh”)
xorecx%,ecx%
mulecx%
pushecx%
push0x68732f2f
push0x6e69622f
movesp%,ebx%
moval%,11
int0x80
其對應的硬編碼如下:
shellcode="/xeb/x0b/x5b/x31/xc0/x31/xc9/x31/xd2"
shellcode+=/xb0/x0b/xcd/x80/xe8/xf0/xff/xff/xff"
shellcode+="/x2f/x62/x69/x6e/x2f/x73/x68"
最終攻擊程序構造的payload如下:
payload=shellcode+'A'*(128-len(shellcode))
+p32(shellcodeaddr).
當執(zhí)行了read函數之后,fp位置的地址被覆蓋為一個作為跳板的指令的地址,使得程序的執(zhí)行流去執(zhí)行shellcode.最終攻擊成功之后的截圖如圖5所示.
圖5 攻擊成功之后的截圖Fig.5 A screenshot after a successful attack
在windows操作系統(tǒng)下本文實驗中的注入攻擊是借助user32.dll來完成的,所使用的弱點程序與在Linux操作系統(tǒng)中使用的程序一樣.首先編寫shellcode(本文所編寫的shellcode是調用MessageBox函數),然后在user32.dll中尋找跳板指令push ebp;ret.注入攻擊成功之后將彈出圖6所示的窗口.
圖6 攻擊成功Fig.6 Attack success
在Linux中通過使用pin對二進制文件插樁間接跳轉指令,去除調用庫函數的間接跳轉指令的地址,調試剩余的間接跳轉指令發(fā)現當函數調用函數指針時控制流跳轉去執(zhí)行棧空間中的shellcode,由此判斷為注入攻擊.
4.3.1 ROP攻擊的實現
在linux操作系統(tǒng)中構造ROPchain時,如果是簡單的gadgets,可以通過objdump來查找.但當我們尋找一些復雜的gadgets的時候,可以借助一些查找gadgets的工具,例如Ropme、Ropper和ROPgadget等.但這些工具多數只能應用于linux操作系統(tǒng)中,所以本文函數指針ROP攻擊實驗在linux32位與linux64位操作系統(tǒng)中進行.
ROP攻擊所使用的ROPchain是從shellcode database中的shellcode經過改寫之后在庫中找到的gadgets構造的ROPchain.在shellcode database中隨機選取了十個簡短的shellcode構造了ROPchain,用于進行本次的實驗.
為了使得本文更具有說服性,本文采用Linux操作系統(tǒng)中自動構造工具ROPgadget構造的ROPchain舉例說明基于函數指針ROP攻擊的實現.由于fpDetect檢測方法對于如何覆蓋函數指針沒有要求,所以實驗中選擇了圖3所示的弱點程序進行實驗.除read函數外還有大量的危險庫函數,比如strcpy、memcpy和malloc等.本文以read函數為例,通過發(fā)送測試代碼實施ROP攻擊.當執(zhí)行了read函數之后的??臻g圖如圖7所示.
圖7 read函數執(zhí)行前后的??臻gFig.7 Stack space of the read function before and after execution
在linux32位操作系統(tǒng)中發(fā)送測試代碼,當read函數執(zhí)行之后緩沖區(qū)被填充上gadgets的地址,其中函數指針fp的位置被替換為gadget1的地址.這個地址很關鍵,因為它的作用是將程序的執(zhí)行流引到gadget2上去執(zhí)行.本文通過精心構造及尋找,最終選定了add 0x2c ,esp%;ret作為gadget1處地址所存儲的指令.在這里值得一提的是,因為庫中不一定有我們想要的那條指令,所以我們應該在庫中尋找與我們需指令執(zhí)行效果最為相近的指令,然后再調整gadgets在緩沖區(qū)中的具體位置,以此思想來構造真正的攻擊.圖8為實施ROP攻擊成功之后的截圖.
圖8 32位操作系統(tǒng)中攻擊成功截圖Fig.8 A screenshot of the attack in the 32-bit operating system
在linux 64 位操作系統(tǒng)中基于函數指針的ROP攻擊的實施與在32位操作系統(tǒng)中有所不同,其內存地址的范圍由32位變成了64位.但是可以使用的內存地址不能大于0x00007fffffffffff,否則會拋出異常.其次是函數參數的傳遞方式發(fā)生了改變,x86中參數都是保存在棧上,但在x64中的前六個參數依次保存在RDI,RSI,RDX,RCX,R8和 R9中,如果還有更多的參數的話才會保存在棧上,所以我們需要尋找一些類似于pop rdi;ret的這種gadget.圖9為實施ROP攻擊成功之后的截圖.其中amd64-64-little代表由AMD公司開發(fā)的64位元的處理器架構.
圖9 64位操作系統(tǒng)中攻擊成功截圖Fig.9 A screenshot of the attack in the 64-bit operating system
4.3.2 函數指針ROP攻擊檢測
針對函數指針ROP攻擊檢測本文分別在windows10操作系統(tǒng)與linux 64位操作系統(tǒng)中進行.在windows 10中,使用IDA Pro對二進制文件進行靜態(tài)分析,編寫idc腳本獲取程序中的除調用庫函數的間接跳轉指令的地址,然后對其進行動態(tài)分析;在linux操作系統(tǒng)中使用pin動態(tài)插裝,取得函數信息并進行gdb調試.如表1所示,fpDetect檢測全部檢測成功.與DBI的檢測方法相比較,fpDetect檢測方法考慮了函數的非入口點跳轉,提高了檢測的準確率.與DROP相比,DROP只考慮到了棧溢出ROP攻擊,而沒有考慮到覆蓋函數指針的攻擊,對于本文中構造的攻擊DROP檢測方法失效.
表1 性能測試表Table 1 Performance test table
本文采用的是動靜結合的檢測思想,所以檢測的準確性會大大提高.本文通過實驗對計算機中的程序進行真實檢測,結果如表2可知,沒有誤報產生,這也進一步驗證了本文方法的有效性.
表2 誤報測試結果表Table 2 Misreport test result table
對于目前發(fā)起的ROP攻擊大多數是利用緩沖區(qū)溢出后覆蓋函數的返回地址后執(zhí)行gadget,而對于通過覆蓋函數指針發(fā)起ROP攻擊的研究很少.本文通過實驗證明了這種攻擊是存在的.因為函數指針在c/c++中普遍存在,所以很容易被攻擊者所利用.本文提出的fpDetect檢測方法能夠檢測基于函數指針發(fā)起的ROP攻擊,同時也提高了檢測的準確率.但是除了檢測攻擊,還要把更多的精力放到防御上.
由于近年來大多數研究集中在應用層代碼重用攻擊上,而面向內核級的代碼復用攻擊將會給計算機帶來更加實質性的破壞[19],所以接下來作者將要研究內核級代碼復用攻擊.并且會把更多的研究重點放到防御上.