歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux編程 >> Linux編程

如何利用GCC編譯選項檢測棧溢出

Stack smashing是堆棧緩沖區溢出(stack buffer overflow)的一個時髦稱謂。它表示利用代碼中存在的緩沖區溢出bug而發起的攻擊。在早期,這完全是程序員的責任,他們要確保代碼中不存在緩沖區溢出的問題。但是隨著時間推移,技術的不斷發展,現在像gcc這樣的編譯器已經有編譯選項用來確保緩沖區溢出問題不被攻擊者利用來破壞系統或者程序。

有一次當我試圖重現一個緩沖區溢出的問題時我才了解到這些編譯選項。我是在Ubuntu 12.04上進行試驗的,gcc版本為4.6.3。我所做的很簡單:

#include <stdio.h>
#include <string.h>

int main(void)
{
    int len = 0;
    char str[10] = {0};

    printf("\n Enter the name \n");

    gets(str); // Used gets() to cause buffer overflow

    printf("\n len = [%d] \n", len);

    len  = strlen(str);
    printf("\n len of string entered is : [%d]\n", len);

    return 0;
}

在上面的代碼中,我故意使用gets()函數來接收字符串,之後計算字符串的長度,並輸出到標准輸出—默認是屏幕。這裡的想法是輸入超過10個長度的字符串,由於gets()函數並不檢測數組邊界,所以它將會把字符寫入到10個以外的地址,這樣就會發生緩沖區溢出。我運行程序之後的結果如下所示:

$ ./stacksmash

 Enter the name
TheGeekStuff

 len = [0]

 len of string entered is : [12]
*** stack smashing detected ***: ./stacksmash terminated
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(__fortify_fail+0x45)[0xb76e4045]
/lib/i386-linux-gnu/libc.so.6(+0x103ffa)[0xb76e3ffa]
./stacksmash[0x8048548]
/lib/i386-linux-gnu/libc.so.6(__libc_start_main+0xf3)[0xb75f94d3]
./stacksmash[0x8048401]
======= Memory map: ========
08048000-08049000 r-xp 00000000 08:06 528260    /home/himanshu/practice/stacksmash
08049000-0804a000 r--p 00000000 08:06 528260    /home/himanshu/practice/stacksmash
0804a000-0804b000 rw-p 00001000 08:06 528260    /home/himanshu/practice/stacksmash
0973a000-0975b000 rw-p 00000000 00:00 0          [heap]
b75af000-b75cb000 r-xp 00000000 08:06 787381    /lib/i386-linux-gnu/libgcc_s.so.1
b75cb000-b75cc000 r--p 0001b000 08:06 787381    /lib/i386-linux-gnu/libgcc_s.so.1
b75cc000-b75cd000 rw-p 0001c000 08:06 787381    /lib/i386-linux-gnu/libgcc_s.so.1
b75df000-b75e0000 rw-p 00000000 00:00 0
b75e0000-b7783000 r-xp 00000000 08:06 787152    /lib/i386-linux-gnu/libc-2.15.so
b7783000-b7784000 ---p 001a3000 08:06 787152    /lib/i386-linux-gnu/libc-2.15.so
b7784000-b7786000 r--p 001a3000 08:06 787152    /lib/i386-linux-gnu/libc-2.15.so
b7786000-b7787000 rw-p 001a5000 08:06 787152    /lib/i386-linux-gnu/libc-2.15.so
b7787000-b778a000 rw-p 00000000 00:00 0
b7799000-b779e000 rw-p 00000000 00:00 0
b779e000-b779f000 r-xp 00000000 00:00 0          [vdso]
b779f000-b77bf000 r-xp 00000000 08:06 794147    /lib/i386-linux-gnu/ld-2.15.so
b77bf000-b77c0000 r--p 0001f000 08:06 794147    /lib/i386-linux-gnu/ld-2.15.so
b77c0000-b77c1000 rw-p 00020000 08:06 794147    /lib/i386-linux-gnu/ld-2.15.so
bfaec000-bfb0d000 rw-p 00000000 00:00 0          [stack]
Aborted (core dumped)

令我驚訝的是,運行環境居然可以檢測到緩沖區溢出的情況。你可以在輸出信息上看到“檢測到棧溢出”(stack smashing detected)的信息。這促使我去探索緩沖區溢出是如何被檢測到的。

當我探索原因時,我發現了gcc的一個編譯選項:-fstack-protector,以下是關於這個選項的描述:

-fstack-protector

啟用該選項後編譯器會產生額外的代碼來檢測緩沖區溢出,例如棧溢出攻擊。這是通過在有缺陷的函數中添加一個保護變量來實現的。這包括會調用到alloca的函數,以及具有超過8個字節緩沖區的函數。當執行到這樣的函數時,保護變量會得到初始化,而函數退出時會檢測保護變量。如果檢測失敗,會輸出一個錯誤信息並退出程序。

!注意:在Ubuntu 6.10以及之後的版本中,如果編譯時沒有指定-fno-fstack-protector, -nostdlib或者-ffreestanding選項的話,那麼這個選項對於C,C++,ObjC,ObjC++語言默認是啟用的。

所以,你會發現gcc已經使用插入附加代碼的方式來檢測緩沖區溢出的問題。我想到的下一個問題是,我從來沒有在編譯時加入這個編譯選項,這個功能是怎樣啟用的?然後我讀到最後兩行,在Ubuntu6.10之後的版本上,此功能已經默認啟用了。

下一步,我決定使用-fno-fstack-protector選項來取消這個棧溢出檢測功能。我對同樣的代碼編譯之後運行,使用和之前一樣輸入,下面是我的做法以及運行結果:

$ gcc -Wall -fno-stack-protector stacksmash.c -o stacksmash
$ ./stacksmash

 Enter the name
TheGeekStuff

 len = [26214]

 len of string entered is : [12]

可以看到,一旦使用了這個編譯選項(根據前面的編譯選項說明,這裡-fstack-protector是不會默認開啟的),使用相同的輸入,運行環境根本無法檢測到緩沖區溢出的問題,len的值已經被破壞了。

當然,如果你對gcc很陌生,你也應該理解我們之前討論過的最常用的gcc編譯選項。

Copyright © Linux教程網 All Rights Reserved