緩沖區溢出的保護機制
緩沖區溢出原理
緩沖區是內存中存放數據的地方。在程序試圖將數據放到機器內存中的某一個位置的時候,因為沒有足夠的空間就會發生緩沖區溢出。而人為的溢出則是有一定企圖的,攻擊者寫一個超過緩沖區長度的字符串,植入到緩沖區,然后再向一個有限空間的緩沖區中植入超長的字符串,這時可能會出現兩個結果:一是過長的字符串覆蓋了相鄰的存儲單元,引起程序運行失敗,嚴重的可導致系統崩潰;另一個結果就是利用這種漏洞可以執行任意指令,甚至可以取得系統root特級權限。
緩沖區是程序運行的時候機器內存中的一個連續塊,它保存了給定類型的數據,隨著動態分配變量會出現問題。大多時為了不占用太多的內存,一個有動態分配變量的程序在程序運行時才決定給它們分配多少內存。如果程序在動態分配緩沖區放入超長的數據,它就會溢出了。一個緩沖區溢出程序使用這個溢出的數據將匯編語言代碼放到機器的內存里,通常是產生root權限的地方。僅僅單個的緩沖區溢出并不是問題的根本所在。但如果溢出送到能夠以root權限運行命令的區域,一旦運行這些命令,那可就等于把機器拱手相讓了。
造成緩沖區溢出的原因是程序中沒有仔細檢查用戶輸入的參數。例如下面程序:
void func1(char *input)
{
char buffer[16];
strcpy(buffer, input);
}
上面的strcpy()將直接吧input中的內容copy到buffer中。這樣只要input的長度大于16,就會造成buffer的溢出,使程序運行出錯。存在像strcpy這樣的問題的標準函數還有strcat(),sprintf(),vsprintf(),gets(),scanf(),以及在循環內的getc(),fgetc(),getchar()等。
當然,隨便往緩沖區中填東西造成它溢出一般只會出現Segmentation fault 錯誤,而不能達到攻擊的目的。最常見的手段是通過制造緩沖區溢出使程序運行一個用戶shell,再通過shell執行其他命令。如果該程序屬于root且有suid權限的話,攻擊者就獲得了一個有root權限的shell,便可以對系統進行任意操作了。
請注意,如果沒有特別說明,下面的內容都假設用戶使用的平臺為基于Intel x86 CPU的Linux系統。對其他平臺來說,本文的概念同樣適用,但程序要做相應修改。
CANNARY(棧保護)
這個選項表示棧保護功能有沒有開啟。
棧溢出保護是一種緩沖區溢出攻擊緩解手段,當函數存在緩沖區溢出攻擊漏洞時,攻擊者可以覆蓋棧上的返回地址來讓shellcode能夠得到執行。當啟用棧保護后,函數開始執行的時候會先往棧里插入cookie信息,當函數真正返回的時候會驗證cookie信息是否合法,如果不合法就停止程序運行。攻擊者在覆蓋返回地址的時候往往也會將cookie信息給覆蓋掉,導致棧保護檢查失敗而阻止shellcode的執行。在Linux中我們將cookie信息稱為canary。
gcc在4.2版本中添加了-fstack-protector和-fstack-protector-all編譯參數以支持棧保護功能,4.9新增了-fstack-protector-strong編譯參數讓保護的范圍更廣。
因此在編譯時可以控制是否開啟棧保護以及程度,例如:
gcc -fno-stack-protector -o test test.c //禁用棧保護
gcc -fstack-protector -o test test.c //啟用堆棧保護,不過只為局部變量中含有 char 數組的函數插入保護代碼
gcc -fstack-protector-all -o test test.c //啟用堆棧保護,為所有函數插入保護代碼
FORTIFY
這個保護機制查了很久都沒有個很好的漢語形容,根據我的理解它其實和棧保護都是gcc的新的為了增強保護的一種機制,防止緩沖區溢出攻擊。由于并不是太常見,也沒有太多的了解。
舉個例子可能簡單明了一些:
一段簡單的存在緩沖區溢出的C代碼
void fun(char *s) {
char buf[0x100];
strcpy(buf, s);
/* Don't allow gcc to optimise away the buf */
asm volatile("" :: "m" (buf));
}
用包含參數-UFORTIFYSOURCE編譯
08048450 <fun>:
push %ebp ;
mov %esp,%ebp
sub $0x118,%esp ; 將0x118存儲到棧上
mov 0x8(%ebp),%eax ; 將目標參數載入eax
mov %eax,0x4(%esp) ; 保存目標參數
lea -0x108(%ebp),%eax ; 數組buf
mov %eax,(%esp) ; 保存
call 8048320 <strcpy@plt>
leave ;
ret
用包含參數-DFORTIFYSOURCE=2編譯
08048470 <fun>:
push %ebp ;
mov %esp,%ebp
sub $0x118,%esp ;
movl $0x100,0x8(%esp) ; 把0x100當作目標參數保存
mov 0x8(%ebp),%eax ;
mov %eax,0x4(%esp) ;
lea -0x108(%ebp),%eax ;
mov %eax,(%esp) ;
call 8048370 <__strcpy_chk@plt>
leave ;
ret
我們可以看到gcc生成了一些附加代碼,通過對數組大小的判斷替換strcpy, memcpy, memset等函數名,達到防止緩沖區溢出的作用。
NX(DEP)
NX即No-eXecute(不可執行)的意思,NX(DEP)的基本原理是將數據所在內存頁標識為不可執行,當程序溢出成功轉入shellcode時,程序會嘗試在數據頁面上執行指令,此時CPU就會拋出異常,而不是去執行惡意指令。
gcc編譯器默認開啟了NX選項,如果需要關閉NX選項,可以給gcc編譯器添加-z execstack參數。
例如:
gcc -z execstack -o test test.c
在Windows下,類似的概念為DEP(數據執行保護),在最新版的Visual Studio中默認開啟了DEP編譯選項。
PIE(ASLR)
一般情況下NX(Windows平臺上稱其為DEP)和地址空間分布隨機化(ASLR)會同時工作。
內存地址隨機化機制(address space layout randomization),有以下三種情況
0 - 表示關閉進程地址空間隨機化。
1 - 表示將mmap的基址,stack和vdso頁面隨機化。
2 - 表示在1的基礎上增加棧(heap)的隨機化。
可以防范基于Ret2libc方式的針對DEP的攻擊。ASLR和DEP配合使用,能有效阻止攻擊者在堆棧上運行惡意代碼。
Built as PIE:位置獨立的可執行區域(position-independent executables)。這樣使得在利用緩沖溢出和移動操作系統中存在的其他內存崩潰缺陷時采用面向返回的編程(return-oriented programming)方法變得難得多。
liunx下關閉PIE的命令如下:
sudo -s echo 0 > /proc/sys/kernel/randomize_va_space
ASLR效果
1、映像隨機化
映像隨機化是在PE文件映射到內存時,對其加載的虛擬地址進行隨機化處理,這個地址是在系統啟動時確定的,系統重啟后這個地址會有變化。
2、堆棧隨機化
程序運行時隨機選擇堆棧的基址,與映像隨機化不同的是堆棧的基址不是在系統啟動時確定的,而是在程序打開時確定的,也就是說同一個程序任意兩次運行時的堆棧基址都不相同。
3、PEB與TEB隨機化
微軟在XP SP2之后不再使用固定的PEB基址0x7FFDF000和TEB基址0x7FFDE000,而是使用具有一定隨機性的基址。
TEB存放在FS:0和FS:[0x18]處,PEB存放在TEB偏移0x30的位置
RELRO
設置符號重定向表格為只讀或在程序啟動時就解析并綁定所有動態符號,從而減少對GOT(Global Offset Table)攻擊。
檢測工具checksec
checksec是一個腳本軟件,也就是用腳本寫的一個文件,不到2000行,可用來學習shell。
源碼參見
http://www.trapkit.de/tools/checksec.html
https://github.com/slimm609/checksec.sh/
下載方法之一為
wget https://github.com/slimm609/checksec.sh/archive/1.6.tar.gz
checksec到底是用來干什么的?
它是用來檢查可執行文件屬性,例如PIE, RELRO, PaX, Canaries, ASLR, Fortify Source等等屬性。
checksec的使用方法:
checksec –file /usr/sbin/sshd
一般來說,如果是學習二進制漏洞利用的朋友,建議大家使用gdb里peda插件里自帶的checksec功能,如下:
gdb-peda$ checksec start
CANARY : disabled
FORTIFY : disabled
NX : ENABLED
PIE : disabled
RELRO : disabled