陳振偉 孫歆



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