ssp編譯
① 大佬 在Xcode中如何開啟SSP棧溢出保護編譯
在虛擬機下,安裝Mac OS X。下載iOS SDK(包含Xcode),在虛擬機環境下安裝,安裝時候就可以,直接打開Xcode進行編程了。
② 幫忙說一下集成電路中的各種標語
電源電路、音頻電路(含MIC輸入部分和DAC音頻輸出部分)、復位電路等
③ 製作偽春菜需要什麼軟體
針對你的問題。我給出的回答是偽春菜不需要特別的軟體。
如果說需要的話,就是對於shell使用的圖像處理軟體:推薦使用的比較上手的,如photoshop。
以及一些輔助性質的軟體:比如紙娃娃機,coordin之類的。tama是偽春菜系統自帶的除錯軟體。
因為偽春菜本身是完全的明文代碼加上調用的圖片,所以完全不需要編譯就可以直接使用。所以調教起來也相對方便。
所以僅僅只是編寫偽春菜的話,只需要一個記事本就可以了。
需要的圖片就用photoshop之類的解決。
④ 如何解決:編譯時提示`__sync_add_and_fetch' undeclared
gcc version 3.4.4 (Gentoo 3.4.4, ssp-3.4.4-1.0, pie-8.7.8)
cc -o Xtest prog-2.cc -lX11
/tmp/ccD1lG9s.o:(.eh_frame+0x11): undefined reference to `__gxx_personality_v0'
collect2: ld returned 1 exit status
解決方法。。
Ok, solved. The problem was that the file extention was cpp, not c. I
could either rename the file or add -lstdc++ as a gcc flag.
http://mail.gnome.org/archives/gtk-app-devel-list/2004-October/msg00207.html
或則直接用c++編譯器,比如g++就沒有這個問題。。
Folks,
Just in case it isn't in other responses from earlier, a solution to this
(presumably) is to use the C++ compiler as the linker instead of the C
compiler. This can be accomplished by editing the makefile and changing
"LD=gcc" to "LD=c++" or more generally to "LD=$(CXX)". Or just do the
same on the command line "make LD=c++".
Presumably the 3.7 configure scripts will proce a Makefile with this
done if they don't already.
Actually, I don't get this error myself on Red Hat 8, with gcc 3.2. It would
appear to be because the libgdal.1.1.so is already linked against
/usr/lib/libstdc++.so.5 which provides the C++ support.
If anyone running into this problem finds that my suggested change to LD
does not solve their problem, I would encourage them to contact me directly
by email, and we will resolve the problem and I will try to see the fix gets
into 3.7 (and perhaps a 3.6.5) if it has not already.
PS. If you don't need OGR just configure without it and you won't run into
this problem.
Best regards,
http://lists.dmsolutions.ca/pipermail/mapserver-users/2003-March/007921.html
本文來自ChinaUnix博客,如果查看原文請點:http://blog.chinaunix.net/u/712/showart_50569.html
⑤ GCC編譯器局部變數地址分配為什麼總是從低
原因:GCC的堆棧保護技術—— canary的使用。
使用的原因是為了防止某些溢出的攻擊。但是只是溢出時方向發生了改變,並沒有起到太大的作用,可能對於傳統的一些攻擊方法有用。
GCC 中的堆棧保護實現
Stack Guard 是第一個使用 Canaries 探測的堆棧保護實現,它於 1997 年作為 GCC 的一個擴展發布。最初版本的 Stack Guard 使用 0x00000000 作為 canary word。盡管很多人建議把 Stack Guard 納入 GCC,作為 GCC 的一部分來提供堆棧保護。但實際上,GCC 3.x 沒有實現任何的堆棧保護。直到 GCC 4.1 堆棧保護才被加入,並且 GCC4.1 所採用的堆棧保護實現並非 Stack Guard,而是 Stack-smashing Protection(SSP,又稱 ProPolice)。
SSP 在 Stack Guard 的基礎上進行了改進和提高。它是由 IBM 的工程師 Hiroaki Rtoh 開發並維護的。與 Stack Guard 相比,SSP 保護函數返回地址的同時還保護了棧中的 EBP 等信息。此外,SSP 還有意將局部變數中的數組放在函數棧的高地址,而將其他變數放在低地址。這樣就使得通過溢出一個數組來修改其他變數(比如一個函數指針)變得更為困難。
⑥ (暢想)如何改進編程模式及cpu體系結構防止緩沖區溢出,不要求標准答案,只要想像得有道理
緩沖區溢出。本文首先解釋什麼是緩沖區溢出,以及它們為何如此常見和如此危險。然後討論廣泛用於解決緩沖區溢出的新 linux 和 UNIX 方法 ―― 以及為什麼這些方法還不足夠。隨後將展示 C/C++ 程序中防止緩沖區溢出的各種方法,同時包括靜態調整大小的方法(比如標準的 C 庫和 OpenBSD/strlcpy 解決方案)和動態調整大小的解決方案,以及一些將為您提供幫助的工具。最後,本文以一些關於緩沖區溢出缺陷的未來發展形勢的預測來結束全文的討論。
如果希望自己的程序是安全的,您需要知道什麼是緩沖區溢出,如何防止它們,可以採用哪些最新的自動化工具來防止它們(以及為什麼這些工具還不足夠),還有如何在您自己的程序中防止它們。
什麼是緩沖區溢出?
緩沖區以前可能被定義為「包含相同數據類型的實例的一個連續計算機內存塊」。在 C 和 C++ 中,緩沖區通常是使用數組和諸如 malloc() 和 new 這樣的內存分配常式來實現的。極其常見的緩沖區種類是簡單的字元數組。 溢出 是指數據被添加到分配給該緩沖區的內存塊之外。
如果攻擊者能夠導致緩沖區溢出,那麼它就能控製程序中的其他值。雖然存在許多利用緩沖區溢出的方法,不過最常見的方法還是「stack-smashing」攻擊。Elias Levy (又名為 Aleph One)的一篇經典文章「Smashing the Stack for Fun and Profit」解釋了 stack-smashing 攻擊,Elias Levy 是 Bugtraq 郵件列表(請參閱 參考資料 以獲得相關鏈接)的前任主持人。
清單 1. 一個簡單的程序
void function1(int a, int b, int c) {
char buffer1[5];
gets(buffer1); /* DON'T DO THIS */
}
void main() {
function(1,2,3);
}
假設使用 gcc 來編譯清單 1 中的簡單程序,在 X86 上的 Linux 中運行,並且緊跟在對 gets() 的調用之後中止。此時的內存內容看起來像什麼樣子呢?答案是它看起來類似圖 1,其中展示了從左邊的低位地址到右邊的高位地址排序的內存布局。
圖 1. 堆棧視圖
內存的底部 內存的頂部
buffer1 sfp ret a b c
<--- 增長 --- [ ] [ ] [ ] [ ] [ ] [ ] ...
為什麼緩沖區溢出如此常見?
在幾乎所有計算機語言中,不管是新的語言還是舊的語言,使緩沖區溢出的任何嘗試通常都會被該語言本身自動檢測並阻止(比如通過引發一個異常或根據需要給緩沖區添加更多空間)。但是有兩種語言不是這樣:C 和 C++ 語言。C 和 C++ 語言通常只是讓額外的數據亂寫到其餘內存的任何位置,而這種情況可能被利用從而導致恐怖的結果。更糟糕的是,用 C 和 C++ 編寫正確的代碼來始終如一地處理緩沖區溢出則更為困難;很容易就會意外地導致緩沖區溢出。除了 C 和 C++ 使用得 非常廣泛外,上述這些可能都是不相關的事實;例如,Red Hat Linux 7.1 中 86% 的代碼行都是用 C 或 C ++ 編寫的。因此,大量的代碼對這個問題都是脆弱的,因為實現語言無法保護代碼避免這個問題。
在 C 和 C++ 語言本身中,這個問題是不容易解決的。該問題基於 C 語言的根本設計決定(特別是 C 語言中指針和數組的處理方式)。由於 C++ 是最兼容的 C 語言超集,它也具有相同的問題。存在一些能防止這個問題的 C/C++ 兼容版本,但是它們存在極其嚴重的性能問題。而且一旦改變 C 語言來防止這個問題,它就不再是 C 語言了。許多語言(比如 Java 和 C#)在語法上類似 C,但它們實際上是不同的語言,將現有 C 或 C++ 程序改為使用那些語言是一項艱巨的任務。
然而,其他語言的用戶也不應該沾沾自喜。有些語言存在允許緩沖區溢出發生的「轉義」子句。Ada 一般會檢測和防止緩沖區溢出(即針對這樣的嘗試引發一個異常),但是不同的程序可能會禁用這個特性。C# 一般會檢測和防止緩沖區溢出,但是它允許程序員將某些常式定義為「不安全的」,而這樣的代碼 可能 會導致緩沖區溢出。因此如果您使用那些轉義機制,就需要使用 C/C++ 程序所必須使用的相同種類的保護機制。許多語言都是用 C 語言來實現的(至少部分是用 C 語言來實現的 ),並且用任何語言編寫的所有程序本質上都依賴用 C 或 C++ 編寫的庫。因此,所有程序都會繼承那些問題,所以了解這些問題是很重要的。
導致緩沖區溢出的常見 C 和 C++ 錯誤
從根本上講,在程序將數據讀入或復制到緩沖區中的任何時候,它需要在復制 之前檢查是否有足夠的空間。能夠容易看出來的異常就不可能會發生 ―― 但是程序通常會隨時間而變更,從而使得不可能成為可能。
遺憾的是,C 和 C++ 附帶的大量危險函數(或普遍使用的庫)甚至連這點(指檢查空間)也無法做到。程序對這些函數的任何使用都是一個警告信號,因為除非慎重地使用它們,否則它們就會成為程序缺陷。您不需要記住這些函數的列表;我的真正目的是說明這個問題是多麼普遍。這些函數包括 strcpy(3)、strcat(3)、sprintf(3) (及其同類 vsprintf(3) )和 gets(3) 。 scanf() 函數集( scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3) 和 vfscanf(3) )可能會導致問題,因為使用一個沒有定義最大長度的格式是很容易的(當讀取不受信任的輸入時,使用格式「%s」總是一個錯誤)。
其他危險的函數包括 realpath(3)、getopt(3)、getpass(3)、streadd(3)、strecpy(3) 和 strtrns(3) 。 從理論上講, snprintf() 應該是相對安全的 ―― 在現代 GNU/Linux 系統中的確是這樣。但是非常老的 UNIX 和 Linux 系統沒有實現 snprintf() 所應該實現的保護機制。
Microsoft 的庫中還有在相應平台上導致同類問題的其他函數(這些函數包括 wcscpy()、_tcscpy()、_mbscpy()、wcscat()、_tcscat()、_mbscat() 和 CopyMemory() )。注意,如果使用 Microsoft 的 MultiByteToWideChar() 函數,還存在一個常見的危險錯誤 ―― 該函數需要一個最大尺寸作為字元數目,但是程序員經常將該尺寸以位元組計(更普遍的需要),結果導致緩沖區溢出缺陷。
另一個問題是 C 和 C++ 對整數具有非常弱的類型檢查,一般不會檢測操作這些整數的問題。由於它們要求程序員手工做所有的問題檢測工作,因此以某種可被利用的方式不正確地操作那些整數是很容易的。特別是,當您需要跟蹤緩沖區長度或讀取某個內容的長度時,通常就是這種情況。但是如果使用一個有符號的值來存儲這個長度值會發生什麼情況呢 ―― 攻擊者會使它「成為負值」,然後把該數據解釋為一個實際上很大的正值嗎?當數字值在不同的尺寸之間轉換時,攻擊者會利用這個操作嗎?數值溢出可被利用嗎? 有時處理整數的方式會導致程序缺陷。
防止緩沖區溢出的新技術
當然,要讓程序員 不犯常見錯誤是很難的,而讓程序(以及程序員)改為使用另一種語言通常更為困難。那麼為何不讓底層系統自動保護程序避免這些問題呢?最起碼,避免 stack-smashing 攻擊是一件好事,因為 stack-smashing 攻擊是特別容易做到的。
一般來說,更改底層系統以避免常見的安全問題是一個極好的想法,我們在本文後面也會遇到這個主題。事實證明存在許多可用的防禦措施,而一些最受歡迎的措施可分組為以下類別:
基於探測方法(canary)的防禦。這包括 StackGuard(由 Immunix 所使用)、ProPolice(由 OpenBSD 所使用)和 Microsoft 的 /GS 選項。
非執行的堆棧防禦。這包括 Solar Designer 的 non-exec 補丁(由 OpenWall 所使用)和 exec shield(由 Red Hat/Fedora 所使用)。
其他方法。這包括 libsafe(由 Mandrake 所使用)和堆棧分割方法。
遺憾的是,迄今所見的所有方法都具有弱點,因此它們不是萬能葯,但是它們會提供一些幫助。
基於探測方法的防禦
研究人員 Crispen Cowan 創建了一個稱為 StackGuard 的有趣方法。Stackguard 修改 C 編譯器(gcc),以便將一個「探測」值插入到返回地址的前面。「探測儀」就像煤礦中的探測儀:它在某個地方出故障時發出警告。在任何函數返回之前,它執行檢查以確保探測值沒有改變。如果攻擊者改寫返回地址(作為 stack-smashing 攻擊的一部分),探測儀的值或許就會改變,系統內就會相應地中止。這是一種有用的方法,不過要注意這種方法無法防止緩沖區溢出改寫其他值(攻擊者仍然能夠利用這些值來攻擊系統)。人們也曾擴展這種方法來保護其他值(比如堆上的值)。Stackguard(以及其他防禦措施)由 Immunix 所使用。
IBM 的 stack-smashing 保護程序(ssp,起初名為 ProPolice)是 StackGuard 的方法的一種變化形式。像 StackGuard 一樣,ssp 使用一個修改過的編譯器在函數調用中插入一個探測儀以檢測堆棧溢出。然而,它給這種基本的思路添加了一些有趣的變化。 它對存儲局部變數的位置進行重新排序,並復制函數參數中的指針,以便它們也在任何數組之前。這樣增強了ssp 的保護能力;它意味著緩沖區溢出不會修改指針值(否則能夠控制指針的攻擊者就能使用指針來控製程序保存數據的位置)。默認情況下,它不會檢測所有函數,而只是檢測確實需要保護的函數(主要是使用字元數組的函數)。從理論上講,這樣會稍微削弱保護能力,但是這種默認行為改進了性能,同時仍然能夠防止大多數問題。考慮到實用的因素,它們以獨立於體系結構的方式使用 gcc 來實現它們的方法,從而使其更易於運用。從 2003 年 5 月的發布版本開始,廣受贊譽的 OpenBSD(它重點關注安全性)在他們的整個發行套件中使用了 ssp(也稱為 ProPolice)。
Microsoft 基於 StackGuard 的成果,添加了一個編譯器標記(/GS)來實現其 C 編譯器中的探測儀。
非執行的堆棧防禦
另一種方法首先使得在堆棧上執行代碼變得不可能。 遺憾的是,x86 處理器(最常見的處理器)的內存保護機制無法容易地支持這點;通常,如果一個內存頁是可讀的,它就是可執行的。一個名叫 Solar Designer 的開發人員想出了一種內核和處理器機制的聰明組合,為 Linux 內核創建了一個「非執行的堆棧補丁」;有了這個補丁,堆棧上的程序就不再能夠像通常的那樣在 x86 上運行。 事實證明在有些情況下,可執行程序 需要在堆棧上;這包括信號處理和跳板代碼(trampoline)處理。trampoline 是有時由編譯器(比如 GNAT Ada 編譯器)生成的奇妙結構,用以支持像嵌套子常式之類的結構。Solar Designer 還解決了如何在防止攻擊的同時使這些特殊情況不受影響的問題。
一段時間之後,人們又想出了一種防止該問題的新思路:將所有可執行代碼轉移到一個稱為「ASCII 保護(ASCII armor)」區域的內存區。要理解這是如何工作的,就必須知道攻擊者通常不能使用一般的緩沖區溢出攻擊來插入 ASCII NUL 字元(0)這個事實。 這意味著攻擊者會發現,要使一個程序返回包含 0 的地址是很困難的。由於這個事實,將所有可執行代碼轉移到包含 0 的地址就會使得攻擊該程序困難多了。
具有這個屬性的最大連續內存范圍是從 0 到 0x01010100 的一組內存地址,因此它們就被命名為 ASCII 保護區域(還有具有此屬性的其他地址,但它們是分散的)。與非可執行的堆棧相結合,這種方法就相當有價值了:非可執行的堆棧阻止攻擊者發送可執行代碼,而 ASCII 保護內存使得攻擊者難於通過利用現有代碼來繞過非可執行堆棧。這樣將保護程序代碼避免堆棧、緩沖區和函數指針溢出,而且全都不需重新編譯。
然而,ASCII 保護內存並不適用於所有程序;大程序也許無法裝入 ASCII 保護內存區域(因此這種保護是不完美的),而且有時攻擊者 能夠將 0 插入目的地址。 此外,有些實現不支持跳板代碼,因此可能必須對需要這種保護的程序禁用該特性。Red Hat 的 Ingo Molnar 在他的「exec-shield」補丁中實現了這種思想,該補丁由 Fedora 核心(可從 Red Hat 獲得它的免費版本)所使用。最新版本的 OpenWall GNU/Linux (OWL)使用了 Solar Designer 提供的這種方法的實現(請參閱 參考資料 以獲得指向這些版本的鏈接)。
其他方法
還有其他許多方法。一種方法就是使標准庫對攻擊更具抵抗力。Lucent Technologies 開發了 Libsafe,這是多個標准 C 庫函數的包裝,也就是像 strcpy() 這樣已知的對 stack-smashing 攻擊很脆弱的函數。Libsafe 是在 LGPL 下授予許可證的開放源代碼軟體。那些函數的 libsafe 版本執行相關的檢查,確保數組改寫不會超出堆棧楨。然而,這種方法僅保護那些特定的函數,而不是從總體上防止堆棧溢出缺陷,並且它僅保護堆棧,而不保護堆棧中的局部變數。它們的最初實現使用了 LD_PRELOAD ,而這可能與其他程序產生沖突。Linux 的 Mandrake 發行套件(從 7.1 版開始)包括了 libsafe。
另一種方法稱為「分割控制和數據堆棧」―― 基本的思路是將堆棧分割為兩個堆棧,一個用於存儲控制信息(比如「返回」地址),另一個用於控制其他所有數據。Xu et al. 在 gcc 中實現了這種方法,StackShield 在匯編程序中實現了這種方法。這樣使得操縱返回地址困難多了,但它不會阻止改變調用函數的數據的緩沖區溢出攻擊。
事實上還有其他方法,包括隨機化可執行程序的位置;Crispen 的「PointGuard」將這種探測儀思想引申到了堆中,等等。如何保護當今的計算機現在已成了一項活躍的研究任務。
一般保護是不足夠的
如此多不同的方法意味著什麼呢?對用戶來說,好的一面在於大量創新的方法正在試驗之中;長期看來,這種「競爭」會更容易看出哪種方法最好。而且,這種多樣性還使得攻擊者躲避所有這些方法更加困難。然而,這種多樣性也意味著開發人員需要 避免編寫會干擾其中任何一種方法的代碼。這在實踐上是很容易的;只要不編寫對堆棧楨執行低級操作或對堆棧的布局作假設的代碼就行了。即使不存在這些方法,這也是一個很好的建議。
操作系統供應商需要參與進來就相當明顯了:至少挑選一種方法,並使用它。緩沖區溢出是第一號的問題,這些方法中最好的方法通常能夠減輕發行套件中幾乎半數已知缺陷的影響。可以證明,不管是基於探測儀的方法更好,還是基於非可執行堆棧的方法更好,它們都具有各自的優點。可以將它們結合起來使用,但是少數方法不支持這樣使用,因為附加的性能損失使得這樣做不值得。我並沒有其他意思,至少就這些方法本身而言是這樣;libsafe 和分割控制及數據堆棧的方法在它們所提供的保護方面都具有局限性。當然,最糟糕的解決辦法就是根本不對這個第一號的缺陷提供保護。還沒有實現一種方法的軟體供應商需要立即計劃這樣做。從 2004 年開始,用戶應該開始避免使用這樣的操作系統,即它們至少沒有對緩沖區溢出提供某種自動保護機制。
然而,沒有哪種方法允許開發人員忽略緩沖區溢出。所有這些方法都能夠被攻擊者破壞。 攻擊者也許能夠通過改變函數中其他數據的值來利用緩沖區溢出;沒有哪種方法能夠防止這點。如果能夠插入某些難於創建的值(比如 NUL 字元),那麼這其中的許多方法都能被攻擊者繞開;隨著多媒體和壓縮數據變得更加普遍,攻擊者繞開這些方法就更容易了。從根本上講,所有這些方法都能減輕從程序接管攻擊到拒絕服務攻擊的緩沖區溢出攻擊所帶來的破壞。遺憾的是,隨著計算機系統在更多關鍵場合的使用,即使拒絕服務通常也是不可接受的。因而,盡管發行套件應該至少包括一種適當的防禦方法,並且開發人員應該使用(而不是反對)那些方法,但是開發人員仍然需要最初就編寫無缺陷的軟體。
C/C++ 解決方案
針對緩沖區溢出的一種簡單解決辦法就是轉為使用能夠防止緩沖區溢出的語言。畢竟,除了 C 和 C++ 外,幾乎每種高級語言都具有有效防止緩沖區溢出的內置機制。但是許多開發人員因為種種原因還是選擇使用 C 和 C++。那麼您能做什麼呢?
事實證明存在許多防止緩沖區溢出的不同技術,但它們都可劃分為以下兩種方法:靜態分配的緩沖區和動態分配的緩沖區。首先,我們將講述這兩種方法分別是什麼。然後,我們將討論靜態方法的兩個例子(標准 C strncpy/strncat 和 OpenBSD 的 strlcpy/strlcat ),接著討論動態方法的兩個例子(SafeStr 和 C++ 的 std::string )。
重要選擇:靜態和動態分配的緩沖區
緩沖區具有有限的空間。因此實際上存在處理緩沖區空間不足的兩種可能方式。
「靜態分配的緩沖區」方法:也就是當緩沖區用完時,您抱怨並拒絕為緩沖區增加任何空間。
「動態分配的緩沖區」方法:也就是當緩沖區用完時,動態地將緩沖區大小調整到更大的尺寸,直至用完所有內存。
靜態方法具有一些缺點。事實上,靜態方法有時可能會帶來不同的缺陷。靜態方法基本上就是丟棄「過多的」數據。如果程序無論如何還是使用了結果數據,那麼攻擊者會嘗試填滿緩沖區,以便在數據被截斷時使用他希望的任何內容來填充緩沖區。如果使用靜態方法,應該確保攻擊者能夠做的最糟糕的事情不會使得預先的假設無效,而且檢查最終結果也是一個好主意。
動態方法具有許多優點:它們能夠向上適用於更大的問題(而不是帶來任意的限制),而且它們沒有導致安全問題的字元數組截斷問題。但它們也具有自身的問題:在接受任意大小的數據時,可能會遇到內存不足的情況 ―― 而這在輸入時也許不會發生。任何內存分配都可能會失敗,而編寫真正很好地處理該問題的 C 或 C++ 程序是很困難的。甚至在內存真正用完之前,也可能導致計算機變得太忙而不可用。簡而言之,動態方法通常使得攻擊者發起拒絕服務攻擊變得更加容易。因此仍然需要限制輸入。此外,必須小心設計程序來處理任意位置的內存耗盡問題,而這不是一件容易的事情。
標准 C 庫方法
最簡單的方法之一是簡單地使用那些設計用於防止緩沖區溢出的標准 C 庫函數(即使在使用 C ++,這也是可行的),特別是 strncpy(3) 和 strncat(3) 。這些標准 C 庫函數一般支持靜態分配方法,也就是在數據無法裝入緩沖區時丟棄它。這種方法的最大優點在於,您可以肯定這些函數在任何機器上都可用,並且任何 C/C++ 開發人員都會了解它們。許許多多的程序都是以這種方式編寫的,並且確實可行。
遺憾的是,要正確地做到這點卻是令人吃驚的困難。下面是其中的一些問題:
strncpy(3) 和 strncat(3) 都要求您給出 剩餘的空間,而不是給出緩沖區的總大小。這之所以會成為問題是因為,雖然緩沖區的大小一經分配就不會變化,但是緩沖區中剩餘的空間量會在每次添加或刪除數據時發生變化。這意味著程序員必須始終跟蹤或重新計算剩餘的空間。這種跟蹤或重新計算很容易出錯,而任何錯誤都可能給緩沖區攻擊打開方便之門。
在發生了溢出(和數據丟失)時,兩個函數都不會給出簡單的報告,因此如果要檢測緩沖區溢出,程序員就必須做更多的工作。
如果源字元串至少和目標一樣長,那麼函數 strncpy(3) 還不會使用 NUL 來結束字元串;這可能會在以後導致嚴重破壞。因而,在運行 strncpy(3) 之後,您通常需要重新結束目標字元串。
函數 strncpy(3) 還可以用來僅把源字元串的 一部分復制到目標中。 在執行這個操作時,要復制的字元的數目通常是基於源字元串的相關信息來計算的。 這樣的危險之處在於,如果忘了考慮可用的緩沖區空間,那麼 即使在使用 strncpy(3) 時也可能會留下緩沖區攻擊隱患。這個函數也不會復制 NUL 字元,這可能也是一個問題。
可以通過一種防止緩沖區溢出的方式使用 sprintf() ,但是意外地留下緩沖區溢出攻擊隱患是非常容易的。 sprintf() 函數使用一個控制字元串來指定輸出格式,該控制字元串通常包括「 %s 」(字元串輸出)。如果指定字元串輸出的精確指定符(比如 %.10s ),那麼您就能夠通過指定輸出的最大長度來防止緩沖區溢出。甚至可以使用「 * 」作為精確指定符(比如「 %.*s 」),這樣您就可以傳入一個最大長度值,而不是在控制字元串中嵌入最大長度值。這樣的問題在於,很容易就會不正確地使用 sprintf() 。一個「欄位寬度」(比如「 %10s 」)僅指定了最小長度 ―― 而不是最大長度。「欄位寬度」指定符會留下緩沖區溢出隱患,而欄位寬度和精確寬度指定符看起來幾乎完全相同 ―― 唯一的區別在於安全的版本具有一個點號。另一個問題在於,精確欄位僅指定一個參數的最大長度,但是緩沖區需要針對組合起來的數據的最大尺寸調整大小。
scanf() 系列函數具有一個最大寬度值,至少 IEEE Standard 1003-2001 清楚地規定這些函數一定不能讀取超過最大寬度的數據。遺憾的是,並非所有規范都清楚地規定了這一點,我們不清楚是否所有實現都正確地實現了這些限制(這在如今的 GNU/Linux 系統上就 不能正確地工作)。如果您依賴它,那麼在安裝或初始化期間運行小測試來確保它能正確工作,這樣做將是明智的。
strncpy(3) 還存在一個惱人的性能問題。從理論上講, strncpy(3) 是 strcpy(3) 的安全替代者,但是 strncpy(3) 還會在源字元串結束時使用 NUL 來填充整個目標空間。 這是很奇怪的,因為實際上並不存在這樣做的很好理由,但是它從一開始就是這樣,並且有些程序還依賴這個特性。這意味著從 strcpy(3) 切換到 strncpy(3) 會降低性能 ―― 這在如今的計算機上通常不是一個嚴重的問題,但它仍然是有害的。
那麼可以使用標准 C 庫的常式來防止緩沖區溢出嗎?是的,不過並不容易。如果計劃沿著這條路線走,您需要理解上述的所有要點。或者,您可以使用下面幾節將要講述的一種替代方法。
OpenBSD 的 strlcpy/strlcat
OpenBSD 開發人員開發了一種不同的靜態方法,這種方法基於他們開發的新函數 strlcpy(3) 和 strlcat(3) 。這些函數執行字元串復制和拼接,不過更不容易出錯。這些函數的原型如下:
size_t strlcpy (char *dst, const char *src, size_t size);
size_t strlcat (char *dst, const char *src, size_t size);
strlcpy() 函數把以 NUL 結尾的字元串從「 src 」復制到「 dst 」(最多 size-1 個字元)。 strlcat() 函數把以 NUL 結尾的字元串 src 附加到 dst 的結尾(但是目標中的字元數目將不超過 size-1)。
初看起來,它們的原型和標准 C 庫函數並沒有多大區別。但是事實上,它們之間存在一些顯著區別。這些函數都接受目標的總大小(而不是剩餘空間)作為參數。這意味著您不必連續地重新計算空間大小,而這是一項易於出錯的任務。此外,只要目標的大小至少為 1,兩個函數都保證目標將以 NUL 結尾(您不能將任何內容放入零長度的緩沖區)。如果沒有發生緩沖區溢出,返回值始終是組合字元串的長度;這使得檢測緩沖區溢出真正變得容易了。
遺憾的是, strlcpy(3) 和 strlcat(3) 並不是在類 UNIX 系統的標准庫中普遍可用。OpenBSD 和 Solaris 將它們內置在 <string.h> 中,但是 GNU/Linux 系統卻不是這樣。這並不是一件那麼困難的事情;因為當底層系統沒有提供它們時,您甚至可以將一些小函數直接包括在自己的程序源代碼中。
SafeStr
Messier 和 Viega 開發了「SafeStr」庫,這是一種用於 C 的動態方法,它自動根據需要調整字元串的大小。使用 malloc() 實現所使用的相同技巧,Safestr 字元串很容易轉換為常規的 C「 char * 」字元串:safestr 在傳遞指針「之前」的地址處存儲重要信息。這種技術的優點在於,在現有程序中使用 SafeStr 將會很容易。SafeStr 還支持「只讀」和「受信任」的字元串,這也可能是有用的。這種方法的一個問題在於它需要 XXL(這是一個給 C 添加異常處理和資源管理支持的庫),因此您實際上要僅為了處理字元串而引入一個重要的庫。Safestr 是在開放源代碼的 BSD 風格的許可證下發布的。
C++ std::string
針對 C++ 用戶的另一種解決方案是標準的 std::string 類,這是一種動態的方法(緩沖區根據需要而增長)。它幾乎是不需要傷腦筋的,因為 C++ 語言直接支持該類,因此不需要做特殊的工作就可使用它,並且其他庫也可能會使用它。就其本身而言, std::string 通常會防止緩沖區溢出,但是如果通過它提取一個普通 C 字元串(比如使用 data() 或 c_str() ),那麼上面討論的所有問題都會重新出現。還要記住 data() 並不總是返回以 NUL 結尾的字元串。
由於種種歷史原因,許多 C++ 庫和預先存在的程序都創建了它們自己的字元串類。這可能使得 std::string 更難於使用,並且在使用那些庫或修改那些程序時效率很低,因為不同的字元串類型將不得不連續地來回轉換。並非其他所有那些字元串類都會防止緩沖區溢出,並且如果它們對 C 不受保護的 char* 類型執行自動轉換,那麼緩沖區溢出缺陷很容易引入那些類中。
⑦ 如何構建自己的 Linux 發行版
風格的書籍,深入研究了調度、內存管理、多進程和線程、文件系統,以及用戶與內核之間的交互。寫作 Linux 書籍的作家相對於 UNIX 作家來說有一個優勢:盡管團體發生了劇變,但是 Linux 內核不可能分成幾個相互競爭的分支,由於 GNU Public License (GPL)、集中式研究實驗室 Open Source Development Lab (OSDL) 和 Linus Torvalds 不可動搖的地位,使得 Linux 有幸成為一個緩慢移動的目標(slow-moving target)。為什麼 UNIX 內核很重要除了某些方面具有一定的相似性之外,不同的 Unix 內核並不怎麼相同。各種 UNIX 風格也具有一個 Linux 所缺少的優點:所有 UNIX 風格都被假定是完全的操作系統。Linux 通常被描述為「只是一個內核」(如果有這樣的定義的話,也是一個武斷的定義),它給出公共功能和實現的核心,不管內核是運行在不太強大的 Pentium? II 機器上還是 Symmetric Multiprocessing (SMP) 系統上,這些公共功能和實現都不會發生本質上的改變。為了更加簡化,有人可能會說,離 Linux 內核越遠,就會發現更多的變化,而 UNIX 系統則趨向於是各種 UNIX/POSIX 標準的離散實現。事情並沒有這么簡單。檢測 Linux 內核和系統級代碼可能是一件很費時間的事情,並且在現實世界中會限制使用。LFS 項目旨在解決 Linux 上有限的系統級可理解性問題。關於內核需要大量的庫和工具來讓 Linux 系統執行最基本的任務這一事實,已經做過討論了,但是如果一個比較熟練的用戶具有一個 slim-line Linux 發行版,他不想下載幾吉位元組不讓他優化系統、也不讓他拋開所有這些麻煩且不必要的工具的二進制代碼,那該怎麼辦?如果一個非常熟練的用戶拒絕接受各種社團發行版的 苛刻條件(diktat),而想要運行一個來自 CD 的 Linux/Apache/MySQL/PHP (LAMP) 類型的應用程序堆棧,那該怎麼辦?LFS 可以解決這些問題。回頁首Linux From ScratchLFS 項目顯然建立於那些對於構成基本的 Linux 系統來說充分而不必要的源文件的基礎之上。它已經超越了 Linux 內核和設備驅動程序,因為要產生一個可工作的 Linux 系統,您必須添加一個完整的編譯器工具鏈、許多 Linux 匯編程序實用工具、glibc 系統庫、系統配置工具和連接到 userland shell 訪問的工具。LFS 建立在這樣一個假設的基礎之上,即 Linux 或 UNIX 允許具有一些腳本編寫知識的用戶,了解一個完全有用的系統的工作方式,而不用深入研究內核代碼本身。為了了解 Linux 系統的工作方式,LFS 的創建者們確定,通過遵循模塊依賴性來編譯系統,可能是了解一般操作系統和特定的 Linux 的機制最自然的方式。用戶掌握了編譯過程之後,就可以開始消除依賴性樹的那些連接到與支持操作系統基本目的無關的系統組件的部件。例如,在編譯完成之後,消除編譯器工具鏈本身是可行的。在沒有全套的命令行實用工具時,可以湊合使用嵌入式 LAMP 堆棧。配置實用工具也可以被丟棄,大多數用戶可以湊合著用一個而不是太多 Linux 將會支持的文件系統。Linux 部件LFS 系統的一個重要部件是,可以作為 tar ball 得到的大量源文件。文檔是另一個重要部件,並且是最重要的。實際上,很有可能利用一個最新的 LFS 書籍文件並創建一個 LFS 發行版,因為 LFS 書籍中詳細描述了每個下載位置和每個源文件及其依賴性的特徵。用於從內核到編譯器到 shell 編譯每組源文件的過程都是已經寫好了的,如果可能,您也可以在描述具有不同特徵的系統的 LFS 書籍中找到替代的常式。LFS 系統的另一個不太可能出現在一般用戶工具箱中的部件是,在基本 LFS 系統組合在一起之後引導系統所需的引導腳本。現在對 LFS 發行版的最大警告是:勇敢的發行版構建者所需的是一個可工作的 Linux 發行版,包括一個完整的編譯器工具鏈和一套文件系統創建實用工具。自然,所有基於源代碼的 Linux 發行版都需要使用各個發行版都完全不同的特定編譯器版本來引導。LFS 不是該領域的惟一系統,但它是惟一允許您直接處理單個源文件的系統。大多數其他基於源代碼的 Linux 系統,比如 Sourcemage 和 MyGeOS,提供一個完整的下載,建議用戶使用。LFS 不作這樣的假設,並且鼓勵拆開 LFS 框架。預先假設起作用的 Linux 發行版已安裝在非外來的(nonexotic)硬體上,即使 LFS 可能沒有配置工具和腳本那麼受關注。要編譯 LFS,您需要准備一個分區和一個文件系統,還需要編譯一個編譯器和系統庫。如果用手工完成的話,這是一個相當傷腦筋的過程,但是也的確可以增加您在處理其餘安裝方面的自信。整個系統的編譯要花一小時到四天的時間,具體時間取決於底層硬體的年代和您的命令行技術熟練程度。作一個相當大的假設,如果您願意很大程度上保留書籍安裝,並使對 LFS 書籍中提議的安裝的更改保持最小,您也可以使用自動化的安裝常式來安裝基於 LFS 的發行版。安裝常式沒有在 LFS 書籍中給出,但是可以在名稱 Automated Linux from Scratch (ALFS) 之下作為基於 XML 的發行版得到。活動安裝可以作為基於 C 的腳本得到,該腳本使用 ncurses 來模擬圖形安裝。該安裝也叫做 nALFS 並給出一個極為靈活的包安裝框架。工作需要一個起作用的 Linux 系統,並帶有可以工作的 C 編譯器和 XML 解析器。一個可以工作的 LFS 系統就足夠了。Automated Linux From ScratchALFS 的目的是超越 LFS 本身。LFS 自己指導基於 Linux 的操作系統的內部工作,但是它不具有單個圖形用戶界面 (GUI)。LFS 既不允許連接到網路,也不允許連接到 Internet。ALFS 可以簡化系統的擴展,例如,通過添加支持 Internet 訪問的庫,或者通過安裝圖形桌面所需的 X 庫。回頁首超越LFSLFS 的創建者們認識到了對其他各種基於源代碼的 Linux 系統的需求。為了這些想要超越 LFS 並添加 X Window System、GNOME 和網路支持的人,創建了另一個 LFS 派生物:Beyond Linux From Scratch (BLFS)。 第三版 LFS 書籍(讓我們不會忘記是在談論書籍而非發行版),形成一個以一個角為基礎的三角形:對於自動化編譯和完全的基於源代碼的 Linux 發行版,基本的 LFS 版本是基礎。BLFS 將基本的 Linux 系統轉化成一個完全的用戶廣泛接受的 Linux 系統。AFLS 簡化基於源代碼的 Linux 安裝的安裝和擴展。整個基於源代碼的系統的編譯是由一個腳本指導的,您在針對運行腳本的硬體調整腳本之後,就可以讓腳本自己運行了。在您(或安裝工程師)確定需要運行哪些包,也即特定的辦公應用程序套件之後,您就可以容易地擴展安裝順序了。ALFS 也遲早會派上用場,因為它適用於從源代碼進行網路范圍的安裝。回頁首Hardened LFSLFS 家族的最後一個成員解決基於源代碼的 Linux 的一個特別重要的方面:安全性。對於不想依賴於所選的 Linux 發行版伺服器交付的補丁的那些人,安全性的普通方法是,針對所選的核心庫和應用程序跟蹤安全報告。對於 LFS 實現人員來說,問題有些不同:盡管不是不可能,但是也難以審計 Linux 內核代碼,也許還包括集中於基於 Linux 的操作系統的內部機能的很多庫和實用工具。代碼審計相當費時,並且只有當補丁伺服器是由專門人員集中維護的時,添加大量補丁才是明智的。但是可以取代一些為反映安全問題的新方法而重新編寫過的庫。一個好的例子是,通過從一個適當大的隨機數池中隨機分配數字,使得猜測進程標識符極為困難。OpenBSD 項目最先採用了該方法,隨後,各種 UNIX 風格和 Linux 發行版都採用了該方法。一個相當新的項目叫做 Hardened Linux From Scratch (HLFS),在 Linux 下採用了這一安全方法。該項目假設相當正規地掌握了 LFS 和 BLFS 的一些部件,並使用了不會成為大多數 Linux 系統中的標準的幾個實用工具和庫。添加到 HLFS 中的最重要的部件可能是 Stack-Smashing Protector (SSP),通過使用 gcc 指令可以啟用該功能。SSP 開發來防禦 stack-smashing 攻擊,這類攻擊屬於影響 Linux 系統的一類最常見的安全威脅。其他安全措施包括一流的隨機數生成器和位置獨立的可執行程序的編譯,其中通常轉化成靜態鏈接對象代碼的可執行代碼作為共享庫出現,而位置獨立的可執行庫通過將地址隨機化可以隱藏這些地址。當然,可從 HLFS Web 站點得到大量補丁,並可查看其源代碼。回頁首日益壯大的 LFS 家族在許多方面,Linux 版本的 LFS 家族是一個方法,給予黑客以構造基於 Linux 的操作系統的能力。但是對於 LFS 的創建者們來說,最重要的結果似乎是:通過 LFS,所有 Linux 發行版對於所涉及的用戶都成了智能的了。通過允許用戶一部分一部分地構建 Linux 發行版,並幫助用戶將一個基於 Linux 的操作系統看作一個由許多部件組成的系統,還可以用另外的方法構建 Linux 發行版。更一般來說,用戶要更改構建 Linux 發行版的方式,不必要是程序員:只要從構建 LFS 系統學到的一點腳本編寫能力就足夠了。LFS 專家可以改變和擴展 Linux 發行版的完美組合,而不會影響它的基本結構。對於有人員和專家維護 Linux 系統,但沒有資金從咨詢機構和公司購買商業支持的機構來說,該功能特別重要。已經針對教學目的和大型網路,演示了基於 LFS 的 Linux 系統。它們也有可能用於其他領域。參考資料 您可以參閱本文在 developerWorks 全球站點上的 英文原文。
最新LFS 版本的門戶是 LFS 下載站點。它提供從幾個方面可用的 LFS 書籍。
LFS 家族的最新成員是 HLFS 或Hardened LFS 風格。
如果願意,LFS 狂熱者們可以創建 自動化的 安裝。
如果基本的 LFS 安裝還不夠,那麼可以繼續安裝更加復雜的 extended Linux From Scratch,也叫做 BLFS。
為了防禦 stack-smashing 攻擊,SSP 指令使得 gcc 可以編譯針對該類型威脅而保護的代碼。
位置獨立的代碼在關於 position-independent executables 的一章中作了簡短介紹。
在developerWorks Linux 專區 可以找到更多為 Linux 開發者准備的資源。
訪問developerWorks 開放源碼專區,獲得廣泛的 how-to 信息、工具和項目更新,以幫助您利用開放源碼技術進行開發,並將它們與 IBM 產品一起使用。
利用IBM 試用軟體 革新您的下一個開放源碼開發項目。可從下載或 DVD 得到試用軟體。
通過參與 developerWorks blogs 加入developerWorks 社區。
關於作者Frank Pohlmann 以前研究的是中東宗教歷史,後來各基金會認為研究宗教辯證歷史與當今世界相去甚遠,從此他便專攻自己熱愛的領域 —— 免費軟體。他獲准成為英國的 LinuxUser and Developer 的技術編輯。您可通過 [email protected] 與他聯系。關閉[x]關於報告濫用的幫助報告濫用謝謝! 此內容已經標識給管理員注意。關閉[x]關於報告濫用的幫助報告濫用報告濫用提交失敗。 請稍後重試。關閉[x]developerWorks:登錄IBM ID:需要一個 IBM ID?忘記IBM ID?密碼:忘記密碼?更改您的密碼 保持登錄。單擊提交則表示您同意developerWorks 的條款和條件。 使用條款 當您初次登錄到 developerWorks 時,將會為您創建一份概要信息。您在developerWorks 概要信息中選擇公開的信息將公開顯示給其他人,但您可以隨時修改這些信息的顯示狀態。您的姓名(除非選擇隱藏)和昵稱將和您在 developerWorks 發布的內容一同顯示。所有提交的信息確保安全。關閉[x]請選擇您的昵稱:當您初次登錄到 developerWorks 時,將會為您創建一份概要信息,您需要指定一個昵稱。您的昵稱將和您在 developerWorks 發布的內容顯示在一起。昵稱長度在 3 至 31 個字元之間。 您的昵稱在 developerWorks 社區中必須是唯一的,並且出於隱私保護的原因,不能是您的電子郵件地址。昵稱:(長度在 3 至 31 個字元之間)單擊提交則表示您同意developerWorks 的條款和條件。 使用條款. 所有提交的信息確保安全。為本文評分評論回頁首
⑧ C# 如何點出結構體欄位
們為何如此常見和如此危險。然後討論廣泛用於解決緩沖區溢出的新 Linux 和 UNIX 方法 ―― 以及為什麼這些方法還不足夠。隨後將展示 C/C++ 程序中防止緩沖區溢出的各種方法,同時包括靜態調整大小的方法(比如標準的 C 庫和 OpenBSD/strlcpy 解決方案)和動態調整大小的解決方案,以及一些將為您提供幫助的工具。最後,本文以一些關於緩沖區溢出缺陷的未來發展形勢的預測來結束全文的討論。
如果希望自己的程序是安全的,您需要知道什麼是緩沖區溢出,如何防止它們,可以採用哪些最新的自動化工具來防止它們(以及為什麼這些工具還不足夠),還有如何在您自己的程序中防止它們。
什麼是緩沖區溢出?
緩沖區以前可能被定義為「包含相同數據類型的實例的一個連續計算機內存塊」。在 C 和 C++ 中,緩沖區通常是使用數組和諸如 malloc() 和 new 這樣的內存分配常式來實現的。極其常見的緩沖區種類是簡單的字元數組。 溢出 是指數據被添加到分配給該緩沖區的內存塊之外。
如果攻擊者能夠導致緩沖區溢出,那麼它就能控製程序中的其他值。雖然存在許多利用緩沖區溢出的方法,不過最常見的方法還是「stack-smashing」攻擊。Elias Levy (又名為 Aleph One)的一篇經典文章「Smashing the Stack for Fun and Profit」解釋了 stack-smashing 攻擊,Elias Levy 是 Bugtraq 郵件列表(請參閱 參考資料 以獲得相關鏈接)的前任主持人。
清單 1. 一個簡單的程序
void function1(int a, int b, int c) {
char buffer1[5];
gets(buffer1); /* DON'T DO THIS */
}
void main() {
function(1,2,3);
}
假設使用 gcc 來編譯清單 1 中的簡單程序,在 X86 上的 Linux 中運行,並且緊跟在對 gets() 的調用之後中止。此時的內存內容看起來像什麼樣子呢?答案是它看起來類似圖 1,其中展示了從左邊的低位地址到右邊的高位地址排序的內存布局。
圖 1. 堆棧視圖
內存的底部 內存的頂部
buffer1 sfp ret a b c
<--- 增長 --- [ ] [ ] [ ] [ ] [ ] [ ] ...
為什麼緩沖區溢出如此常見?
在幾乎所有計算機語言中,不管是新的語言還是舊的語言,使緩沖區溢出的任何嘗試通常都會被該語言本身自動檢測並阻止(比如通過引發一個異常或根據需要給緩沖區添加更多空間)。但是有兩種語言不是這樣:C 和 C++ 語言。C 和 C++ 語言通常只是讓額外的數據亂寫到其餘內存的任何位置,而這種情況可能被利用從而導致恐怖的結果。更糟糕的是,用 C 和 C++ 編寫正確的代碼來始終如一地處理緩沖區溢出則更為困難;很容易就會意外地導致緩沖區溢出。除了 C 和 C++ 使用得 非常廣泛外,上述這些可能都是不相關的事實;例如,Red Hat Linux 7.1 中 86% 的代碼行都是用 C 或 C ++ 編寫的。因此,大量的代碼對這個問題都是脆弱的,因為實現語言無法保護代碼避免這個問題。
在 C 和 C++ 語言本身中,這個問題是不容易解決的。該問題基於 C 語言的根本設計決定(特別是 C 語言中指針和數組的處理方式)。由於 C++ 是最兼容的 C 語言超集,它也具有相同的問題。存在一些能防止這個問題的 C/C++ 兼容版本,但是它們存在極其嚴重的性能問題。而且一旦改變 C 語言來防止這個問題,它就不再是 C 語言了。許多語言(比如 Java 和 C#)在語法上類似 C,但它們實際上是不同的語言,將現有 C 或 C++ 程序改為使用那些語言是一項艱巨的任務。
然而,其他語言的用戶也不應該沾沾自喜。有些語言存在允許緩沖區溢出發生的「轉義」子句。Ada 一般會檢測和防止緩沖區溢出(即針對這樣的嘗試引發一個異常),但是不同的程序可能會禁用這個特性。C# 一般會檢測和防止緩沖區溢出,但是它允許程序員將某些常式定義為「不安全的」,而這樣的代碼 可能 會導致緩沖區溢出。因此如果您使用那些轉義機制,就需要使用 C/C++ 程序所必須使用的相同種類的保護機制。許多語言都是用 C 語言來實現的(至少部分是用 C 語言來實現的 ),並且用任何語言編寫的所有程序本質上都依賴用 C 或 C++ 編寫的庫。因此,所有程序都會繼承那些問題,所以了解這些問題是很重要的。
導致緩沖區溢出的常見 C 和 C++ 錯誤
從根本上講,在程序將數據讀入或復制到緩沖區中的任何時候,它需要在復制 之前檢查是否有足夠的空間。能夠容易看出來的異常就不可能會發生 ―― 但是程序通常會隨時間而變更,從而使得不可能成為可能。
遺憾的是,C 和 C++ 附帶的大量危險函數(或普遍使用的庫)甚至連這點(指檢查空間)也無法做到。程序對這些函數的任何使用都是一個警告信號,因為除非慎重地使用它們,否則它們就會成為程序缺陷。您不需要記住這些函數的列表;我的真正目的是說明這個問題是多麼普遍。這些函數包括 strcpy(3)、strcat(3)、sprintf(3) (及其同類 vsprintf(3) )和 gets(3) 。 scanf() 函數集( scanf(3)、fscanf(3)、sscanf(3)、vscanf(3)、vsscanf(3) 和 vfscanf(3) )可能會導致問題,因為使用一個沒有定義最大長度的格式是很容易的(當讀取不受信任的輸入時,使用格式「%s」總是一個錯誤)。
其他危險的函數包括 realpath(3)、getopt(3)、getpass(3)、streadd(3)、strecpy(3) 和 strtrns(3) 。 從理論上講, snprintf() 應該是相對安全的 ―― 在現代 GNU/Linux 系統中的確是這樣。但是非常老的 UNIX 和 Linux 系統沒有實現 snprintf() 所應該實現的保護機制。
Microsoft 的庫中還有在相應平台上導致同類問題的其他函數(這些函數包括 wcscpy()、_tcscpy()、_mbscpy()、wcscat()、_tcscat()、_mbscat() 和 CopyMemory() )。注意,如果使用 Microsoft 的 MultiByteToWideChar() 函數,還存在一個常見的危險錯誤 ―― 該函數需要一個最大尺寸作為字元數目,但是程序員經常將該尺寸以位元組計(更普遍的需要),結果導致緩沖區溢出缺陷。
另一個問題是 C 和 C++ 對整數具有非常弱的類型檢查,一般不會檢測操作這些整數的問題。由於它們要求程序員手工做所有的問題檢測工作,因此以某種可被利用的方式不正確地操作那些整數是很容易的。特別是,當您需要跟蹤緩沖區長度或讀取某個內容的長度時,通常就是這種情況。但是如果使用一個有符號的值來存儲這個長度值會發生什麼情況呢 ―― 攻擊者會使它「成為負值」,然後把該數據解釋為一個實際上很大的正值嗎?當數字值在不同的尺寸之間轉換時,攻擊者會利用這個操作嗎?數值溢出可被利用嗎? 有時處理整數的方式會導致程序缺陷。
防止緩沖區溢出的新技術
當然,要讓程序員 不犯常見錯誤是很難的,而讓程序(以及程序員)改為使用另一種語言通常更為困難。那麼為何不讓底層系統自動保護程序避免這些問題呢?最起碼,避免 stack-smashing 攻擊是一件好事,因為 stack-smashing 攻擊是特別容易做到的。
一般來說,更改底層系統以避免常見的安全問題是一個極好的想法,我們在本文後面也會遇到這個主題。事實證明存在許多可用的防禦措施,而一些最受歡迎的措施可分組為以下類別:
基於探測方法(canary)的防禦。這包括 StackGuard(由 Immunix 所使用)、ProPolice(由 OpenBSD 所使用)和 Microsoft 的 /GS 選項。
非執行的堆棧防禦。這包括 Solar Designer 的 non-exec 補丁(由 OpenWall 所使用)和 exec shield(由 Red Hat/Fedora 所使用)。
其他方法。這包括 libsafe(由 Mandrake 所使用)和堆棧分割方法。
遺憾的是,迄今所見的所有方法都具有弱點,因此它們不是萬能葯,但是它們會提供一些幫助。
基於探測方法的防禦
研究人員 Crispen Cowan 創建了一個稱為 StackGuard 的有趣方法。Stackguard 修改 C 編譯器(gcc),以便將一個「探測」值插入到返回地址的前面。「探測儀」就像煤礦中的探測儀:它在某個地方出故障時發出警告。在任何函數返回之前,它執行檢查以確保探測值沒有改變。如果攻擊者改寫返回地址(作為 stack-smashing 攻擊的一部分),探測儀的值或許就會改變,系統內就會相應地中止。這是一種有用的方法,不過要注意這種方法無法防止緩沖區溢出改寫其他值(攻擊者仍然能夠利用這些值來攻擊系統)。人們也曾擴展這種方法來保護其他值(比如堆上的值)。Stackguard(以及其他防禦措施)由 Immunix 所使用。
IBM 的 stack-smashing 保護程序(ssp,起初名為 ProPolice)是 StackGuard 的方法的一種變化形式。像 StackGuard 一樣,ssp 使用一個修改過的編譯器在函數調用中插入一個探測儀以檢測堆棧溢出。然而,它給這種基本的思路添加了一些有趣的變化。 它對存儲局部變數的位置進行重新排序,並復制函數參數中的指針,以便它們也在任何數組之前。這樣增強了ssp 的保護能力;它意味著緩沖區溢出不會修改指針值(否則能夠控制指針的攻擊者就能使用指針來控製程序保存數據的位置)。默認情況下,它不會檢測所有函數,而只是檢測確實需要保護的函數(主要是使用字元數組的函數)。從理論上講,這樣會稍微削弱保護能力,但是這種默認行為改進了性能,同時仍然能夠防止大多數問題。考慮到實用的因素,它們以獨立於體系結構的方式使用 gcc 來實現它們的方法,從而使其更易於運用。從 2003 年 5 月的發布版本開始,廣受贊譽的 OpenBSD(它重點關注安全性)在他們的整個發行套件中使用了 ssp(也稱為 ProPolice)。
Microsoft 基於 StackGuard 的成果,添加了一個編譯器標記(/GS)來實現其 C 編譯器中的探測儀。
非執行的堆棧防禦
另一種方法首先使得在堆棧上執行代碼變得不可能。 遺憾的是,x86 處理器(最常見的處理器)的內存保護機制無法容易地支持這點;通常,如果一個內存頁是可讀的,它就是可執行的。一個名叫 Solar Designer 的開發人員想出了一種內核和處理器機制的聰明組合,為 Linux 內核創建了一個「非執行的堆棧補丁」;有了這個補丁,堆棧上的程序就不再能夠像通常的那樣在 x86 上運行。 事實證明在有些情況下,可執行程序 需要在堆棧上;這包括信號處理和跳板代碼(trampoline)處理。trampoline 是有時由編譯器(比如 GNAT Ada 編譯器)生成的奇妙結構,用以支持像嵌套子常式之類的結構。Solar Designer 還解決了如何在防止攻擊的同時使這些特殊情況不受影響的問題。
一段時間之後,人們又想出了一種防止該問題的新思路:將所有可執行代碼轉移到一個稱為「ASCII 保護(ASCII armor)」區域的內存區。要理解這是如何工作的,就必須知道攻擊者通常不能使用一般的緩沖區溢出攻擊來插入 ASCII NUL 字元(0)這個事實。 這意味著攻擊者會發現,要使一個程序返回包含 0 的地址是很困難的。由於這個事實,將所有可執行代碼轉移到包含 0 的地址就會使得攻擊該程序困難多了。
具有這個屬性的最大連續內存范圍是從 0 到 0x01010100 的一組內存地址,因此它們就被命名為 ASCII 保護區域(還有具有此屬性的其他地址,但它們是分散的)。與非可執行的堆棧相結合,這種方法就相當有價值了:非可執行的堆棧阻止攻擊者發送可執行代碼,而 ASCII 保護內存使得攻擊者難於通過利用現有代碼來繞過非可執行堆棧。這樣將保護程序代碼避免堆棧、緩沖區和函數指針溢出,而且全都不需重新編譯。
然而,ASCII 保護內存並不適用於所有程序;大程序也許無法裝入 ASCII 保護內存區域(因此這種保護是不完美的),而且有時攻擊者 能夠將 0 插入目的地址。 此外,有些實現不支持跳板代碼,因此可能必須對需要這種保護的程序禁用該特性。Red Hat 的 Ingo Molnar 在他的「exec-shield」補丁中實現了這種思想,該補丁由 Fedora 核心(可從 Red Hat 獲得它的免費版本)所使用。最新版本的 OpenWall GNU/Linux (OWL)使用了 Solar Designer 提供的這種方法的實現(請參閱 參考資料 以獲得指向這些版本的鏈接)。
其他方法
還有其他許多方法。一種方法就是使標准庫對攻擊更具抵抗力。Lucent Technologies 開發了 Libsafe,這是多個標准 C 庫函數的包裝,也就是像 strcpy() 這樣已知的對 stack-smashing 攻擊很脆弱的函數。Libsafe 是在 LGPL 下授予許可證的開放源代碼軟體。那些函數的 libsafe 版本執行相關的檢查,確保數組改寫不會超出堆棧楨。然而,這種方法僅保護那些特定的函數,而不是從總體上防止堆棧溢出缺陷,並且它僅保護堆棧,而不保護堆棧中的局部變數。它們的最初實現使用了 LD_PRELOAD ,而這可能與其他程序產生沖突。Linux 的 Mandrake 發行套件(從 7.1 版開始)包括了 libsafe。
另一種方法稱為「分割控制和數據堆棧」―― 基本的思路是將堆棧分割為兩個堆棧,一個用於存儲控制信息(比如「返回」地址),另一個用於控制其他所有數據。Xu et al. 在 gcc 中實現了這種方法,StackShield 在匯編程序中實現了這種方法。這樣使得操縱返回地址困難多了,但它不會阻止改變調用函數的數據的緩沖區溢出攻擊。
事實上還有其他方法,包括隨機化可執行程序的位置;Crispen 的「PointGuard」將這種探測儀思想引申到了堆中,等等。如何保護當今的計算機現在已成了一項活躍的研究任務。
一般保護是不足夠的
如此多不同的方法意味著什麼呢?對用戶來說,好的一面在於大量創新的方法正在試驗之中;長期看來,這種「競爭」會更容易看出哪種方法最好。而且,這種多樣性還使得攻擊者躲避所有這些方法更加困難。然而,這種多樣性也意味著開發人員需要 避免編寫會干擾其中任何一種方法的代碼。這在實踐上是很容易的;只要不編寫對堆棧楨執行低級操作或對堆棧的布局作假設的代碼就行了。即使不存在這些方法,這也是一個很好的建議。
操作系統供應商需要參與進來就相當明顯了:至少挑選一種方法,並使用它。緩沖區溢出是第一號的問題,這些方法中最好的方法通常能夠減輕發行套件中幾乎半數已知缺陷的影響。可以證明,不管是基於探測儀的方法更好,還是基於非可執行堆棧的方法更好,它們都具有各自的優點。可以將它們結合起來使用,但是少數方法不支持這樣使用,因為附加的性能損失使得這樣做不值得。我並沒有其他意思,至少就這些方法本身而言是這樣;libsafe 和分割控制及數據堆棧的方法在它們所提供的保護方面都具有局限性。當然,最糟糕的解決辦法就是根本不對這個第一號的缺陷提供保護。還沒有實現一種方法的軟體供應商需要立即計劃這樣做。從 2004 年開始,用戶應該開始避免使用這樣的操作系統,即它們至少沒有對緩沖區溢出提供某種自動保護機制。
然而,沒有哪種方法允許開發人員忽略緩沖區溢出。所有這些方法都能夠被攻擊者破壞。 攻擊者也許能夠通過改變函數中其他數據的值來利用緩沖區溢出;沒有哪種方法能夠防止這點。如果能夠插入某些難於創建的值(比如 NUL 字元),那麼這其中的許多方法都能被攻擊者繞開;隨著多媒體和壓縮數據變得更加普遍,攻擊者繞開這些方法就更容易了。從根本上講,所有這些方法都能減輕從程序接管攻擊到拒絕服務攻擊的緩沖區溢出攻擊所帶來的破壞。遺憾的是,隨著計算機系統在更多關鍵場合的使用,即使拒絕服務通常也是不可接受的。因而,盡管發行套件應該至少包括一種適當的防禦方法,並且開發人員應該使用(而不是反對)那些方法,但是開發人員仍然需要最初就編寫無缺陷的軟體。
C/C++ 解決方案
針對緩沖區溢出的一種簡單解決辦法就是轉為使用能夠防止緩沖區溢出的語言。畢竟,除了 C 和 C++ 外,幾乎每種高級語言都具有有效防止緩沖區溢出的內置機制。但是許多開發人員因為種種原因還是選擇使用 C 和 C++。那麼您能做什麼呢?
事實證明存在許多防止緩沖區溢出的不同技術,但它們都可劃分為以下兩種方法:靜態分配的緩沖區和動態分配的緩沖區。首先,我們將講述這兩種方法分別是什麼。然後,我們將討論靜態方法的兩個例子(標准 C strncpy/strncat 和 OpenBSD 的 strlcpy/strlcat ),接著討論動態方法的兩個例子(SafeStr 和 C++ 的 std::string )。
重要選擇:靜態和動態分配的緩沖區
緩沖區具有有限的空間。因此實際上存在處理緩沖區空間不足的兩種可能方式。
「靜態分配的緩沖區」方法:也就是當緩沖區用完時,您抱怨並拒絕為緩沖區增加任何空間。
「動態分配的緩沖區」方法:也就是當緩沖區用完時,動態地將緩沖區大小調整到更大的尺寸,直至用完所有內存。
靜態方法具有一些缺點。事實上,靜態方法有時可能會帶來不同的缺陷。靜態方法基本上就是丟棄「過多的」數據。如果程序無論如何還是使用了結果數據,那麼攻擊者會嘗試填滿緩沖區,以便在數據被截斷時使用他希望的任何內容來填充緩沖區。如果使用靜態方法,應該確保攻擊者能夠做的最糟糕的事情不會使得預先的假設無效,而且檢查最終結果也是一個好主意。
動態方法具有許多優點:它們能夠向上適用於更大的問題(而不是帶來任意的限制),而且它們沒有導致安全問題的字元數組截斷問題。但它們也具有自身的問題:在接受任意大小的數據時,可能會遇到內存不足的情況 ―― 而這在輸入時也許不會發生。任何內存分配都可能會失敗,而編寫真正很好地處理該問題的 C 或 C++ 程序是很困難的。甚至在內存真正用完之前,也可能導致計算機變得太忙而不可用。簡而言之,動態方法通常使得攻擊者發起拒絕服務攻擊變得更加容易。因此仍然需要限制輸入。此外,必須小心設計程序來處理任意位置的內存耗盡問題,而這不是一件容易的事情。
標准 C 庫方法
最簡單的方法之一是簡單地使用那些設計用於防止緩沖區溢出的標准 C 庫函數(即使在使用 C ++,這也是可行的),特別是 strncpy(3) 和 strncat(3) 。這些標准 C 庫函數一般支持靜態分配方法,也就是在數據無法裝入緩沖區時丟棄它。這種方法的最大優點在於,您可以肯定這些函數在任何機器上都可用,並且任何 C/C++ 開發人員都會了解它們。許許多多的程序都是以這種方式編寫的,並且確實可行。
遺憾的是,要正確地做到這點卻是令人吃驚的困難。下面是其中的一些問題:
strncpy(3) 和 strncat(3) 都要求您給出 剩餘的空間,而不是給出緩沖區的總大小。這之所以會成為問題是因為,雖然緩沖區的大小一經分配就不會變化,但是緩沖區中剩餘的空間量會在每次添加或刪除數據時發生變化。這意味著程序員必須始終跟蹤或重新計算剩餘的空間。這種跟蹤或重新計算很容易出錯,而任何錯誤都可能給緩沖區攻擊打開方便之門。
在發生了溢出(和數據丟失)時,兩個函數都不會給出簡單的報告,因此如果要檢測緩沖區溢出,程序員就必須做更多的工作。
如果源字元串至少和目標一樣長,那麼函數 strncpy(3) 還不會使用 NUL 來結束字元串;這可能會在以後導致嚴重破壞。因而,在運行 strncpy(3) 之後,您通常需要重新結束目標字元串。
函數 strncpy(3) 還可以用來僅把源字元串的 一部分復制到目標中。 在執行這個操作時,要復制的字元的數目通常是基於源字元串的相關信息來計算的。 這樣的危險之處在於,如果忘了考慮可用的緩沖區空間,那麼 即使在使用 strncpy(3) 時也可能會留下緩沖區攻擊隱患。這個函數也不會復制 NUL 字元,這可能也是一個問題。
可以通過一種防止緩沖區溢出的方式使用 sprintf() ,但是意外地留下緩沖區溢出攻擊隱患是非常容易的。 sprintf() 函數使用一個控制字元串來指定輸出格式,該控制字元串通常包括「 %s 」(字元串輸出)。如果指定字元串輸出的精確指定符(比如 %.10s ),那麼您就能夠通過指定輸出的最大長度來防止緩沖區溢出。甚至可以使用「 * 」作為精確指定符(比如「 %.*s 」),這樣您就可以傳入一個最大長度值,而不是在控制字元串中嵌入最大長度值。這樣的問題在於,很容易就會不正確地使用 sprintf() 。一個「欄位寬度」(比如「 %10s 」)僅指定了最小長度 ―― 而不是最大長度。「欄位寬度」指定符會留下緩沖區溢出隱患,而欄位寬度和精確寬度指定符看起來幾乎完全相同 ―― 唯一的區別在於安全的版本具有一個點號。另一個問題在於,精確欄位僅指定一個參數的最大長度,但是緩沖區需要針對組合起來的數據的最大尺寸調整大小。
scanf() 系列函數具有一個最大寬度值,至少 IEEE Standard 1003-2001 清楚地規定這些函數一定不能讀取超過最大寬度的數據。遺憾的是,並非所有規范都清楚地規定了這一點,我們不清楚是否所有實現都正確地實現了這些限制(這在如今的 GNU/Linux 系統上就 不能正確地工作)。如果您依賴它,那麼在安裝或初始化期間運行小測試來確保它能正確工作,這樣做將是明智的。
strncpy(3) 還存在一個惱人的性能問題。從理論上講, strncpy(3) 是 strcpy(3) 的安全替代者,但是 strncpy(3) 還會在源字元串結束時使用 NUL 來填充整個目標空間。 這是很奇怪的,因為實際上並不存在這樣做的很好理由,但是它從一開始就是這樣,並且有些程序還依賴這個特性。這意味著從 strcpy(3) 切換到 strncpy(3) 會降低性能 ―― 這在如今的計算機上通常不是一個嚴重的問題,但它仍然是有害的。
那麼可以使用標准 C 庫的常式來防止緩沖區溢出嗎?是的,不過並不容易。如果計劃沿著這條路線走,您需要理解上述的所有要點。或者,您可以使用下面幾節將要講述的一種替代方法。
OpenBSD 的 strlcpy/strlcat
OpenBSD 開發人員開發了一種不同的靜態方法,這種方法基於他們開發的新函數 strlcpy(3) 和 strlcat(3) 。這些函數執行字元串復制和拼接,不過更不容易出錯。這些函數的原型如下:
size_t strlcpy (char *dst, const char *src, size_t size);
size_t strlcat (char *dst, const char *src, size_t size);
strlcpy() 函數把以 NUL 結尾的字元串從「 src 」復制到「 dst 」(最多 size-1 個字元)。 strlcat() 函數把以 NUL 結尾的字元串 src 附加到 dst 的結尾(但是目標中的字元數目將不超過 size-1)。
初看起來,它們的原型和標准 C 庫函數並沒有多大區別。但是事實上,它們之間存在一些顯著區別。這些函數都接受目標的總大小(而不是剩餘空間)作為參數。這意味著您不必連續地重新計算空間大小,而這是一項易於出錯的任務。此外,只要目標的大小至少為 1,兩個函數都保證目標將以 NUL 結尾(您不能將任何內容放入零長度的緩沖區)。如果沒有發生緩沖區溢出,返回值始終是組合字元串的長度;這使得檢測緩沖區溢出真正變得容易了。
遺憾的是, strlcpy(3) 和 strlcat(3) 並不是在類 UNIX 系統的標准庫中普遍可用。OpenBSD 和 Solaris 將它們內置在 <string.h> 中,但是 GNU/Linux 系統卻不是這樣。這並不是一件那麼困難的事情;因為當底層系統沒有提供它們時,您甚至可以將一些小函數直接包括在自己的程序源代碼中。
SafeStr
Messier 和 Viega 開發了「SafeStr」庫,這是一種用於 C 的動態方法,它自動根據需要調整字元串的大小。使用 malloc() 實現所使用的相同技巧,Safestr 字元串很容易轉換為常規的 C「 char * 」字元串:safestr 在傳遞指針「之前」的地址處存儲重要信息。這種技術的優點在於,在現有程序中使用 SafeStr 將會很容易。SafeStr 還支持「只讀」和「受信任」的字元串,這也可能是有用的。這種方法的一個問題在於它需要 XXL(這是一個給 C 添加異常處理和資源管理支持的庫),因此您實際上要僅為了處理字元串而引入一個重要的庫。Safestr 是在開放源代碼的 BSD 風格的許可證下發布的。
C++ std::string
針對 C++ 用戶的另一種解決方案是標準的 std::string 類,這是一種動態的方法(緩沖區根據需要而增長)。它幾乎是不需要傷腦筋的,因為 C++ 語言直接支持該類,因此不需要做特殊的工作就可使用它,並且其他庫也可能會使用它。就其本身而言, std::string 通常會防止緩沖區溢出,但是如果通過它提取一個普通 C 字元串(比如使用 data() 或 c_str() ),那麼上面討論的所有問題都會重新出現。還要記住 data() 並不總是返回以 NUL 結尾的字元串。
由於種種歷史原因,許多 C++ 庫和預先存在的程序都創建了它們自己的字元串類。這可能使得 std::string 更難於使用,並且在使用那些庫或修改那些程序時效率很低,因為不同的字元串類型將不得不連續地來回轉換。並非其他所有那些字元串類都會防止緩沖區溢出,並且如果它們對 C 不受保護的 char* 類型執行自動轉換,那麼緩沖區溢出缺陷很容易引入那些類中。
⑨ propolice是什麼
IBM 的 stack-smashing 保護程序(ssp,起初名為 ProPolice)是 StackGuard 的方法的一種變化形式。像 StackGuard 一樣,ssp 使用一個修改過的編譯器在函數調用中插入一個探測儀以檢測堆棧溢出。然而,它給這種基本的思路添加了一些有趣的變化。 它對存儲局部變數的位置進行重新排序,並復制函數參數中的指針,以便它們也在任何數組之前。這樣增強了ssp 的保護能力;它意味著緩沖區溢出不會修改指針值(否則能夠控制指針的攻擊者就能使用指針來控製程序保存數據的位置)。默認情況下,它不會檢測所有函數,而只是檢測確實需要保護的函數(主要是使用字元數組的函數)。從理論上講,這樣會稍微削弱保護能力,但是這種默認行為改進了性能,同時仍然能夠防止大多數問題。考慮到實用的因素,它們以獨立於體系結構的方式使用 gcc 來實現它們的方法,從而使其更易於運用。從 2003 年 5 月的發布版本開始,廣受贊譽的 OpenBSD(它重點關注安全性)在他們的整個發行套件中使用了 ssp(也稱為 ProPolice)。