在過去的十年中,以緩沖區(qū)溢出為類型的安全漏洞占是最為常見的一種形式了。更為嚴(yán)重的是,緩沖區(qū)溢出漏洞占了遠(yuǎn)程網(wǎng)絡(luò)攻擊的絕大多數(shù),這種攻擊可以使得一個(gè)匿名的Internet用戶有機(jī)會(huì)獲得一臺(tái)主機(jī)的部分或全部的控制權(quán)!如果能有效地消除緩沖區(qū)溢出的漏洞,則很大一部分的安全威脅可以得到緩解。在本文中,我們研究了各種類型的緩沖區(qū)溢出漏洞和攻擊手段,同時(shí)我們也研究了各種的防御手段,這些手段用來消除這些漏洞所造成的影響,其中包括我們自己的堆棧保護(hù)方法。然后我們要考慮如何在保證現(xiàn)有系統(tǒng)功能和性能不變的情況下,如何使用這些方法來消除這些安全漏洞。
一、前言
在過去的十年中,以緩沖區(qū)溢出為類型的安全漏洞占是最為常見的一種形式了。更為嚴(yán)重的是,緩沖區(qū)溢出漏洞占了遠(yuǎn)程網(wǎng)絡(luò)攻擊的絕大多數(shù),這種攻擊可以使得一個(gè)匿名的Internet用戶有機(jī)會(huì)獲得一臺(tái)主機(jī)的部分或全部的控制權(quán)!由于這類攻擊使任何人都有可能取得主機(jī)的控制權(quán),所以它代表了一類極其嚴(yán)重的安全威脅。
緩沖區(qū)溢出攻擊之所以成為一種常見安全攻擊手段其原因在于緩沖區(qū)溢出漏洞太普通了,并且易于實(shí)現(xiàn)。而且,緩沖區(qū)溢出成為遠(yuǎn)程攻擊的主要手段其原因在于緩沖區(qū)溢出漏洞給予了攻擊者他所想要的一切:殖入并且執(zhí)行攻擊代碼。被殖入的攻擊代碼以一定的權(quán)限運(yùn)行有緩沖區(qū)溢出漏洞的程序,從而得到被攻擊主機(jī)的控制權(quán)。
比如,在1998年Lincoln實(shí)驗(yàn)室用來評(píng)估入侵檢測(cè)的的5種遠(yuǎn)程攻擊中,有3種是基于社會(huì)工程學(xué)的信任關(guān)系,2種是緩沖區(qū)溢出。而在1998年CERT的13份建議中,有9份是是與緩沖區(qū)溢出有關(guān)的,在 1999年,至少有半數(shù)的建議是和緩沖區(qū)溢出有關(guān)的。在Bugtraq的調(diào)查中,有2/3的被調(diào)查者認(rèn)為緩沖區(qū)溢出漏洞是一個(gè)很嚴(yán)重的安全問題。
緩沖區(qū)溢出漏洞和攻擊有很多種形式,我們會(huì)在第二部分對(duì)他們進(jìn)行描述和分類。相應(yīng)地防衛(wèi)手段也隨者攻擊方法的不同而不同,我們會(huì)放在第三部分描述,它的內(nèi)容包括針對(duì)每種攻擊類型的有效的防衛(wèi)手段。我們還要要介紹堆棧保護(hù)方法,這種方法在解決緩沖區(qū)溢出的漏洞方面很有效果,并且沒有犧牲系統(tǒng)的兼容性和性能。在第四部分,我們要討論各種防衛(wèi)方法的綜合使用。最后在第五部分是我們的結(jié)論。
二、緩沖區(qū)溢出的漏洞和攻擊
緩沖區(qū)溢出攻擊的目的在于擾亂具有某些特權(quán)運(yùn)行的程序的功能,這樣可以使得攻擊者取得程序的控制權(quán),如果該程序具有足夠的權(quán)限,那么整個(gè)主機(jī)就被控制了。一般而言,攻擊者攻擊root程序,然后執(zhí)行類似“exec(sh)”的執(zhí)行代碼來獲得root的shell,但不一直是這樣的。為了達(dá)到這個(gè)目的,攻擊者必須達(dá)到如下的兩個(gè)目標(biāo):
1. 在程序的地址空間里安排適當(dāng)?shù)拇a。
2. 通過適當(dāng)?shù)爻跏蓟拇嫫骱痛鎯?chǔ)器,讓程序跳轉(zhuǎn)到我們安排的地址空間執(zhí)行。
我們根據(jù)這兩個(gè)目標(biāo)來對(duì)緩沖區(qū)溢出攻擊進(jìn)行分類。在2.1部分,我們將描述攻擊代碼是如何放入被攻擊程序的地址空間的(這個(gè)就是“緩沖區(qū)”名字的的由來)。在2.2部分,我們介紹攻擊者如何使一個(gè)程序的緩沖區(qū)溢出,并且執(zhí)行轉(zhuǎn)移到攻擊代碼(這個(gè)就是“溢出”的由來)。在2.3部分,我們介紹綜合在2.1和2.2部分所討論的代碼安排和控制程序執(zhí)行流程的技術(shù)。
2.1 在程序的地址空間里安排適當(dāng)?shù)拇a的方法
有兩種在被攻擊程序地址空間里安排攻擊代碼的方法:
殖入法:
攻擊者向被攻擊的程序輸入一個(gè)字符串,程序會(huì)把這個(gè)字符串放到緩沖區(qū)里。這個(gè)字符串包含的數(shù)據(jù)是可以在這個(gè)被攻擊的硬件平臺(tái)上運(yùn)行的指令序列。在這里攻擊者用被攻擊程序的緩沖區(qū)來存放攻擊代碼。具體的方式有以下兩種差別:
1. 攻擊者不必為達(dá)到此目的而溢出任何緩沖區(qū),可以找到足夠的空間來放置攻擊代碼
2. 緩沖區(qū)可以設(shè)在任何地方:堆棧(自動(dòng)變量)、堆(動(dòng)態(tài)分配的)和靜態(tài)數(shù)據(jù)區(qū)(初始化或者未初始化的數(shù)據(jù))
利用已經(jīng)存在的代碼:
有時(shí)候,攻擊者想要的代碼已經(jīng)在被攻擊的程序中了,攻擊者所要做的只是對(duì)代碼傳遞一些參數(shù),然后使程序跳轉(zhuǎn)到我們的目標(biāo)。比如,攻擊代碼要求執(zhí)行 “exe("/bin/sh")”,而在libc庫中的代碼執(zhí)行“exec(arg)”,其中arg使一個(gè)指向一個(gè)字符串的指針參數(shù),那么攻擊者只要把傳入的參數(shù)指針改向指向"/bin/sh",然后調(diào)轉(zhuǎn)到libc庫中的相應(yīng)的指令序列。
2.2 控制程序轉(zhuǎn)移到攻擊代碼的方法
所有的這些方法都是在尋求改變程序的執(zhí)行流程,使之跳轉(zhuǎn)到攻擊代碼。最基本的就是溢出一個(gè)沒有邊界檢查或者其他弱點(diǎn)的緩沖區(qū),這樣就擾亂了程序的正常的執(zhí)行順序。通過溢出一個(gè)緩沖區(qū),攻擊者可以用近乎暴力的方法改寫相鄰的程序空間而直接跳過了系統(tǒng)的檢查。
這里分類的基準(zhǔn)是攻擊者所尋求的緩沖區(qū)溢出的程序空間類型。原則上是可以任意的空間。比如,最初的Morris Worm使用了fingerd程序的緩沖區(qū)溢出,擾亂fingerd要執(zhí)行的文件的名字。實(shí)際上,許多的緩沖區(qū)溢出是用暴力的方法來尋求改變程序指針的。這類程序的不同的地方就是程序空間的突破和內(nèi)存空間的定位不同。
激活紀(jì)錄(Activation Records):
每當(dāng)一個(gè)函數(shù)調(diào)用發(fā)生時(shí),調(diào)用者會(huì)在堆棧中留下一個(gè)激活紀(jì)錄,它包含了函數(shù)結(jié)束時(shí)返回的地址。攻擊者通過溢出這些自動(dòng)變量,使這個(gè)返回地址指向攻擊代碼。通過改變程序的返回地址,當(dāng)函數(shù)調(diào)用結(jié)束時(shí),程序就跳轉(zhuǎn)到攻擊者設(shè)定的地址,而不是原先的地址。這類的緩沖區(qū)溢出被稱為“stack smashing attack”,使目前常用的緩沖區(qū)溢出攻擊方式。
函數(shù)指針(Function Pointers):
“void (* foo)()”聲明了一個(gè)返回值為void函數(shù)指針的變量foo。函數(shù)指針可以用來定位任何地址空間,所以攻擊者只需在任何空間內(nèi)的函數(shù)指針附近找到一個(gè)能夠溢出的緩沖區(qū),然后溢出這個(gè)緩沖區(qū)來改變函數(shù)指針。在某一時(shí)刻,當(dāng)程序通過函數(shù)指針調(diào)用函數(shù)時(shí),程序的流程就按攻擊者的意圖實(shí)現(xiàn)了!它的一個(gè)攻擊范例就是在Linux系統(tǒng)下的superprobe程序。
長(zhǎng)跳轉(zhuǎn)緩沖區(qū)(Longjmp buffers):
在C語言中包含了一個(gè)簡(jiǎn)單的檢驗(yàn)/恢復(fù)系統(tǒng),稱為setjmp/longjmp。意思是在檢驗(yàn)點(diǎn)設(shè)定“setjmp(buffer)”,用“longjmp(buffer)”來恢復(fù)檢驗(yàn)點(diǎn)。然而,如果攻擊者能夠進(jìn)入緩沖區(qū)的空間,那么“longjmp(buffer)”實(shí)際上是跳轉(zhuǎn)到攻擊者的代碼。象函數(shù)指針一樣,longjmp緩沖區(qū)能夠指向任何地方,所以攻擊者所要做的就是找到一個(gè)可供溢出的緩沖區(qū)。一個(gè)典型的例子就是Perl 5.003,攻擊者首先進(jìn)入用來恢復(fù)緩沖區(qū)溢出的的longjmp緩沖區(qū),然后誘導(dǎo)進(jìn)入恢復(fù)模式,這樣就使Perl的解釋器跳轉(zhuǎn)到攻擊代碼上了!
2.3 綜合代碼殖入和流程控制技術(shù)
現(xiàn)在我們研究綜合代碼殖入和流程控制的技術(shù)。
最簡(jiǎn)單和常見的緩沖區(qū)溢出攻擊類型就是在一個(gè)字符串里綜合了代碼殖入和激活紀(jì)錄。攻擊者定位一個(gè)可供溢出的自動(dòng)變量,然后向程序傳遞一個(gè)很大的字符串,在引發(fā)緩沖區(qū)溢出改變激活紀(jì)錄的同時(shí)殖入了代碼。這個(gè)是由Levy指出的攻擊的模板。因?yàn)镃在習(xí)慣上只為用戶和參數(shù)開辟很小的緩沖區(qū),因此這種漏洞攻擊的實(shí)例不在少數(shù)。
代碼殖入和緩沖區(qū)溢出不一定要在在一次動(dòng)作內(nèi)完成。攻擊者可以在一個(gè)緩沖區(qū)內(nèi)放置代碼,這是不能溢出緩沖區(qū)。然后,攻擊者通過溢出另外一個(gè)緩沖區(qū)來轉(zhuǎn)移程序的指針。這種方法一般用來解決可供溢出的緩沖區(qū)不夠大(不能放下全部的代碼)的情況。
如果攻擊者試圖使用已經(jīng)常駐的代碼而不是從外部殖入代碼,他們通常有必須把代碼作為參數(shù)化。舉例來說,在libc(幾乎所有的C程序都要它來連接) 中的部分代碼段會(huì)執(zhí)行“exec(something)”,其中somthing就是參數(shù)。攻擊者然后使用緩沖區(qū)溢出改變程序的參數(shù),然后利用另一個(gè)緩沖區(qū)溢出使程序指針指向libc中的特定的代碼段。
三. 緩沖區(qū)溢出的保護(hù)方法
目前有四種基本的方法保護(hù)緩沖區(qū)免受緩沖區(qū)溢出的攻擊和影響。在3.1中介紹了強(qiáng)制寫正確的代碼的方法。在3.2中介紹了通過操作系統(tǒng)使得緩沖區(qū)不可執(zhí)行,從而阻止攻擊者殖入攻擊代碼。這種方法有效地阻止了很多緩沖區(qū)溢出的攻擊,但是攻擊者并不一定要殖入攻擊代碼來實(shí)現(xiàn)緩沖區(qū)溢出的攻擊(參見2.1 節(jié)),所以這種方法還是存在很弱點(diǎn)的。在3.3中,我們介紹了利用編譯器的邊界檢查來實(shí)現(xiàn)緩沖區(qū)的保護(hù)。這個(gè)方法使得緩沖區(qū)溢出不可能出現(xiàn),從而完全消除了緩沖區(qū)溢出的威脅,但是相對(duì)而言代價(jià)比較大。在3.4中我們介紹一種間接的方法,這個(gè)方法在程序指針失效前進(jìn)行完整性檢查。這樣雖然這種方法不能使得所有的緩沖區(qū)溢出失效,但它的的確確阻止了絕大多數(shù)的緩沖區(qū)溢出攻擊,而能夠逃脫這種方法保護(hù)的緩沖區(qū)溢出也很難實(shí)現(xiàn)。然后在3.5,我們要分析這種保護(hù)方法的兼容性和性能優(yōu)勢(shì)(與數(shù)組邊界檢查)。
3.1 編寫正確的代碼
編寫正確的代碼是一件非常有意義但耗時(shí)的工作,特別象編寫C語言那種具有容易出錯(cuò)傾向的程序(如:字符串的零結(jié)尾),這種風(fēng)格是由于追求性能而忽視正確性的傳統(tǒng)引起的。盡管花了很長(zhǎng)的時(shí)間使得人們知道了如何編寫安全的程序,具有安全漏洞的程序依舊出現(xiàn)。因此人們開發(fā)了一些工具和技術(shù)來幫助經(jīng)驗(yàn)不足的程序員編寫安全正確的程序。
最簡(jiǎn)單的方法就是用grep來搜索源代碼中容易產(chǎn)生漏洞的庫的調(diào)用,比如對(duì)strcpy和sprintf的調(diào)用,這兩個(gè)函數(shù)都沒有檢查輸入?yún)?shù)的長(zhǎng)度。事實(shí)上,各個(gè)版本C的標(biāo)準(zhǔn)庫均有這樣的問題存在。
為了尋找一些常見的諸如緩沖區(qū)溢出和操作系統(tǒng)競(jìng)爭(zhēng)條件等漏洞,代碼檢查小組檢查了很多的代碼。然而依然有漏網(wǎng)之魚存在。盡管采用了strncpy和 snprintf這些替代函數(shù)來防止緩沖區(qū)溢出的發(fā)生,但是由于編寫代碼的問題,仍舊會(huì)有這種情況發(fā)生。比如lprm程序就是最好的例子,雖然它通過了代碼的安全檢查,但仍然有緩沖區(qū)溢出的問題存在。
為了對(duì)付這些問題,人們開發(fā)了一些高級(jí)的查錯(cuò)工具,如fault injection等。這些工具的目的在于通過人為隨機(jī)地產(chǎn)生一些緩沖區(qū)溢出來尋找代碼的安全漏洞。還有一些靜態(tài)分析工具用于偵測(cè)緩沖區(qū)溢出的存在。
雖然這些工具幫助程序員開發(fā)更安全的程序,但是由于C語言的特點(diǎn),這些工具不可能找出所有的緩沖區(qū)溢出漏洞。所以,偵錯(cuò)技術(shù)只能用來減少緩沖區(qū)溢出的可能,并不能完全地消除它的存在。除非程序員能保證他的程序萬無一失,否則還是要用到以下3.2到3.4部分的內(nèi)容來保證程序的可靠性能。
3.2 非執(zhí)行的緩沖區(qū)
通過使被攻擊程序的數(shù)據(jù)段地址空間不可執(zhí)行,從而使得攻擊者不可能執(zhí)行被殖入被攻擊程序輸入緩沖區(qū)的代碼,這種技術(shù)被稱為非執(zhí)行的緩沖區(qū)技術(shù)。事實(shí)上,很多老的Unix系統(tǒng)都是這樣設(shè)計(jì)的,但是近來的Unix和MS Windows系統(tǒng)由于實(shí)現(xiàn)更好的性能和功能,往往在在數(shù)據(jù)段中動(dòng)態(tài)地放入可執(zhí)行的代碼。所以為了保持程序的兼容性不可能使得所有程序的數(shù)據(jù)段不可執(zhí)行。
但是我們可以設(shè)定堆棧數(shù)據(jù)段不可執(zhí)行,這樣就可以最大限度地保證了程序的兼容性。Linux和Solaris都發(fā)布了有關(guān)這方面的內(nèi)核補(bǔ)丁。因?yàn)閹缀鯖]有任何合法的程序會(huì)在堆棧中存放代碼,這種做法幾乎不產(chǎn)生任何兼容性問題,除了在Linux中的兩個(gè)特例,這時(shí)可執(zhí)行的代碼必須被放入堆棧中:
信號(hào)傳遞:
Linux通過向進(jìn)程堆棧釋放代碼然后引發(fā)中斷來執(zhí)行在堆棧中的代碼來實(shí)現(xiàn)向進(jìn)程發(fā)送Unix信號(hào)。非執(zhí)行緩沖區(qū)的補(bǔ)丁在發(fā)送信號(hào)的時(shí)候是允許緩沖區(qū)可執(zhí)行的。
GCC的在線重用:
研究發(fā)現(xiàn)gcc在堆棧區(qū)里放置了可執(zhí)行的代碼作為在線重用之用。然而,關(guān)閉這個(gè)功能并不產(chǎn)生任何問題,只有部分功能似乎不能使用。
非執(zhí)行堆棧的保護(hù)可以有效地對(duì)付把代碼殖入自動(dòng)變量的緩沖區(qū)溢出攻擊,而對(duì)于其他形式的攻擊則沒有效果(參見2.1)。通過引用一個(gè)駐留的程序的指針,就可以跳過這種保護(hù)措施。其他的攻擊可以采用把代碼殖入堆或者靜態(tài)數(shù)據(jù)段中來跳過保護(hù)。
3.3 數(shù)組邊界檢查
殖入代碼引起緩沖區(qū)溢出是一個(gè)方面,擾亂程序的執(zhí)行流程是另一個(gè)方面。不象非執(zhí)行緩沖區(qū)保護(hù),數(shù)組邊界檢查完全放置了緩沖區(qū)溢出的產(chǎn)生和攻擊。這樣,只要數(shù)組不能被溢出,溢出攻擊也就無從談起。為了實(shí)現(xiàn)數(shù)組邊界檢查,則所有的對(duì)數(shù)組的讀寫操作都應(yīng)當(dāng)被檢查以確保對(duì)數(shù)組的操作在正確的范圍內(nèi)。最直接的方法是檢查所有的數(shù)組操作,但是通??梢圆捎靡恍﹥?yōu)化的技術(shù)來減少檢查的次數(shù)。目前有以下的幾種檢查方法:
3.3.1 Compaq C 編譯器
Compaq公司為Alpha CPU開發(fā)的C編譯器(在Tru64的Unix平臺(tái)上是cc,在Alpha Linux平臺(tái)上是ccc)支持有限度的邊界檢查(使用-check_bounds參數(shù))。這些限制是:
只有顯示的數(shù)組引用才被檢查,比如“a[3]”會(huì)被檢查,而“*(a+3)”則不會(huì)。
由于所有的C數(shù)組在傳送的時(shí)候是指針傳遞的,所以傳遞給函數(shù)的的數(shù)組不會(huì)被檢查。
帶有危險(xiǎn)性的庫函數(shù)如strcpy不會(huì)在編譯的時(shí)候進(jìn)行邊界檢查,即便是指定了邊界檢查。
由于在C語言中利用指針進(jìn)行數(shù)組操作和傳遞是如此的頻繁,因此這種局限性是非常嚴(yán)重的。通常這種邊界檢查用來程序的查錯(cuò),而且不能保證不發(fā)生緩沖區(qū)溢出的漏洞。
3.3.2 Jones & Kelly: C的數(shù)組邊界檢查
Richard Jones和Paul Kelly開發(fā)了一個(gè)gcc的補(bǔ)丁,用來實(shí)現(xiàn)對(duì)C程序完全的數(shù)組邊界檢查。由于沒有改變指針的含義,所以被編譯的程序和其他的gcc模塊具有很好的兼容性。更進(jìn)一步的是,他們由此從沒有指針的表達(dá)式中導(dǎo)出了一個(gè)“基”指針,然后通過檢查這個(gè)基指針來偵測(cè)表達(dá)式的結(jié)果是否在容許的范圍之內(nèi)。
當(dāng)然,這樣付出的性能上的代價(jià)是巨大的:對(duì)于一個(gè)頻繁使用指針的程序如向量乘法,將由于指針的頻繁使用而使速度比本來慢30倍。
這個(gè)編譯器目前還很不成熟;一些復(fù)雜的程序(如elm)還不能在這個(gè)上面編譯,執(zhí)行通過。然而在它的一個(gè)更新版本之下,它至少能編譯執(zhí)行ssh軟件的加密軟件包。其實(shí)現(xiàn)的性能要下降12倍。
3.3.3 Purify:存儲(chǔ)器存取檢查
Purify是C程序調(diào)試時(shí)查看存儲(chǔ)器使用的工具而不是專用的安全工具。Purify使用“目標(biāo)代碼插入”技術(shù)來檢查所有的存儲(chǔ)器存取。通過用Purify連接工具連接,可執(zhí)行代碼在執(zhí)行的時(shí)候數(shù)組的所有引用來保證其合法性。這樣帶來的性能上的損失要下降3-5倍。
3.3.4 類型-安全語言
所有的緩沖區(qū)溢出漏洞都源于C語言缺乏類型安全。如果只有類型-安全的操作才可以被允許執(zhí)行,這樣就不可能出現(xiàn)對(duì)變量的強(qiáng)制操作。如果作為新手,可以推薦使用具有類型-安全的語言如Java和ML。
但是作為Java執(zhí)行平臺(tái)的Java虛擬機(jī)是C程序,因此通過攻擊JVM的一條途徑是使JVM的緩沖區(qū)溢出。因此在系統(tǒng)中采用緩沖區(qū)溢出防衛(wèi)技術(shù)來使用強(qiáng)制類型-安全的語言可以收到意想不到的效果。
3.4 程序指針完整性檢查
程序指針完整性檢查和邊界檢查由略微的不同。與防止程序指針被改變不同,程序指針完整性檢查在程序指針被引用之前檢測(cè)到它的改變。因此,即便一個(gè)攻擊者成功地改變了程序的指針,由于系統(tǒng)事先檢測(cè)到了指針的改變,因此這個(gè)指針將不會(huì)被使用。
與數(shù)組邊界檢查相比,這種方法不能解決所有的緩沖區(qū)溢出問題;采用其他的緩沖區(qū)溢出方法就可以避免這種檢測(cè)。但是這種方法在性能上有很大的優(yōu)勢(shì),而且在兼容性也很好。
程序完整性檢查大體上有三個(gè)研究方向。在3.4.1中會(huì)介紹Snarskii為FreeBSD開發(fā)了一套定制的能通過監(jiān)測(cè)cpu堆棧來確定緩沖區(qū)溢出的libc。在3.4.2中會(huì)介紹我們自己的堆棧保護(hù)方法所開發(fā)的一個(gè)編譯器,它能夠在函數(shù)調(diào)用的時(shí)候自動(dòng)生成完整性檢測(cè)代碼。最后在3.4.3,我們介紹正在開發(fā)中的指針保護(hù)方法,這種方法類似于堆棧保護(hù),它提供對(duì)所有程序指針的完整性的保護(hù)。 3.4.1 手寫的堆棧監(jiān)測(cè) Snarskii為FreeBSD開發(fā)了一套定制的能通過監(jiān)測(cè)cpu堆棧來確定緩沖區(qū)溢出的libc。這個(gè)應(yīng)用完全用手工匯編寫的,而且只保護(hù) libc中的當(dāng)前有效紀(jì)錄函數(shù)。這個(gè)應(yīng)用達(dá)到了設(shè)計(jì)要求,對(duì)于基于libc庫函數(shù)的攻擊具有很好的防衛(wèi),但是不能防衛(wèi)其它方式的攻擊。 3.4.2 堆棧保護(hù):編譯器生成的有效紀(jì)錄完整性檢測(cè) 堆棧保護(hù)是一種提供程序指針完整性檢查的編譯器技術(shù),通過檢查函數(shù)活動(dòng)紀(jì)錄中的返回地址來實(shí)現(xiàn)。堆棧保護(hù)作為gcc的一個(gè)小的補(bǔ)丁,在每個(gè)函數(shù)中,加入了函數(shù)建立和銷毀的代碼。加入的函數(shù)建立代碼實(shí)際上在堆棧中函數(shù)返回地址后面加了一些附加的字節(jié)。而在函數(shù)返回時(shí),首先檢查這個(gè)附加的字節(jié)是否被改動(dòng)過。如果發(fā)生過緩沖區(qū)溢出的攻擊,那么這種攻擊很容易在函數(shù)返回前被檢測(cè)到。 但是,如果攻擊者預(yù)見到這些附加字節(jié)的存在,并且能在溢出過程中同樣地制造他們,那么他就能成功地跳過堆棧保護(hù)的檢測(cè)。通常,我們有如下的兩種方案對(duì)付這種欺騙: 終止符號(hào): 利用在C語言中的終止符號(hào)如0(null),CR,LF,-1(EOF)等不能在常用的字符串函數(shù)中使用,因?yàn)檫@些函數(shù)一旦遇到這些終止符號(hào),就結(jié)束函數(shù)過程了。 隨機(jī)符號(hào): 利用一個(gè)在函數(shù)調(diào)用時(shí)產(chǎn)生的一個(gè)32位的隨機(jī)數(shù)來實(shí)現(xiàn)保密,使得攻擊者不可能猜測(cè)到附加字節(jié)的內(nèi)容。而且,每次調(diào)用,附加字節(jié)的內(nèi)容都在改變,也無法預(yù)測(cè)。 通過檢查堆棧的完整性的堆棧保護(hù)法是從Synthetix方法演變來的。Synthetix方法通過使用準(zhǔn)不變量來確保特定變量的正確性。這些特定的變量的改變是程序?qū)崿F(xiàn)能預(yù)知的,而且只能在滿足一定的條件才能可以改變。這種變量我們稱為準(zhǔn)變量。Synthetix開發(fā)了一些工具用來保護(hù)這些變量。 攻擊者通過緩沖區(qū)溢出而產(chǎn)生的改變可以被系統(tǒng)當(dāng)做非法的動(dòng)作。在某些極端的情況下,這些準(zhǔn)不變量有可能被非法改變,這是就需要堆棧保護(hù)來提供更完善的保護(hù)了。 實(shí)驗(yàn)的數(shù)據(jù)表明,堆棧保護(hù)對(duì)于各種系統(tǒng)的緩沖區(qū)溢出攻擊都有很好的保護(hù)作用,并能保持較好的兼容性和系統(tǒng)性能。隨后,我們用堆棧保護(hù)的方法重新構(gòu)造了一個(gè)完整的Linux系統(tǒng)(Red Hat 5.1)。然后我們用XFree86-3.3.2-5和lsof的漏洞對(duì)此進(jìn)行了攻擊,結(jié)果表明,這個(gè)系統(tǒng)有效地抵御了這些攻擊。這些分析表明,堆棧保護(hù)能有效抵御現(xiàn)在的和將來的基于堆棧的攻擊。 堆棧保護(hù)版本的Red Hat Linux 5.1已經(jīng)在各種系統(tǒng)上運(yùn)行了多年,包括個(gè)人的筆記本電腦和工作組文件服務(wù)器。從我們的Web服務(wù)器上可以得到這個(gè)版本,而且在我們的郵件列表里已經(jīng)有了 55個(gè)成員。出了僅有的一次例外,這個(gè)系統(tǒng)和本來的系統(tǒng)工作完全一樣,這表明堆棧保護(hù)并不對(duì)系統(tǒng)的兼容性構(gòu)成很大的影響。 我們已經(jīng)用各種性能測(cè)試來評(píng)測(cè)堆棧保護(hù)的性能。Mircobenchmarks的結(jié)果表明在函數(shù)的調(diào)用,堆棧保護(hù)中增加了系統(tǒng)的 開銷。而在網(wǎng)絡(luò)的測(cè)試中(需要用到堆棧保護(hù)的地方),則表明這種開銷不是很大。 我們的第一個(gè)測(cè)試對(duì)象是SSH,它提供了極強(qiáng)的加密和認(rèn)證,用來替代Berkeley的r系列指令。SSH使用了軟件加密,因此系統(tǒng)的占用的帶寬不大,我們用網(wǎng)絡(luò)間復(fù)制一個(gè)大的文件來測(cè)試帶寬: scp bigsource localhost:bigdest
測(cè)試結(jié)果表明:堆棧保護(hù)幾乎不影響SSH的網(wǎng)絡(luò)吞吐性能。 第二個(gè)測(cè)試使用了Apache Web服務(wù)器。如果這種服務(wù)器存在基于堆棧的攻擊,那么攻擊者就可以輕易地取得Web服務(wù)器的控制權(quán),允許攻擊者閱讀隱秘的內(nèi)容和肆意篡改主頁的內(nèi)容。同時(shí),Web服務(wù)器也是對(duì)性能和帶寬要求較高的一個(gè)服務(wù)器部件。 我們用WebStone對(duì)帶有和不帶堆棧保護(hù)的Apache Web服務(wù)器進(jìn)行了測(cè)試。 和SSH一樣,他們的性能幾乎沒有區(qū)別。在客戶數(shù)目較少的情況下,帶有保護(hù)的服務(wù)器性能比不帶保護(hù)的略微好些,在客戶端數(shù)目多的時(shí)候,不帶保護(hù)的性能好些。在最壞的情況下,帶保護(hù)的服務(wù)器比不帶保護(hù)的要差8%的連接性能,而在平均延時(shí)上保持優(yōu)勢(shì)。象以前一樣,我們把這些歸結(jié)為噪聲的影響。因此,我們的結(jié)論是:堆棧保護(hù)對(duì)Web服務(wù)器系統(tǒng)性能沒有重大的影響。 3.4.3 指針保護(hù):編譯器生成程序指針完整性檢查 在堆棧保護(hù)設(shè)計(jì)的時(shí)候,沖擊堆棧構(gòu)成了緩沖區(qū)溢出攻擊的常見的一種形式。有人推測(cè)存在一種模板來構(gòu)成這些攻擊(在1996年的時(shí)候)。從此,很多簡(jiǎn)單的漏洞被發(fā)現(xiàn),實(shí)施和補(bǔ)丁了,很多攻擊者開始用在第二部分中描述的更一般的方法實(shí)施緩沖區(qū)溢出攻擊。 指針保護(hù)是堆棧保護(hù)針對(duì)這種情況的一個(gè)推廣。通過在所有的代碼指針之后放置附加字節(jié)來檢驗(yàn)指針在被調(diào)用之前的合法性。如果檢驗(yàn)失敗,會(huì)發(fā)出報(bào)警信號(hào)和退出程序的執(zhí)行,就如同在堆棧保護(hù)中的行為一樣。這種方案有兩點(diǎn)需要注意: 附加字節(jié)的定位: 附加字節(jié)的空間是在被保護(hù)的變量被分配的時(shí)候分配的,同時(shí)在被保護(hù)字節(jié)初始化過程中被初始化。這樣就帶來了問題;為了保持兼容性,我們不想改變被保護(hù)變量的大小,因此我們不能簡(jiǎn)單地在變量的結(jié)構(gòu)定義中加入附加字。還有,對(duì)各種類型也有不同附加字節(jié)數(shù)目。 檢查附加字節(jié): 每次程序指針被引用的時(shí)候都要檢查附加字節(jié)的完整性。這個(gè)也存在問題;因?yàn)?#8220;從存取器讀”在編譯器中沒有語義;編譯器更關(guān)心指針的使用,而各種的優(yōu)化算法傾向于從存儲(chǔ)器中讀入變量。 還有隨著不同類型的變量,讀入的方法也各自不同。 我們已經(jīng)開發(fā)了指針保護(hù)的一個(gè)原型(還是基于gcc的),通過附加字節(jié)來保護(hù)靜態(tài)分配的函數(shù)指針,但不適用于結(jié)構(gòu)和數(shù)組類型。這個(gè)計(jì)劃還遠(yuǎn)沒有完成。一旦這個(gè)項(xiàng)目完成了,那么用它和堆棧保護(hù)構(gòu)成的可執(zhí)行代碼將不會(huì)受到緩沖區(qū)溢出的攻擊了。 目前為止,只有很少一部分使用非指針變量的攻擊能逃脫指針保護(hù)的檢測(cè)。但是,可以通過在編譯器上強(qiáng)制對(duì)某一變量加入附加字節(jié)來實(shí)現(xiàn)檢測(cè),這時(shí)需要程序員自己手工加入相應(yīng)的保護(hù)了。 3.5 兼容性和性能的考慮 程序指針完整性檢查與邊界檢查相比,并不能防止所有的緩沖區(qū)溢出問題。然而在執(zhí)行的性能和兼容性上具有相當(dāng)?shù)膬?yōu)勢(shì): 性能: 邊界檢查必須在每個(gè)數(shù)組元素操作時(shí)完成一次檢查。相比之下,程序指針檢查只在被引用的時(shí)候?qū)崿F(xiàn)檢查。無論在C還是在C++中,這種花在程序指針引用上的開銷始終比數(shù)組的指針引用小。 應(yīng)用效能: 邊界檢查最難實(shí)現(xiàn)之處在于在C語言中,很能確定數(shù)組的邊界。這是由于在C中,數(shù)組的概念和通用指針的混用造成的。由于一個(gè)指針是一個(gè)獨(dú)立的對(duì)象,沒有與特定的邊界條件關(guān)聯(lián),只有一個(gè)系統(tǒng)的機(jī)器字來存儲(chǔ)它,而標(biāo)識(shí)邊界信息的數(shù)據(jù)卻沒有存放。因此需要特殊的方法來恢復(fù)這些信息;數(shù)組的引用將不在是一個(gè)簡(jiǎn)單的指針,而是一個(gè)對(duì)緩沖區(qū)描述的指針組。 與現(xiàn)有代碼的兼容性: 一些邊界檢查方法為了與現(xiàn)有的代碼保持兼容而在系統(tǒng)的性能上得到了損失。而另一些則用別的方法達(dá)到目的。這樣就打破的傳統(tǒng)的C的轉(zhuǎn)換規(guī)則,轉(zhuǎn)而產(chǎn)生了一類新的C編譯器,只能編譯C的一個(gè)子集,有的還不能使用指針或者需要?jiǎng)e的改變。 四. 有效的組合 在這里我們研究、比較在第二部分描述的各種漏洞攻擊和在第三部分描述的防衛(wèi)方法,以此來確定何種組合能完全消除緩沖區(qū)溢出問題。但是我們沒有把邊界檢查計(jì)算在內(nèi),因?yàn)樗苡行У胤乐顾械木彌_區(qū)溢出,但是所需的開銷也是驚人的。 最普通的緩沖區(qū)溢出形式是攻擊活動(dòng)紀(jì)錄然后在堆棧中殖入代碼。這種類型的攻擊在1996年中有很多紀(jì)錄。而非執(zhí)行堆棧和堆棧保護(hù)的方法都可以有效防衛(wèi)這種攻擊。非執(zhí)行堆棧可以防衛(wèi)所有把代碼殖入堆棧的攻擊方法,堆棧保護(hù)可以防衛(wèi)所有改變活動(dòng)紀(jì)錄的方法。這兩種方法相互兼容,可以同時(shí)防衛(wèi)多種可能的攻擊。 剩下的攻擊基本上可以用指針保護(hù)的方法來防衛(wèi),但是在某些特殊的場(chǎng)合需要用手工來實(shí)現(xiàn)指針保護(hù)。全自動(dòng)的指針保護(hù)需要對(duì)每個(gè)變量加入附加字節(jié),這樣使得指針邊界檢查在某些情況下具有優(yōu)勢(shì)。
最為有趣的是,第一個(gè)緩沖區(qū)溢出漏洞--Morris蠕蟲使用了現(xiàn)今所有方法都無法有效防衛(wèi)的方法,但是卻很少有人用到,也許是這種方法過于復(fù)雜的緣故吧。
五. 結(jié)論
在本文中,我們?cè)敿?xì)描述和分析了緩沖區(qū)溢出的攻擊和防衛(wèi)方法。由于這種攻擊是目前常見的攻擊手段,所以進(jìn)行這個(gè)方面的研究工作是有意義和成效的。研究的結(jié)果表明,堆棧保護(hù)方法和非執(zhí)行緩沖區(qū)方法對(duì)于當(dāng)前絕大多數(shù)的攻擊都能有效地防御,指針保護(hù)的方法可以對(duì)剩下的攻擊進(jìn)行有效的防御。最后聲明的是對(duì)于 Morris蠕蟲的攻擊,迄今還沒有有效的防御手段
|