linux漏洞分析入門筆記-棧溢出


ida7.0

ubuntu16.04 lts

0x00:環境配置

使用IDA遠程調試Linux程序步驟如下:

1. 在進行遠程調試之前需要對Linux平台進行一些准備工作。在IDA的安裝目錄中的dbgsrv文件夾中,選擇linux_server或者linux_serverx64復制到需要調試Linux程序所在的目錄下。將復制過來的文件賦予執行權限chmod 777 linux_server*。執行該文件./linux_server或者./linux_server64。

2. 在IDA中選擇菜單Debugger-Run-Remote Linux debugger。如圖。分別將程序所在位置,程序所在目錄,參數(沒有可不寫),主機IP,主機端口,點擊OK。相對路徑路徑要填寫相對

linux_server或者linux_serverx64的相對路徑。

          圖1

          圖2

        圖3

3. 此時,下關鍵函數下好斷點后,即可進行動態調試,如下圖:

        圖4

常用快捷鍵包括:

a. 單步步過:F8

b. 單步步入:F7

c. 執行到光標位置:F4

d. 設置斷點:F2

e. 順序執行:F9

0x01:漏洞簡介

1.一個簡單的linux x64平台棧溢出漏洞,漏洞定位到vuln函數,如下圖:

        圖5

        圖6

        圖7

0x02.漏洞調試

1.從上面匯編代碼可以看出,進入該vuln后 sub rsp-0x40 ,堆棧開辟了0x40字節空間,然后調用gets函數讀入數據到edi所指向的空間,edi此時實際上是等於rsp的指向棧頂的位置,gets函數讀入數據以換行符號為結束標志,在遇到換行符號前,會讀取任意數據到棧里,這樣當讀入超長字符串后,就會覆蓋函數的返回地址,在該函數執行retn時就會可以返回到任意我們指定的地方去執行代碼。產生緩沖溢出漏洞,下好斷點后開始動態調試。

2.嘗試構造字符去覆蓋函數的返回地址,代碼如下:

 1 from pwn import *
 2 import pdb
 3 context.log_level = 'debug'
 4 target = process('./test')
 5 elf=ELF('./test')
 6 pdb.set_trace()
 7 #poc
 8 rop='A'*0x40#
 9 rop+='B'*0x8#
10 rop+=p64(0x400763)#pop rdi ret
11 rop+=p64(0x7FFFF7B99D57)#/bin/sh
12 rop+=p64(0x7FFFF7A52390)#System
13 target.sendline(rop)
14 target.interactive()

代碼執行后棧此時的情況如下:

        圖8

        圖9

后面用8個字符c就可以覆蓋返回地址了,函數返回時將會跳轉到cccccccc 指向的空間去執行,如圖9所示。

0x03:利用Ret2Lib突破NX保護

1.用checksec來檢查目標文件進發現它開啟了NX保護,如圖10所示:

        圖10

2.NX就是將非代碼段的地址空間設置成不可執行屬性,一旦系統從這些地址空間進行取指令時,CPU就是報內存違例異常,結束進程。棧空間也被操作系統設置了不可執行屬性,因此我們注入的Shellcode就無法執行了。

既然注入Shellcode無法執行,進程和動態庫的代碼段怎么也要執行吧,具有可執行屬性,那我們能否利用進程空間現有的代碼段給合成想要的功能代碼,答案是肯定的。

在系統函數庫(Linux稱為libc)有個system函數,它就是通過/bin/sh命令去執行一個用戶執行命令或者腳本,我們完全可以利用system函數來實現Shellcode的功能。EIP改寫成system函數地址后,在執行system函數時,它需要獲取參數。而根據Linux X86 32位函數調用約定,參數是壓到棧上的。但是棧空間完全由我們控制了,所以控制system的函數不是一件難事情。

這種攻擊方法稱之為ret2libc,即return-to-libc,返回到系統庫函數執行的攻擊方法。

但是我們使用的環境是64bit系統,它和32位系統的一個區別就是system函數的參數傳遞方式。32位系統使用堆棧來傳參,在64位系統中使用RDI來傳遞參數,所以我們不僅需要控制系統棧,還需要控制RDI,這無疑給我們增加了許多難度,但是這並不是做不到的!

要獲得shell需要做如下步驟:

a. 獲取system函數的地址。

b. 獲取“/bin/sh”字符串的地址。

c. 將RDI中的值,改成“/bin/sh”字符串的地址。

3.所以Shellcode不能放在棧下來執行,因此我們就需用用到ROP技術來間接執行功能代碼。

0x04:簡單ROP構造

1.由於目標程序有數據執行保護,所以我們往棧中的填充的數據並不能執行。所以在內存中代碼最好找到類似“pop rdi, ret”這樣的語句,由於我們可以完全控制棧中的數據,所以我們就可以通過pop為rdi賦值,再通過ret指令跳轉到我們希望的地方。

但是很不幸,目標程序並沒有到這樣的指令,不過我們可以找到其它代替指令,圖11所示:

        圖11

pop rdi 的機器碼是 5f c3,然而 pop r15 的機器碼是 41 5f c3,而且一般pop r15之后一般都是緊跟ret指令。

所以我們就可以使用pop r15指令的后半部分,即 5f (pop rdi)。

2.由於系統開戶地址空間隨機化,我們先臨時通過echo 0 > /proc/sys/kernel/randomize_va_space關閉地址隨機化功能寫死地址進行測試。

3.最后構造后rop代碼如下:

 1 from pwn import *
 2 import pdb
 3 context.log_level = 'debug'
 4 target = process('./test')
 5 elf=ELF('./test')
 6 pdb.set_trace()
 7 #poc
 8 rop='A'*0x40#
 9 rop+='B'*0x8#
10 rop+=p64(0x400763)#pop rdi ret
11 rop+=p64(0x7FFFF7B99D57)#/bin/sh address
12 rop+=p64(0x7FFFF7A52390)#System address
13 target.sendline(rop)
14 target.interactive()

4.運行poc后通過IDA調試看看棧的情況如圖12所示:

        圖12

5.執行完后就可以正確獲得shell,如圖13所示:

        圖13

0x05:通過plt和got繞NX與ascii armoring

1. 上面這個poc成功執行得利於關閉ASLR,system函數和“/bin/sh”的地址才能固定下來。我們構造poc才方便很多。雖然目標程序編譯時默認沒有開啟ALSR,但程序使用的系統動態鏈接庫會受到ALSR的約束,每次重新啟動程序后,libc.so的地址會隨機生成。所以我們的poc就會失效,下面我們就來構造一個不受libc.so基地址隨機變化影響的poc。

2.通過return-to-plt來實現繞過libc.so基地址隨機化。

什么是return-to-plt?

在這種技術中,而不是返回到libc函數(其地址是隨機的)攻擊者返回到一個函數的PLT(其地址不是隨機的、其地址在執行之前已知)。由於'function@PLT'不是隨機的,所以攻擊者不再需要預測libc的基地址,而是可以簡單地返回到“function@PLT”來調用“function”。

 

什么是PLT,如何通過調用“function@PLT”來調用“函數”?

要了解過程鏈接表(PLT),先讓我簡要介紹一下共享庫!

與靜態庫不同,共享庫代碼段在多個進程之間共享,而其數據段對於每個進程是唯一的。這有助於減少內存和磁盤空間。由於代碼段在多個進程之間共享,所以應該只有read和execute權限,因此動態鏈接器不能重新定位代碼段中存在的數據符號或函數地址(因為它沒有寫權限)。那么動態鏈接如何在運行時重新定位共享庫符號而不修改其代碼段?它使用PIC完成!

 

什么是PIC?

位置無關代碼(PIC)是為了解決這個問題而開發的 - 它確保共享庫代碼段在多個進程之間共享,盡管在加載時執行重定位。PIC通過一級間接尋址實現這一點-共享庫代碼段不包含絕對虛擬地址來代替全局符號和函數引用,而是指向數據段中的特定表。該表是全局符號和函數絕對虛擬地址的占位符。動態鏈接器作為重定位的一部分來填充此表。因此,只有重定位數據段被修改,代碼段保持不變!

 

動態鏈接器以兩種不同的方式重新定位PIC中發現的全局符號和函數,如下所述:

全局偏移表(GOT):

 

全局偏移表包含每個全局變量的4字節條目,其中4字節條目包含全局變量的地址。當代碼段中的指令引用全局變量時,而不是全局變量的絕對虛擬地址,指令指向GOT中條目。當加載共享庫時,GOT條目由動態鏈接器重新定位。因此,PIC使用該表來重新定位具有單個間接級別的全局符號。

 

過程鏈接表(PLT): 過程鏈接表包含每個全局函數的存根代碼。代碼段中的調用指令不直接調用函數('function'),而是調用存根代碼(function @ PLT)。這個存根代碼在動態鏈接器的幫助下解析了函數地址並將其復制到GOT(GOT [n])。這次解析僅在函數('function')的第一次調用期間發生,稍后當代碼段中的調用指令調用存根代碼(function @PLT)時,而不是調用動態鏈接器來解析函數地址('function')存根代碼直接從GOT(GOT [n])獲取功能地址並跳轉到它。因此,PIC使用這個表來重新定位具有兩級間接的功能地址。

        圖14

用ida反編譯目標程序后發現其中有printf,gets ,setvbuf,在內存這幾個函數的got表地址是固定的。從圖14可以看出在執行printf函數前,edi指向的是格式化串,rsi指向的是被打印串的地址。如果控制了rsi那么我們就可以打印任何地址的內容。然后通過當前函數地址(gets) - system = 偏移地址 (兩個函數的相對偏移是固定的),得到一個固定的相對偏移地址,得到偏移地址后通過當前地址加上偏移得到system函數的內存地址,然后傳入’/bin/sh’,執行system就達到目的。

3.通過構造ROP獲得system函數地址,在目標程序中找到圖15代碼。

        圖15

        圖16

看看printf_got_addr=0x600af0 這個數據里面剛好有個0x0a,這個就是換行符號對應的內存值,因此在讀取0xf0后gets就結束讀取了,所以后面的就無法正常覆蓋了,我們得換一種方法來實現調用printf,就是將printf_got_addr=0x600af0地址拆開,然后在通過 call    qword ptr [r12+rbx*8] 來組合,只要沒有0x0a就行,最后執行后如圖16所示。執行完后再讓它返回到發生漏洞的函數中,再將構造rop來執行system,通過pop edi ret來實現,步驟和第4步相同。

執行后成功獲得shell,如圖17、18所示:

        圖17

        圖18

0x06:總結

1. Linux系統中對應用程序漏洞防護有三個:

SSP(Stack-Smashing Protectot):堆棧防溢出保護,它會在每個函數的棧幀底部添加一個隨機字節,每次函數將要返回時,都會這個隨機字節進行驗證,如果這個隨機字節被篡改,則說明該棧幀發生數據溢出,報出異常,程序終止。在編譯時可以通過-fno-stack-protector選項取消這項保護。

NX(Never eXecute):數據執行保護,在64位系統的CPU中增加一位NX位,用來標示數據如果可寫就不可執行。在overflow這個程序中我們具有對棧數據寫的權限,就沒有對棧數據可執行的權限。

ASLR(Address Space Layout Randomization):地址空間隨機化,在每次程序加載運行的時候,堆棧數據的定位都會進行隨機化處理。由於每次程序運行時堆棧地址都會發生變化,所以無疑給溢出利用增加了很大的難度。可以通過這個命令

echo 0 > /proc/sys/kernel/randomize_va_space ,取消ASLR保護,然后方便驗證poc。最后通過plt方式過掉ASLR。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM