陳振偉 孫歆
摘 要:緩沖區(qū)溢出是指數(shù)據(jù)緩沖區(qū)復(fù)制的過程中,由于沒有注意緩沖區(qū)的邊界,越過邊界,覆蓋了和緩沖區(qū)相鄰內(nèi)存區(qū)域而引起的內(nèi)存問題。緩沖區(qū)溢出是最常見的內(nèi)存錯(cuò)誤之一,也是攻擊者入侵系統(tǒng)時(shí)所用到的最強(qiáng)大、最經(jīng)典的一類漏洞利用方式。成功利用緩沖區(qū)溢出漏洞可以修改內(nèi)存中變量的值,甚至可以劫持進(jìn)程,執(zhí)行惡意代碼,最終獲得主機(jī)的控制權(quán)。
關(guān)鍵詞:棧溢出;漏洞利用;數(shù)據(jù)執(zhí)行保護(hù);Linux
中圖分類號(hào):TP309 文獻(xiàn)標(biāo)識(shí)碼:A
Research on bypassing the NX protection of Linux with ROP
Abstract: Buffer overflow refers to the memory anomaly where a program, while writing data to a buffer, overruns the buffer's boundary and overwrites adjacent memory locations.Buffer overflow is one of the most common memory anomaly, and is the most powerful and classic way to exploit vulnerabilities when attackers hack the system. Attackers can modify the value of variables in the memory, hijack the processes, executemalwares, and ultimately control the hosts.
Key words: stack overflow; vulnerability exploitation; data execution prevention; linux
1 引言
棧溢出核心思想是通過局部變量覆蓋函數(shù)返回地址來修改EIP和注入 Shellcode,在函數(shù)返回時(shí)跳到Shellcode去執(zhí)行。要防止這種攻擊,最有效的辦法就是讓攻擊者注入的Shellcode無法執(zhí)行,這就是數(shù)據(jù)執(zhí)行保護(hù)(Data Execution Prevention,DEP)安全機(jī)制的初衷。
數(shù)據(jù)執(zhí)行保護(hù)(DEP)主要用于抵抗緩沖區(qū)溢出攻擊。該安全策略可以控制程序?qū)?nèi)存的訪問方式,即被保護(hù)的程序內(nèi)存可以被約束為只能被寫或被執(zhí)行(W XOR X),而不能先寫后執(zhí)行。目前,這種安全策略已經(jīng)在系統(tǒng)中得到了廣泛的應(yīng)用。
DEP述語是微軟公司提出來的,在Window XP操作系統(tǒng)開始支持該安全特性。DEP特性需要硬件頁表機(jī)制來提供支持。Linux在X86_32位CPU沒有提供軟件的DEP機(jī)制,在64位CPU則利用NX位來實(shí)現(xiàn)DEP(當(dāng)前Linux很少將該特性說成DEP)。
DEP就是將非代碼段的地址空間設(shè)置成不可執(zhí)行屬性,一旦系統(tǒng)從這些地址空間進(jìn)行取指令時(shí),CPU就是報(bào)內(nèi)存違例異常,進(jìn)而殺死進(jìn)程。??臻g也被操作系統(tǒng)設(shè)置了不可執(zhí)行屬性,因此注入的Shellcode就無法執(zhí)行了。數(shù)據(jù)執(zhí)行保護(hù)策略雖然對(duì)程序運(yùn)行時(shí)的內(nèi)存訪問提供了安全保護(hù),保證內(nèi)存只能被寫或者被執(zhí)行而不能先寫后執(zhí)行。但是不幸的是,這種保護(hù)方式并不是完全有效的,其仍然不能抵御不違反 W XOR X 保護(hù)策略的攻擊方式。
2 漏洞利用
通過對(duì)一個(gè)具有棧溢出漏洞的程序進(jìn)行實(shí)例分析,該程序文件名為overflow,具體實(shí)驗(yàn)環(huán)境如表1所示。
2.1遠(yuǎn)程調(diào)試前IDA配置
本文將用IDA對(duì)Linux程序進(jìn)行遠(yuǎn)程的動(dòng)態(tài)調(diào)試。使用IDA對(duì)Linux程序進(jìn)行動(dòng)態(tài)調(diào)試之前需要對(duì)IDA和Linux環(huán)境進(jìn)行配置,具體步驟簡(jiǎn)要概括為三點(diǎn)。
1) 將IDA安裝目錄/dbgsrv/linux_server文件拷貝到Linux系統(tǒng)中,增加執(zhí)行權(quán)限并運(yùn)行。
2) 打開IDA,選擇 Debugger-Run-Remote Linux debug。
3) 在遠(yuǎn)程調(diào)試配置界面填入被調(diào)試文件的程序名、位置、參數(shù)、Linux主機(jī)IP、Linux用戶密碼等信息,如圖1所示。
IDA的常用功能包括源碼顯示:F5;自定義名稱:Ctrl+N;文字視圖切換:空格,效果如圖2所示。
2.2 棧溢出通用分析方式
2.2.1 禁用ASLR
在接下來的兩章中將對(duì)Overflow文件進(jìn)行漏洞利用。在漏洞利用之前需要對(duì)Linux系統(tǒng)的堆棧地址隨機(jī)化功能進(jìn)行禁用。否則每次程序載入時(shí),都會(huì)為堆棧重新隨機(jī)化的分配內(nèi)存地址,將對(duì)調(diào)試產(chǎn)生極大的干擾。
禁用方法:在root權(quán)限下執(zhí)行 cat 0 > /proc/sys/kernel/randomize_va_space
在系統(tǒng)重啟之后,這個(gè)文件中的值會(huì)恢復(fù),所以在電腦重啟以后,如再次進(jìn)行調(diào)試,需要重新執(zhí)行該命令。
2.2.2 靜態(tài)流程分析
先對(duì)Overflow文件進(jìn)行靜態(tài)分析,了解其代碼的執(zhí)行流程。
可獲得幾條信息。
a. 該程序首先創(chuàng)建了一個(gè)UDP的socket。
b. 與本地環(huán)路IP地址和12345端口進(jìn)行綁定。
c. 顯示一個(gè)“waiting for message ….”的字符串。
d. 然后recvfrom。
e. 再根據(jù)收到的信息轉(zhuǎn)換一下IP地址,顯示信息。
f. 將輸入的字符串進(jìn)行加密,使用“abcdefghijklmn”作為加密的密鑰。
g. 執(zhí)行Calc函數(shù)。
h. 將字符串“You have failed …”發(fā)送回去,進(jìn)行sleep,最后循環(huán)到上面的接收數(shù)據(jù),重復(fù)步驟c。
再仔細(xì)的分析一下可以看到,這個(gè)程序的利用點(diǎn)在Calc函數(shù)當(dāng)中,如圖3所示。
如果忽略hook_foo函數(shù),在Calc函數(shù)返回之前,緊跟一個(gè)memcpy函數(shù),rdi和rsi分別為memcpy的兩個(gè)參數(shù),在執(zhí)行memcpy函數(shù)之前沒有檢查是否存在參數(shù)越界的情況,所以這里具有棧溢出的風(fēng)險(xiǎn)。
2.2.3 使用IDA進(jìn)行Linux的遠(yuǎn)程動(dòng)態(tài)調(diào)試
在EncodeBuffer函數(shù)上下斷點(diǎn)。執(zhí)行,此時(shí)rdi、esi、esi當(dāng)中的值分別是buffer地址,輸入字符串長(zhǎng)度,密鑰所在地址。
按5次F8,執(zhí)行到call Calc,F(xiàn)7步入。如圖4所示,可以看出前面有3個(gè)跳轉(zhuǎn)。
分別是判斷輸入是否為空,輸入長(zhǎng)度是否為1,輸入長(zhǎng)度是否大于0x63。此時(shí)需要記住一個(gè)值,即Calc的返回地址,如圖5所示。
可以看出00007FFFFFFFBEB8當(dāng)中存的就是calc的返回地址。在地址為400960處的代碼上下斷點(diǎn),F(xiàn)9執(zhí)行,此時(shí)觀察寄存器的值可以看到RDI和RSI的值(分別為Memcpy的目的操作數(shù)和源操作數(shù)),如圖6所示。
這是已知calc的返回地址是BEB8,Memcpy的目的操作數(shù)是BDA0。
0xBEB8 –0xBDA0 = 0x118.所以第0x118個(gè)字節(jié)將會(huì)覆蓋返回地址。
注:實(shí)際實(shí)驗(yàn)中具體的地址可能有所變化,但是Memcpy目的操作數(shù)與Calc的返回地址的距離是不會(huì)變化的,所以不會(huì)產(chǎn)生影響。
我們可以通過這個(gè)設(shè)計(jì)我們的輸入數(shù)據(jù),如表2所示。
在輸入數(shù)據(jù)的開始寫入shellcode數(shù)據(jù),第118個(gè)字節(jié)使用00007FFFFFFFBBE0來覆蓋Calc的返回地址。
由于Overflow程序使用UDP socket來收發(fā)數(shù)據(jù),所以首先要將數(shù)據(jù)數(shù)據(jù)寫入文件(go.in)。然后使用cat go.in | nc–u 127.0.0.1 12345,將輸入數(shù)據(jù)發(fā)送給Overflow。
由于此程序在執(zhí)行Calc函數(shù)前使用了EncodeBuffer函數(shù)對(duì)輸入的數(shù)據(jù)進(jìn)行了加密,如圖2所示。通過閱讀代碼可以發(fā)現(xiàn)在加密時(shí)使用字符串“abcdefghijklmn”作為加密的參數(shù),所以可以判定此程序使用某種對(duì)稱加密方式,以字符串“abcdefghijklmn”作為加密的密鑰。根據(jù)對(duì)稱加密算法的原理,對(duì)密文加密可以得到明文,我們可以將構(gòu)造好的源POC文件作為第一次的輸出,在程序?qū)ζ浼用芎螅瑢⒓用芎蟮臄?shù)據(jù)在IDA的內(nèi)存窗口中進(jìn)行讀取,保存為新的POC文件。這樣在漏洞利用時(shí),對(duì)加密數(shù)據(jù)進(jìn)行解密,就可以在程序獲取正常的shellcode代碼。
重新運(yùn)行程序,在EncodeBuffer函數(shù)處下斷點(diǎn),右鍵點(diǎn)擊RDI,選擇Open register window,這時(shí)應(yīng)該可以看到輸入數(shù)據(jù),一直向下瀏覽,可以看到連續(xù)的0,就是輸入數(shù)據(jù)的結(jié)尾了。從第一個(gè)0,上面的一個(gè)數(shù)據(jù)開始,正是輸入數(shù)據(jù)即00007FFFFFFFBEB0,這時(shí)按F8,此時(shí)的BEB0,就變成了加密后的數(shù)據(jù)。將該輸入數(shù)據(jù)重新寫到輸入數(shù)據(jù)文件中。文件的數(shù)據(jù)應(yīng)該是如圖7所示。
重新運(yùn)行程序,用修改后的輸入數(shù)據(jù)作為輸入。如圖8所示。可以看到calc的返回地址成功被修改為輸入buffer的內(nèi)存地址。
由于程序使用了對(duì)稱加密,只要將加密后的數(shù)據(jù)作為輸入即可使用真正的數(shù)據(jù)。同理,將加密后的Shellcode作為輸入,就可使用真正的Shellcode,如圖9所示。
按F8執(zhí)行后提示錯(cuò)誤,如圖10所示。
脫離IDA的調(diào)試環(huán)境,直接運(yùn)行Overflow,用剛剛的數(shù)據(jù)作為輸入,會(huì)發(fā)現(xiàn)段錯(cuò)誤。
這說明該程序有數(shù)據(jù)執(zhí)行保護(hù)(DEP|NX),還需要其他技術(shù)才能獲取shell。但是對(duì)于一般的棧溢出程序,使用以上方法即可完全達(dá)到目的。
2.3 NX Exploit編寫
雖然注入Shellcode無法執(zhí)行,但是進(jìn)程和動(dòng)態(tài)庫的代碼段是必須要執(zhí)行的,具有可執(zhí)行屬性,那么攻擊者就可以利用進(jìn)程空間現(xiàn)有的代碼段進(jìn)行攻擊。
系統(tǒng)函數(shù)庫(Linux稱為libc)包含一個(gè)System函數(shù),它通過/bin/sh命令去執(zhí)行一個(gè)命令或者腳本,我們完全可以利用System來實(shí)現(xiàn)Shellcode的功能。根據(jù)Linux X86 32位函數(shù)調(diào)用約定,參數(shù)是壓到棧上的,但是由于棧溢出漏洞,導(dǎo)致棧數(shù)據(jù)可以由我們控制,所以通過System函數(shù)可以執(zhí)行任意代碼。通過EIP將改寫成System函數(shù)地址,從而去執(zhí)行棧中執(zhí)行的代碼達(dá)到Shellcode的目的。這種攻擊方法稱之為ROP(即Return-Oriented Programming,,也稱Return-to-Libc),即返回到系統(tǒng)庫函數(shù)執(zhí)行的攻擊方法。
但是使用的環(huán)境是64位系統(tǒng),它和32位系統(tǒng)的函數(shù)傳參方式不同。32位系統(tǒng)使用堆棧來傳參,而64位系統(tǒng)中使用RDI等寄存器來傳遞前六個(gè)參數(shù),所以不僅需要控制系統(tǒng)棧,還需要控制RDI,這無疑給攻擊增加了許多難度,但是這同樣有方法辦到,思路有三:(1)獲取System函數(shù)的地址;(2)獲取“/bin/sh”字符串的地址;(3)將RDI中的值,改成“/bin/sh”字符串的地址。
2.3.1 System函數(shù)地址
這個(gè)函數(shù)存在于libc.so中,一般都會(huì)被程序加載,也可以通過調(diào)試狀態(tài)的IDA進(jìn)行查看。獲取System函數(shù)的方法有很多種,我們使用如下方法。編寫一段程序代碼如下:
intmain()
{
system();
}
然后使用如下命令:
gdb -p dummy
run
p system
一般來說結(jié)果如下,不同版本或環(huán)境產(chǎn)生的結(jié)果可能略有不同:
$1 = {
2.3.2 獲取“/bin/sh”字符串地址
“/bin/sh”字符串的地址可以使用IDA的搜索方法,如圖11所示,我們搜索得到該字符串的地址是00007FFFF7B91CDB。
2.3.3 設(shè)置RDI的值
由于該程序有數(shù)據(jù)執(zhí)行保護(hù),所以往棧中填充的數(shù)據(jù)并不能執(zhí)行,因此如何控制RDI的值是一個(gè)難點(diǎn)。目前可采用的方法就是Ret2Lib,通過Calc函數(shù)的返回地址控制程序的RIP,同時(shí)也可以通過控制RIP來執(zhí)行內(nèi)存中已有的代碼指令。只要在內(nèi)存中找到“pop rdi, ret”語句,就可以根據(jù)控制棧中的數(shù)據(jù)為rdi賦值,再通過ret指令跳轉(zhuǎn)到指定的地址。
由于pop rdi 的機(jī)器碼是 5f c3,所以尋找pop rdi指令可以使用下面的命令:
objdump–d /lib/x86_64-linux-gun/libc.so.6 | grep–B1 c3 | grep–C3 5f
通過上面的指令可以搜索到libc.so文件中的5f c3 機(jī)器碼所在的偏移地址,再通過IDA等工具可以獲取libc.so的加載基址,計(jì)算出機(jī)器碼所在的內(nèi)存位置。在實(shí)際調(diào)試的時(shí)候我們使用的地址是00007FFFF7A3855E。
2.3.4 構(gòu)造輸入數(shù)據(jù)
現(xiàn)在已知信息如表3所示。
構(gòu)造好的輸入數(shù)據(jù)如圖12所示。但是這個(gè)程序會(huì)將輸入的數(shù)據(jù)加密,為了解密數(shù)據(jù),我們將加密后的數(shù)據(jù)作為輸入數(shù)據(jù),如圖13所示。
在這份輸入數(shù)據(jù)中真正數(shù)據(jù)的前后各4個(gè)字節(jié)也要進(jìn)行加密,這樣在調(diào)試的時(shí)候可以方便的看到輸入的3個(gè)地址數(shù)據(jù)的邊界,成功后的效果如圖14所示。
3 Linux內(nèi)存防護(hù)總結(jié)
Linux系統(tǒng)對(duì)應(yīng)用程序的保護(hù)主要有三個(gè)方面。
(1) SSP(Stack-Smashing Protectot):堆棧防溢出保護(hù),它會(huì)在每個(gè)函數(shù)的棧幀底部添加一個(gè)隨機(jī)字節(jié),每次函數(shù)將要返回時(shí),都會(huì)對(duì)個(gè)隨機(jī)字節(jié)進(jìn)行驗(yàn)證,如果這個(gè)隨機(jī)字節(jié)被篡改,則說明該棧幀發(fā)生數(shù)據(jù)溢出,程序報(bào)錯(cuò)并終止運(yùn)行。在編譯時(shí)可以通過-fno-stack-protector選項(xiàng)取消這項(xiàng)保護(hù)。
(2)NX(Never eXecute):數(shù)據(jù)執(zhí)行保護(hù),在64位系統(tǒng)的CPU中增加了NX位,表示數(shù)據(jù)如果可寫就不可執(zhí)行。在Overflow這個(gè)程序中,我們對(duì)棧數(shù)據(jù)擁有寫權(quán)限,但就沒有了對(duì)棧數(shù)據(jù)的執(zhí)行權(quán)限。
(3)ASLR(Address Space Layout Randomization):地址空間隨機(jī)化,在每次程序加載運(yùn)行的時(shí)候,堆棧數(shù)據(jù)的定位都會(huì)進(jìn)行隨機(jī)化處理。由于每次程序運(yùn)行時(shí)堆棧地址都會(huì)發(fā)生變化,無疑給溢出利用增加了很大的難度。在本文中通過命令echo 0 > /proc/sys/kernel/randomize_va_space,取消ASLR保護(hù)。
整體上來說,操作系統(tǒng)層面上做的防護(hù)還是具有一定效果的,而且主流操作系統(tǒng)目前都將各種防護(hù)方式同時(shí)進(jìn)行實(shí)施。在緩沖區(qū)溢出上的攻擊與防御,一直是一個(gè)長(zhǎng)期對(duì)抗的過程,今后,溢出漏洞利用的難度也會(huì)越來越高。
4 結(jié)束語
目前,在操作系統(tǒng)層面已經(jīng)對(duì)緩沖區(qū)溢出攻擊做了很多努力和防護(hù)策略。所謂“道高一尺魔高一丈”,這些安全策略也都有繞過的方式。SSP防護(hù)(即Windows中GS防護(hù))可以通過攻擊SEH鏈的方式,直接繞過棧幀底部的隨機(jī)字節(jié),使SSP防護(hù)直接失去作用。NX防護(hù)(即Windows中DEP防護(hù))可以通過本文所利用的ROP技術(shù),構(gòu)造出一個(gè)ROP鏈,使Shellcode代碼不保存在棧的數(shù)據(jù)區(qū),通過其他模塊的代碼進(jìn)行Shellcode代碼執(zhí)行從而繞過NX防護(hù)。ASLR給棧溢出攻擊帶來了很大難度,但是也出現(xiàn)一些攻擊思路,包括尋找未啟用ASLR的模塊進(jìn)行防護(hù)繞過,或者使用堆噴射技術(shù)在內(nèi)存中添加大量空指令(NOP)代碼,雖然地址空間不固定,但最后EIP依然會(huì)“滑落”到Shellcode代碼中。
參考文獻(xiàn)
[1] 王清.0day安全:軟件漏洞分析技術(shù)[M].北京:電子工業(yè)出版社.
[2] 李承遠(yuǎn).逆向工程核心原理[M].北京:人民郵電出版社.
[3] 林椏泉.漏洞戰(zhàn)爭(zhēng):軟件漏洞分析精要[M].北京:電子工業(yè)出版社.
[4] Matt Welsh & Lar Kaufman.Linux權(quán)威指南.[M]北京:中國(guó)電力出版社.
[5] Kris Kaspersky.Shellcoder編程揭秘[M].北京:電子工業(yè)出版社.
[6] 劉孜. Return-into-libc攻擊及其防御.http://www.ibm.com/developerworks/cn/linux/1402_liumei_rilattack/.
[7] 0x2b0 Return into libc. http://www.dmi.unipg.it/bista/didattica/sicurezza-pg/buffer-overrun/hacking-book/0x2b0-exploit-in-not-executable-stack.html.
[8] Ben Lynn. 64-bit Linux Return-Oriented Programming.http://crypto.stanford.edu/~blynn/rop/.
[9] 海楓.使用ret2libc攻擊方法繞過數(shù)據(jù)執(zhí)行保護(hù). http://blog.csdn.net/linyt/article/details/43643499.