談及防御之前,首先簡要回顧一下緩沖區溢出的攻擊大系:
棧溢出(stack smashing)
未檢查輸入緩沖區長度,導致數組越界,覆蓋棧中局部變量空間之上的棧桢指針%ebp以及函數返回地址retaddr, 當函數返回執行ret指令時,retaddr從棧中彈出,作為下一條指令的地址賦給%eip寄存器,繼而改變原程序的執行流程指向我們的shellcode.
堆溢出(malloc/free heap corruption)
一種是和傳統的棧溢出一樣,當輸入超出malloc()預先分配的空間大小,就會覆蓋掉這段空間之後的一段存儲區域,如果該存儲區域有一個重要的變量比如euid,那麼我就可以用它來攻擊。另一種是典型的double-free堆腐敗,在內存回收操作中,合並相鄰空閒塊重新插入雙向鏈表時會有一個寫4字節內存的操作,如果弱點程序由於編程錯誤free()一個不存在的塊,我們就可以精心偽造這個塊,從而覆蓋任何我們想要的值:函數的返回地址、庫函數的.plt地址等
格式化字符竄漏洞(format string vulnerability)
如果格式竄由用戶定制,攻擊者就可以任意偽造格式竄,利用*printf()系列函數的特性就可以窺探堆棧空間的內容,超常輸入可以引發傳統的緩沖區溢出,或是用”%n”覆蓋指針、返回地址等。
整形變量溢出(integer variable overflow)
利用整數的范圍、符號等問題觸發安全漏洞,大多數整形溢出不能直接利用,但如果該整形變量決定內存分配等操作,我們就有可能間接利用該漏洞。
其他的攻擊手法(others)
只能算是手法,不能算是一種單獨的類別。利用ELF文件格式的特性如:覆蓋.plt(過程連接表)、.dtor(析構函數指針)、.got(全局偏移表)、return-to-libc(返回庫函數)等的方式進行攻擊。
一、編譯保護技術
Stackguard
因為緩沖區溢出的通常都會改寫函數返回地址,stackguard是個編譯器補丁,它產生一個"canary"值(一個單字)放到返回地址的前面,如果當函數返回時,發現這個canary的值被改變了,就證明可能有人正在試圖進行緩沖區溢出攻擊,程序會立刻響應,發送一條入侵警告消息給syslogd,然後終止進程。"canary"包含:NULL(0x00), CR(0x0d), LF (0x0a) 和 EOF (0xff)四個字 符,它們應該可以阻止大部分的字符串操作,使溢出攻擊無效。一個隨機數canary在程序執行的時候被產生。所以攻擊者不能通過搜索程序的二進制文件得到"canary"值。如果/dev/urandom存在,隨機數就從那裡取得。否則,就從通過對當前時間進行編碼得到。其隨機性足以阻止絕大部分的預測攻擊。Immunix系統為采用stackguard編譯的Red Hat Linux,但stackguard所提供的保護並非絕對安全,滿足一些條件就可以突破限制:如覆蓋一個函數指針、可能存在的exit()或_exit()系統調用地址、GOT等。
Stackshield
StackShield使用了另外一種不同的技術。它的做法是創建一個特別的堆棧用來儲存函數返回地址的一份拷貝。它在受保護的函數的開頭和結尾分別增加一段代碼,開頭處的代碼用來將函數返回地址拷貝到一個特殊的表中,而結尾處的代碼用來將返回地址從表中拷貝回堆棧。因此函數執行流程不會改變,將總是正確返回到主調函數中。在新的版本中已經增加了一些新的保護措施,當調用一個地址在非文本段內的函數指針時,將終止函數的執行。
Stackshield無法防御只覆蓋%ebp的單字節溢出,同樣,我們也可以通過覆蓋其他的ELF結構來繞過限制。
二、庫函數鏈接保護
Formatguard
Formatguard是個Glibc的補丁,遵循GPL,它使用特殊的CPP(gcc預編譯程序)宏取代原有的*printf()的參數統計方式,它會比較傳遞給*printf的參數的個數和格式竄的個數,如果格式竄的個數大於實際參數的個數,就判定為攻擊行為,向syslogd發送消息並終止進程。如果弱點程序調用Glibc以外的庫,formatguard就無法保護。
Libsafe
Libsafe是一個動態鏈接庫,在標准的C庫之前被加載,主要加固了gets(),strcpy(),strcat(),sprintf()……等容易發生安全問題的C函數,它設計為只針對stack smashing && format string類型的攻擊。Alert7很早也寫過如何繞過libsafe保護的文章。
三、棧不可執行
Solar designer’s nonexec kernel patch
從名字可以看出這是一個Linux上的內核補丁,該補丁最主要的特性是:用戶區堆棧不可執行[Non-executable User Stack]由於x86 CPU上並沒有提供頁(page)執行的bit位,所以該補丁通過減小代碼段的虛擬地址來區分數據段和代碼段,程序執行流返回 0xC0000000以下一段用戶堆棧空間的操作都被認為是緩沖區溢出攻擊行為,隨即產生一個通用保護異常而終止進程。這樣把shellcode安置在buffer或環境變量(都位於堆棧段)的exploit都會失效。當然其安全也不是絕對的,利用PLT返回庫函數的文章裡詳細描述了突破該補丁的攻擊方法。該補丁還有一些其他的特性:動態鏈接庫映射到地址低端(0x00開始)、限制符號鏈接攻擊、/tmp目錄限制、/proc目錄限制、execve系統調用加固等。
Solaris/SPARC nonexec-stack protection
在Solaris/SPARC下可以通過去掉堆棧的執行權限來禁止堆棧段執行,方法如下,在/etc/system中加入兩條語句:
Set noexec_user_stack = 1
Set noexec_user_stack_log = 1
第一條禁止堆棧執行,第二條記錄所有嘗試在堆棧段運行代碼的活動。Reboot之後才會生效。所有只讓棧不可執行的保護是有限的。Return-to-libc、fake frame之類的技術都可以突破限制,不過棧不可執行的保護已經極大了提升了攻擊難度。
四、數據段不可執行
kNoX
Linux內核補丁,功能:數據段的頁不可執行,撤銷共享內存,加強對execve系統調用的限制,對文件描述符0、1、2的特殊處理,/proc目錄的限制,FIFO限制,符號鏈接限制,該補丁只支2.2內核。
RSX
Linux內核模塊,數據段(stack、heap)不可執行。
Exec shield
Exec-shield從內核態顯示的跟蹤一個應用程序所包含的可執行映像的最大虛擬地址,動態的維護這個“可執行虛擬地址的最大值”稱為“可執行限界”,每次發生進程切換的時候調度進程就會用這個值更新代碼段描述符寫入GDT,exec-shield動態的跟蹤每個應用程序,所以每個程序運行時都有不同的“可執行限界”,因為可執行限界通常是個很低的虛擬地址,所以除了stack以外mmap()映射的區域以及malloc()分配的空間都處在可執行限界之上,因此都是不可執行的。當然Exec-shield無法防御跳轉到低16M地址空間和return-to-libc的攻擊,不過還是能阻止絕大多數把shellcode安置在數據段的攻擊
五、增強的緩沖區溢出保護及內核MAC
OpenBSD security feature
OpenBSD和Hardened Gentoo、Adamantix、SELinux都是屬於默認安全等級非常高的操作系統。OpenBSD經過代碼審計,漏洞非常少。同樣他具有很多安全特性:
*使用strlcpy()和strlcat()函數替換原有的危險函數
*內存保護:W^X、只讀數據段、頁保護、mmap()隨機映射、malloc()隨機映射、atexit()及stdio保護、
*特權分離
*特權回收
*BSD chroot jail
*其他的很多特性
其中W^X有不少內容:stack、mmap隨機映射,只讀GOT/PLT/.ctor/.dtor等。雖然理論上OpenBSD無法阻止所有類型的攻擊,但已經阻斷了不少攻擊手法。
PaX
PaX是個非常BT的東西,好像天生就是緩沖區溢出的死對頭,他嚴厲的審視每一種攻擊方式,予以阻斷。
*基於x86段式內存管理的數據段不可執行
*基於頁式內存管理的數據段的頁不可執行
*內核頁只讀{
-Const結構只讀
-系統調用表只讀
-局部段描述符表(IDT)只讀
-全局段描述符表(GDT)只讀
-數據頁只讀
-該特性不能與正常的LKM功能共存 }
*完全的地址空間隨機映射{
-每個系統調用的內核棧隨機映射
-用戶棧隨機映射
-ELF可執行映像隨機映射
-Brk()分配的heap隨機映射
-Mmap()管理的heap隨機映射
-動態鏈接庫隨機映射 }
*還有諸如把動態鏈接庫映射到0x00開始的低地址的其他特性
這裡順便提一下Phrack58上Nergal寫過的<
>,這篇大作裡提到用偽造棧桢(Fakeframe)和dl-resolve()技術突破PaX若干保護的方法,這極有可能*nix應用層exploit技術中最高級的技術,Nergal解決了幾個問題:Stack/Heap/BSS不可執行、mmap隨機映射,顯然這種高級的技術仍然無法無條件的突破PaX,所以在一個運行完全版PaX的Linux上,你想發動緩沖區溢出可能是沒有機會的!!!
Grsecurity
六、硬件級別的保護
X86 CPU上采用4GB平坦模式,數據段和代碼段的線性地址是重疊的,頁面只要可讀就可以執行,所以上面提到的諸多內核補丁才會費盡心機設計了各種方法來使數據段不可執行。現在Alpha、PPC、PA-RISC、SPARC、SPARC64、AMD64、IA64都提供了頁執行bit位。Intel及AMD新增加的頁執行比特位稱為NX安全技術,Windows XP SP2及Linux Kernel 2.6都支持NX,雖然這種硬件級的頁保護不如PaX那樣強,但硬件級別的支持無疑大大增加了軟件和操作系統的兼容性,能夠使緩沖區溢出的防護得到普及。
Conclusion
安全和易用性總是站在對立面上,以上提及的保護技術都會引起少量的性能損耗,設計者們已經從性能的角度優化了他們的作品。然而人們更關心的問題是兼容性,也許你會發現在那些運營級的BOX上根本看不到這些東西,是的,人們希望的另一種安全是不發生錯誤,即穩定的運行,使用這些額外的保護會給人造成心理不安,我相信隨著NX的流行以及保護技術本身的發展這些問題都會得到解決。也許你經常會看到這樣或那樣的文章講述如何突破緩沖區溢出保護的高級exploit技術,其實很多內容只適合作為教學、或者技術本身還處在研究階段,在實際的攻擊中,使用高級的bypass技術通常需要滿足一些條件,並不是單純多花點力氣增長了exploit代碼的長度就能達到目的,在使用緩沖區溢出保護的系統上,攻擊將變得非常困難,有些時候其實就是不可能,尤其是在遠程無法精確得到必須的ELF符號地址的時候,很多技術都將變成紙上談兵。使用類似PaX的補丁,+iptables規則,再結合內核MAC,想入侵得到shell幾乎是不可能的,可惜偶沒錢,不然拿個Linux box放到Internet上公測,讓牛人們盡興的玩玩,哈~在緩沖區溢出尚未成為歷史的今天,暫且緬懷一下吧,這當然也不是什麼悲觀的論調,舊技術的消亡必然伴隨著新技術的誕生,如果沒有了Evil Hacking我們還坐在電腦前干什麼呢?