先來看看基於 Red Hat 與 Fedora 衍生版(例如 CentOS)系統用於阻止棧溢出攻擊的內核參數,主要包含兩項:
kernel.exec-shield
可執行棧保護,字面含義比較“繞”,
實際上就是用來控制能否執行存儲在棧
中的代碼,其值為1時表示禁止;為0時表示允許;默認為1,表示禁止執行棧
中的代碼,如此一來,即便覆蓋了函數的返回地址導致棧溢出,也無法執行
shellcode
查看與修改系統當前的可執行棧保護參數:
[root@localhost 桌面]# sysctl -a | grep -e exec
ernel.exec-shield = 1
[root@localhost 桌面]# cat /proc/sys/kernel/exec-shield
1
[root@localhost 桌面]# sysctl -w kernel.exec-shield=0
kernel.exec-shield = 0
[root@localhost 桌面]# cat /proc/sys/kernel/exec-shield
0
注意:
一,只有在學習和測試棧溢出攻擊的原理時,才建議關閉可執行棧保護機制
二,在基於 Debian 與 Ubuntu 衍生版
(例如 BackTrack 5 Release 3)的系統上,不支持可執行棧保護機制:
root@bt:~# uname -a
Linux bt 3.2.6 #1 SMP Fri Feb 17 10:40:05 EST 2012 i686 GNU/Linux
root@bt:~# sysctl -a | grep -e kernel.exec
error: permission denied on key 'vm.compact_memory'
error: permission denied on key 'dev.parport.parport0.autoprobe'
error: permission denied on key 'dev.parport.parport0.autoprobe0'
error: permission denied on key 'dev.parport.parport0.autoprobe1'
error: permission denied on key 'dev.parport.parport0.autoprobe2'
error: permission denied on key 'dev.parport.parport0.autoprobe3'
error: permission denied on key 'net.ipv4.route.flush'
error: permission denied on key 'net.ipv6.route.flush'
root@bt:~#
或許是社區的開發人員認為,有下面另一種叫做堆棧地址隨機化的機制,就足夠應對緩沖區溢出攻擊了,也可能是由於 Debian / Ubuntu 面向運行桌面應用居多的用戶群體,它並不像銷售給企業公司用戶的 Red Hat 類發行版那樣,對安全性的要求更為嚴格,才能保護用戶的服務器,資產安全
kernel.randomize_va_space
堆棧地址隨機初始化,很好理解,就是在每次將程序加載到內存時,進程地
址空間的堆棧起始地址都不一樣,動態變化,導致猜測或找出地址來執行
shellcode 變得非常困難,它和可執行棧保護的區別在於,后者即便是找到了
地址也是無法執行的;所有的 2.6 以上版本的 Linux 內核都支持並啟用了
這一項特性;
查看與修改系統當前的堆棧地址隨機初始化參數:
[root@localhost 桌面]# sysctl -a | grep randomize
kernel.randomize_va_space = 2
[root@localhost 桌面]# cat /proc/sys/kernel/randomize_va_space
2
[root@localhost 桌面]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@localhost 桌面]# cat /proc/sys/kernel/randomize_va_space
0
參數值為2時,表示啟用隨機地址功能;0表示關閉;
基於 Debian 與 Ubuntu 衍生版默認支持並且啟用了隨機地址功能:
1
2
root@bt:~# sysctl -a | grep -e kernel.randomize
kernel.randomize_va_space = 2
下面,我們在一個啟用了隨機地址功能的機器上,查看執行同一個程序4次加載的動態鏈接共享庫的入口地址(linux-gate.so.1),發現每次的入口地址都不同,驗證了隨機地址的效果:
[root@centos6-5 桌面]# cat /proc/sys/kernel/randomize_va_space
2
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00dff000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00503000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00213000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x004b5000)
[root@centos6-5 桌面]#
同理,關閉隨機地址功能,連續5次查看執行程序時加載的 linux-gate.so.1 入口地址:
[root@centos6-5 桌面]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
[root@centos6-5 桌面]# ldd /bin/ls | grep linux-gate.so.1
linux-gate.so.1 => (0x00110000)
再來看看 GCC 4.1 版本以后引入的兩個編譯參數:
(默認情況下,編譯時不會指定這兩個參數,用於阻止缺乏安全編碼意識的程序員寫出存在緩沖區溢出漏洞的程序,
但是站在逆向工程的角度來看,指定這兩個參數,特意構造有漏洞的程序,對於學習和理解棧溢出的原理還是有幫助的;
再次提醒,這里介紹的兩個編譯參數必須同時打開,而且還要同時禁用前面講的可執行棧保護與棧地址隨機初始化,滿足這4個條件后,一般而言,就可以測試 shellcode 的效果了
)
GCC 編譯選項 -fno-stack-protector
禁用棧保護功能,默認是啟用的;
gcc 的棧保護機制是指,在棧的緩沖區(大小通常由程序員給定)寫入內容前
,在結束地址之后與返回地址之前,放入隨機的驗證碼,由於棧幀是從內存高
址段向內存低址段增長的(回顧第一張圖),結束地址在高址段;返回地
址在低址段,棧溢出時,會從高址段向低址段覆蓋數據,因此如果想要覆蓋返
回地址的內容,必定先覆蓋結束地址,驗證碼,才能覆蓋返回地址,
因此可以通過比較寫入緩沖區前后的驗證碼是否發生改變,來檢測並阻止溢出
攻擊
GCC 編譯選項 -z execstack
啟用可執行棧,默認是禁用的;
該選項與 ld 鏈接器有關:ld 鏈接器在鏈接程序的時候,如果所有的 .o 文
件的堆棧段都標記為不可執行,那么整個庫的堆棧段才會被標記為不可執行;
相反,即使只有一個 .o 文件的堆棧段被標記為可執行,那么整個庫的堆棧段
將被標記為可執行,
換言之,默認是所有的 .o 文件的堆棧段都標記為不可執行
檢查堆棧段可執行性的方法是:
1
[root@centos6-5vm /]# readelf -lW stack-overflow1-test | grep GNU_STACK
找出在輸出中有無 E 標記;有 E 標記就說明堆棧段是可執行的,其中,
stack-overflow1-test 為我們編寫的簡單棧溢出測試程序,源碼如下:
#include <stdio.h>
greeting(char *temp1, char *temp2){
char name[20];
strcpy(name, temp2);
printf("hello %s %s\n", temp1, name);
}
main(int argc, char *argv[]){
greeting(argv[1], argv[2]);
printf("bye %s %s\n", argv[1], argv[2]);
}
這是一個典型的存在緩沖區溢出漏洞的程序,greeting 函數定義了一個只有20字節大小的字符數組(緩沖區),strcpy 函數不檢查用戶輸入的第二個參數(由 main 函數的第二個參數傳遞)是否超過20字節,就寫入這個緩沖區中
我們用 gcc 默認參數配置來編譯這個源文件,然后檢查堆棧段的可執行性:
[root@centos6-5vm /]# gcc -v -Wall -o stack-overflow1-test stack-overflow1-test.c
(***************省略部分輸出*************
GNU C (GCC) 版本 4.4.7 20120313 (Red Hat 4.4.7-4) (i686-redhat-linux)
由 GNU C 版本 4.4.7 20120313 (Red Hat 4.4.7-4) 編譯,GMP 版本 4.3.1,MPFR 版本 2.4.1。
GGC 准則:--param ggc-min-expand=100 --param ggc-min-heapsize=131072
Compiler executable checksum: 5f02f32570d532de29ae0b402446343a
stack-overflow1-test.c:2: 警告:返回類型默認為‘int’
stack-overflow1-test.c: 在函數‘greeting’中:
stack-overflow1-test.c:4: 警告:隱式聲明函數‘strcpy’
stack-overflow1-test.c:4: 警告:隱式聲明與內建函數‘strcpy’不兼容
stack-overflow1-test.c: 在文件層:
stack-overflow1-test.c:8: 警告:返回類型默認為‘int’
stack-overflow1-test.c: 在函數‘main’中:
stack-overflow1-test.c:11: 警告:在有返回值的函數中,控制流程到達函數尾
stack-overflow1-test.c: 在函數‘greeting’中:
stack-overflow1-test.c:6: 警告:在有返回值的函數中,控制流程到達函數尾
COLLECT_GCC_OPTIONS='-v' '-Wall' '-o' 'stack-overflow1-test' '-mtune=generic' '-march=i686'
as -V -Qy -o /tmp/ccv9qpvk.o /tmp/ccT7rHyO.s
GNU assembler version 2.20.51.0.2 (i686-redhat-linux) using BFD version version 2.20.51.0.2-5.36.el6 20100205
***************省略部分輸出*************
可以看到, -Wall 選項(參考前文解釋)顯示所有的警告和錯誤信息,對於增加程序的可移植性非常有幫助,例如它指出在源碼的二行,greeting 自定義函數沒有定義返回類型,將采用默認返回類型:int
另外,在 greeting 函數中,調用 strcpy 函數前未聲明和定義(在程序中調用 strcpy 函數需要包含系統頭文件 string.h)
同樣,我們沒有為 main 函數指定返回類型
在上面的例子中,雖然每個警告都非致命的語法或詞法錯誤,但是 -Wall 選項確實可以“強制”培養程序員的良好編程習慣
言歸正傳,檢查生成的二進制可執行文件:
[root@centos6-5vm /]# readelf -lW stack-overflow1-test | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RW 0x4
其中沒有 E 標記,這說明該程序即便存在溢出漏洞,但是由於 GCC 的堆棧段不可執行保護機制,該漏洞也沒有太大被利用的可能性
我們“故意”關掉 GCC 的堆棧段不可執行保護機制:指定 -z execstack 選項,再次編譯源文件:
[root@centos6-5vm /]# gcc -v -Wall -z execstack -o stack-overflow1-test stack-overflow1-test.c
[root@centos6-5vm /]# readelf -lW stack-overflow1-test | grep GNU_STACK
GNU_STACK 0x000000 0x00000000 0x00000000 0x00000 0x00000 RWE 0x4
這次的輸出中,帶有 E 標記,說明堆棧段是可執行的,
再次強調,在 CentOS6.5 中,要真正能利用這個程序測試你編寫的 shellcode ,需要執行下面操作:
[root@localhost 桌面]# sysctl -w kernel.randomize_va_space=0
kernel.randomize_va_space = 0
[root@localhost 桌面]# sysctl -w kernel.exec-shield=0
kernel.exec-shield = 0
[root@localhost 桌面]# gcc -v -fno-stack-protector -z execstack -g -o stack-overflow1-test stack-overflow1-test.c
ubuntu –buffer-overflow
Ubuntu下面的GCC默認開啟了Stack Smashing Protector,如果想在這個系統中學習緩沖區溢出的原理,在編譯時要加上fno-stack-protector選項,否則運行時會出現*** stack smashing detected ***: xxx terminated,而不是期望的Segmentation fault。同時還需要加上允許棧執行的選項。
gcc -fno-stack-protector -z execstack -mpreferred-stack-boundary=2 -ggdb -o xxx xxx.c