Linux下的棧溢出案例分析-GDB調試操練

來源:互聯網
上載者:User

標籤:gdb   調試   棧溢出   彙編   

摘要:
 本文主要示範linux平台下的棧溢出,首先根據理論對範例程式碼進行溢出攻擊;結果是溢出攻擊成立,但是與設想的有差別;然後採用GDB調試工具對發生的意外,進行深入的分析。

測試的平台:
1.  ubuntu 9;   gcc 4.4.1;   Gdb 7.0-ubuntu
2.  ubuntu系統安裝在virtual box 3.2.8虛擬機器上;

範例程式碼如下:

#include<string.h>void overflow(char* arg){char buf[12];strcpy(buf, arg);}int main(int argc, char *argv[]){if(argc > 1)overflow(argv[1]);return 0;}

如果按照一般的方式編譯:
gcc –o stackoverflow stackoverflow.c
linux系統能夠探測到程式中的stack  overflow,從而終止程式,如所示:

那有沒有辦法讓系統不探測到stack overflow,此處可以在編譯時間,禁用堆棧保護,具體命令如下:
gcc –fno-stack-protector –o stackoverflow stackoverflow.c
然後採用gdb調試stackoverflow,

這裡的輸出跟設想的存在很大的差別,因為設想中的函數棧如下:

前面的12個位元組填充buf,然後接下來的4個位元組填充ebp,最後的4個位元組填充RET地址,那麼照理說,這裡的eip應該是0x65656565,那為什麼此處是0x61616161,剛好是aaaa的值呢?

根據單步調試的結果,發現eip變為0x61616161是在main函數退出後達到的,按照設想應該是在overflow退出時,eip變為0x65656565。

為什麼overflow退出後還能回到main函數?可能的原因:輸入的字串沒有覆蓋掉ret地址,但是字串卻意外地將main的返回地址給覆蓋掉了?

但就算是覆蓋,為什麼覆蓋的值沒有採用e的值,而是採用的a的值?要知道a是在字串的起始處?這點的確讓人奇怪。

1. 我們還是採用一步步調試的方式來觀察問題所在,先看下gcc編譯後的反組譯碼代碼:
使用到的命令:
set disassembly-flavor intel  //將彙編設定為intel風格;
disassemble main  //反組譯碼main函數;

Main函數:

1. push ebp2. mvo ebp, esp3. and esp, 0xfffffff04. sub esp, 0x105. cmp DWORD PTR [ebp+0x8], 0x16. jle  0x804841d <main+31>7. mov eax, DWORD PTR [ebp+0xc]8. add eax, 0x49. mov eax, DWORD PTR [eax]10. mov DWORD PTR [esp], eax11. call 0x80483e4 <overflow>12. mov eax, 0x013. leave14. ret

然後再來看下,overflow的反組譯碼代碼,命令:disassemble overflow
Overflow函數:

1. push ebp2. mov ebp, esp3. sub esp, 0x284. mov  eax, DWORD PTR[ebp+0x8]5. mov  DWORD PTR[esp+0x4], eax6. lea  eax, [ebp-0x14]7. mov  DWORD  PTR [esp], eax8. call  0x804831c <[email protected]>9. leave10. ret


我們單步調試上述的指令,關注其中esp值的變化。總圖如下,後面是對其中每一步的分析:

在完成main.1後,命令p $esp後,esp的值變為:Esp = 0xbffff438
Main.3後,esp的值變為0xbffff430,估計是用於對齊;
Main.4後,esp的值為0xbffff420;
Main.7-10,這裡主要將argv[]的arg[1]字串的首地址取出來,並且將其放置在esp中,此時esp的值為0xbffff420;

執行overflow.1後,esp的值變為:0xbffff41c,其中存放著main的下一條語句的地址,
通過命令:x $esp可以看到overflow返回的地址:
0xbffff41c   0x0804841d
Overflow.1執行後,esp的值變為0xbffff418,用於存放ebp的值;
Overflow.3執行後,esp的值變為0xbffff3f0,
Overflow.5-7執行後,將字串的地址放置在3f0+0x4地址處,然後再將臨時字串也即buf的首地址[ebp-0x14]放置在3f0地址處(當前的esp指向處);

執行完overflow.8後,我們查看buf起始處後,發現的確完成了內容的賦值,命令如下:
X 地址;

執行完leave指令後,發現esp的值變為:0xbffff41c,剛好指向的返回main的地址;然後再執行ret指令後,發現esp變為0xbffff420;然後,程式跳回到main函數;

然後再執行main函數中的leave指令,按照設想中leave指令執行後,esp的值應該變為43c,指向main的返回地址;但是實際我們執行後,esp的值變為0xbffff404,其中的內容剛好是0x61616161,也就是aaaa的值;這裡我們測試時使用的參數的頭四個字元剛好是aaaa;

到這裡為止,就明白整個問題的癥結:
 Main函數中調用leave指令時,esp的值並沒有調整到位。本來應該指向43c(前面的地址忽略)的,此處卻指向的404?
 那麼為什麼會產生這樣的狀況?照理說這是編譯器該乾的事,為什麼此處編譯器沒有盡責呢?

奇怪的是:
 不發生棧溢出時,也即我們輸入的字串長度不超過12時,main中的leave指令執行後,程式可以正常的返回到43c的位置,順利退出;
 那這裡的問題就很奇怪了:棧溢出是發生在overflow中,程式可以從overflow中順利返回到main中,然後main的leave指令就不正常了;如果overflow中沒有棧溢出,程式也順利返回到main函數,然後main的leave指令可以正常工作。

上述的問題的癥結在於搞清楚leave指令本身;猜想其可能會依賴某些寄存器,或者依賴特定的儲存單元來達到恢複目的。

首先看看能不能stepi進入leave;答案是leave是單條指令,不是一個處理函數;
所以我們試試能不能從寄存器上發現點什麼,發現其實對照vistual studio中的操作,
Leave指令應該等效為:
 Mov  esp, ebp
 Pop  ebp

之前關注的都是esp,那按照上面的等效的話,接下來應該關注ebp寄存器的變化。所以,接下來要做的1. 首先驗證上述的的等效成立,2. 要盯著ebp在執行過程中的變化。

對於問題1的驗證,我們偷個懶,直接baidu之,發現的確符合我們的猜想,leave指令主要就是恢複esp和ebp之前的儲存值。(後面順帶地測試了下,的確也主要做了對應的操作。)

如此我們就回到問題2,主要查看進入overflow和退出overflow時,以及進入main和退出main時,寄存器ebp值的變化。

會溢出的版本下,我們查看call strcpy前和後的ebp的值,如所示,我們發現調用strcpy函數的前與後過程中,ebp的值都是418(省略前面的),也就是說,調用strcpy函數過程前後ebp的值是正常的。

那麼後續的執行leave指令,按照正常的版本(經過正常版本的驗證),那麼esp = 418,然後再經過pop ebp後,esp的值變為41c;而ebp的值應該恢複為438;

實際的執行後,esp如預期的發生變化,但是其中ebp的值卻沒有按照預想中的變化;那麼問題只可能出在pop ebp這個語句,也只有一個原因:就是ebp儲存的棧中的地址的內容被修改了,也就意味著418地址處的值在函數調用過程中被修改為400,原來應該是438。

那可能出現這種情況的也只有在overflow函數調用中,可能發生這樣的情況。根據上述的分析,那我們在棧溢出版本中,在strcpy調用前後查看418處的值,即可證明這點。果然下面的圖示正好說明該問題:

執行完strcpy函數後,看倒數最後一行,0xbffff418處的內容被修改為0xbffff400;作為對比,我們來看看非溢出版本的情況:

可以發現最後一行的ebp值沒有被修改,因此,不會發生錯誤。

現在可以確定:ebp的值在溢出的strcpy調用中被修改了,從而導致主函數退出時的問題。那下面的問題是:strcpy是怎麼樣修改ebp值的?這個問題的解答要深入到strcpy的彙編代碼裡面,才能得到答案,本文不作探討,有興趣的可以再深入研究。

結論與啟發:
1. 雖然函數是否可以順利返回取決於棧上的返回地址,但是此例也讓我們看到通過間接地修改ebp的值,也可以達到控制返回地址的目的;只不過這裡的修改ebp值不會影響到當前函數的返回,但會影響上一級函數的返回;

2. 雖然對linux下的彙編不熟,但是借鑒visual studio下的代碼的熟悉,還是容易讀通linux下的彙編的;由此,可以體會的借鑒的價值,而借鑒的前提在於對某些技術的深入;

3. 找問題過程中,採用了對比的手法,比如對比BSD下的上述代碼版本,可以按照設想的執行;讓我確認上述代碼的確存在問題;由此,可以體會對比的價值;

Linux下的棧溢出案例分析-GDB調試操練

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在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.