該類錯誤是修改了返回指針,通常是因爲數組
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有一種針對緩衝區溢出的保護機制,可經過選項「-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. 緩衝區溢出攻擊
我一直有個疑問:程序代碼加載在代碼段上,程序數據保存在數據段與堆棧段上,原本是河水不犯井水。緩衝區溢出只發生在數據段與堆棧段,只要規定這兩 個段的內容可讀寫但不可執行,不就不須要擔憂緩衝區溢出攻擊了嗎?但事實上,許多程序爲了提升效率,會在堆棧段上動態地生成可執行代碼,好比Linux本 身就這樣作,因此「數據段和堆棧段不可執行」這樣的說法是錯的。
Intel和AMD已經在一些CPU上加入了「防病毒」功能,這種CPU可區分哪些地址空間能夠執行代碼、而哪些地址空間不能夠執行。
經過實驗,知道GCC有必定的保護機制,可防止緩衝區溢出攻擊的發生。但這種方法有必定的侷限性,因此仍有攻擊的可能。我彙編很懶,有不少問題沒有 徹底搞明白。因爲我很懶也沒有毅力(我很痛恨本身的這種做風),不明白的地方留待「之後」研究。至於用Firefox瀏覽網頁究竟會不會中毒的問題,應該 也仍是會吧?