先來看看基於 Red Hat 與 Fedora 衍生版(例如 CentOS)系統用於阻止棧溢出攻擊的內核參數,主要包含兩項:linux
可執行棧保護,字面含義比較「繞」,程序員
實際上就是用來控制可否執行存儲在棧shell
中的代碼,其值爲1時表示禁止;爲0時表示容許;默認爲1,表示禁止執行棧編程
中的代碼,如此一來,即使覆蓋了函數的返回地址致使棧溢出,也沒法執行ubuntu
shellcodecentos
查看與修改系統當前的可執行棧保護參數:數組
[root@localhost 桌面]# sysctl -a | grep -e exec安全
ernel.exec-shield = 1服務器
[root@localhost 桌面]# cat /proc/sys/kernel/exec-shielddom
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 類發行版那樣,對安全性的要求更爲嚴格,才能保護用戶的服務器,資產安全
堆棧地址隨機初始化,很好理解,就是在每次將程序加載到內存時,進程地
址空間的堆棧起始地址都不同,動態變化,致使猜想或找出地址來執行
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下面的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