*** stack smashing detected ***: ./server terminated

該類錯誤是修改了返回指針,通常是因爲數組

1. 數組越界賦值。(數組沒有邊界檢查)int a[8]; a[8],a[9],a[-1]。。都能正常編譯,鏈接,運行時可能出錯。安全

2.使用 strcpy等不安全(不帶長度檢測的函數),char a[1], char *b="aaa"; strcpy(a,b);函數

 

局部變量(函數內的變量)存在棧中,應爲棧是先下(低地址)生長的,故 函數返回指針 要比局部變量的地址高,像相似的a[8]之類的就有機會訪問到 函數返回指針了。spa

 

首先運行第一個程序:操作系統

#include 「string.h」
void fun1(const char *str)
{
   char buffer[5];
  strcpy((char*)buffer, (char*)str);
}
int main()
{
   fun1(」AAAAAAAAAAAAAAAAAAAAAAAAA」);
}指針

程序執行結果是「段錯誤」。用GDB調試,在從fun1函數返回時出現「Cannot access memory at address 0×41414145」的錯誤提示,這大體符合指望。但有一點搞不明白的是被改寫的返回地址是’AAAA’,即應該是0×41414141纔對,實際狀況 怎麼會多了4個字節 (0×41414145)?調試

這個程序是因爲返回地址無效致使錯誤,那麼我就寫一個有效的地址上去吧。運行第二個程序:blog

void fun1()
{
   int i;
   const char buffer[] = 「111111111″;
   for (i = 0; i < 20; i++)
     *((int*)(buffer+i)) = (int)buffer;
}
int main()
{
   fun1();
}字符串

程序的返回地址被改寫成「有效」的地址「buffer」,只是內容確實無效的指令「11111111」,結果程序的運行結果是:get

*** stack smashing detected ***: ./a.out terminated

 

GCC的緩衝區溢出保護

經過查閱資料知道,GCC有一種針對緩衝區溢出的保護機制,可經過選項「-fno-stack-protector」來將其關閉。實驗中出現的錯誤信息,就正好是檢測到緩衝區溢出而致使的錯誤信息。見出錯程序的彙編代碼:

<fun1>:
push   %ebp
mov    %esp,%ebp
sub    $0×28,%esp
mov    %gs:0×14,%eax
mov    %eax,-0×4(%ebp)        ; 把%gs:0×14保存到-0×4(%ebp) 中 
xor    %eax,%eax
movl   $0×31313131,-0xe(%ebp)
movl   $0×31313131,-0xa(%ebp)
movw   $0×31,-0×6(%ebp)
movl   $0×0,-0×14(%ebp)
jmp    8048423 <fun1+0×3f>
lea    -0xe(%ebp),%edx
mov    -0×14(%ebp),%eax
add    %eax,%edx
lea    -0xe(%ebp),%eax
mov    %eax,(%edx)
addl   $0×1,-0×14(%ebp)
cmpl   $0×13,-0×14(%ebp)
jle    8048412 <fun1+0×2e>
mov    -0×4(%ebp),%eax
xor    %gs:0×14,%eax        ; 檢查%gs:0×14與-0×4(%ebp)的值是否相同 
je     804843a <fun1+0×56>        ; 若是相同則退出函數
call   804831c <__stack_chk_fail@plt>        ; 檢測到緩衝區溢出,跳轉到__stack_chk_fail@plt函數
leave
ret
<main>:
lea    0×4(%esp),%ecx
and    $0xfffffff0,%esp
pushl  -0×4(%ecx)
push   %ebp
mov    %esp,%ebp
push   %ecx
sub    $0×4,%esp        ; 在棧上分配4字節空間。在進入函數以後,其地址至關於-0×4(%ebp) 
add    $0×4,%esp
pop    %ecx
pop    %ebp
lea    -0×4(%ecx),%esp

從彙編代碼看出,func2比func1只是多出了一條指令來爲臨時變量申請存儲空間,這與其C代碼相吻合;func3比func2卻多出了一大片 代碼,而其C代碼的不一樣卻只是臨時數組的類型的不一樣而已。經過分析可知,這些多出的代碼就是用來檢查緩衝區溢出的。在調用函數以前,在棧上分配4字節的存 儲空間。在進入函數以後,一個數(%gs:0×14)保存到這4字節空間中。在退出函數以前作一次檢查,若是剛纔保存的數被修改,那能夠確定是發生了緩衝 區溢出。

GS是附加段寄存器,我也沒搞明白是幹什麼用的,更加不清楚%gs:0×14的值何時被肯定。這種檢測方法的思路很簡單,就是在棧上放置一個隨 機數,而後檢查這個隨機數有沒有被改寫。因爲隨機數放置在返回地址以前,聰明的黑客應該能夠經過圖3的方式植入惡意代碼。但對黑客來講,這樣有兩個不便: (1)惡意代碼需植入到當前函數的棧幀上,空間很小,很難植入攻擊性強的代碼;(2)因爲主要是利用字符串拷貝來植入惡意惡意代碼,而字符串拷貝是遇到 ’/0′才結束的,這樣就要求惡意代碼起始地址的低8位必須爲全0,這樣才能夠恰好改寫返回地址,而不會進入「雷區」(隨機數)。在狹小的空間內,完成這 樣的操做實在難於登天;若是可用空間很大的話,這兩個問題其實也不難克服。

 

圖3. 緩衝區溢出攻擊

關於CPU和操做系統

我一直有個疑問:程序代碼加載在代碼段上,程序數據保存在數據段與堆棧段上,原本是河水不犯井水。緩衝區溢出只發生在數據段與堆棧段,只要規定這兩 個段的內容可讀寫但不可執行,不就不須要擔憂緩衝區溢出攻擊了嗎?但事實上,許多程序爲了提升效率,會在堆棧段上動態地生成可執行代碼,好比Linux本 身就這樣作,因此「數據段和堆棧段不可執行」這樣的說法是錯的。

Intel和AMD已經在一些CPU上加入了「防病毒」功能,這種CPU可區分哪些地址空間能夠執行代碼、而哪些地址空間不能夠執行。

小結

經過實驗,知道GCC有必定的保護機制,可防止緩衝區溢出攻擊的發生。但這種方法有必定的侷限性,因此仍有攻擊的可能。我彙編很懶,有不少問題沒有 徹底搞明白。因爲我很懶也沒有毅力(我很痛恨本身的這種做風),不明白的地方留待「之後」研究。至於用Firefox瀏覽網頁究竟會不會中毒的問題,應該 也仍是會吧?

文章摘自:http://fujinbing.iteye.com/blog/1866682

相關文章
相關標籤/搜索