《網(wǎng)絡(luò)安全與保密》課件第12章_第1頁
《網(wǎng)絡(luò)安全與保密》課件第12章_第2頁
《網(wǎng)絡(luò)安全與保密》課件第12章_第3頁
《網(wǎng)絡(luò)安全與保密》課件第12章_第4頁
《網(wǎng)絡(luò)安全與保密》課件第12章_第5頁
已閱讀5頁,還剩272頁未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

第12章安全編程12.1緩沖區(qū)溢出(bufferoverflow)12.2格式化字符串(FormatString)12.3整數(shù)安全12.4條件競爭12.5臨時(shí)文件12.6動(dòng)態(tài)內(nèi)存分配和釋放參考文獻(xiàn)思考題

由于商業(yè)、非商業(yè)的原因,程序員所希望的就是在最短的時(shí)間內(nèi)完成程序并實(shí)現(xiàn)盡可能多的功能。這就很容易導(dǎo)致程序中存在安全問題,也將大大增加應(yīng)用程序的后續(xù)費(fèi)用(包括清除漏洞、給用戶升級產(chǎn)品、產(chǎn)品更新時(shí)的技術(shù)支持等所需的費(fèi)用)。事實(shí)上,在過去的許多年里,互聯(lián)網(wǎng)上的許多攻擊都是利用程序員在程序中犯下的錯(cuò)誤實(shí)現(xiàn)的,其中包括常見的緩沖區(qū)溢出攻擊和格式化字符串攻擊等。

在程序設(shè)計(jì)過程中,如果我們能遵守一定的安全編程準(zhǔn)則,那么就可以大大減少程序中的安全錯(cuò)誤,從而也能讓后續(xù)費(fèi)用降到最低。本章主要討論程序設(shè)計(jì)中常見的安全問題,并針對這些問題給出了安全建議和正確的使用方法。本章主要是針對C語言展開討論,雖然其它編程語言中也存在安全問題。

12.1緩沖區(qū)溢出(bufferoverflow)

所謂的緩沖區(qū)溢出,是指一種系統(tǒng)攻擊的手段,它是通過往程序的緩沖區(qū)寫超出其長度的內(nèi)容,造成緩沖區(qū)的溢出,從而破壞程序的堆棧,使程序轉(zhuǎn)而執(zhí)行其它指令,以達(dá)到攻擊的目的。美國俄勒岡州科學(xué)與技術(shù)研究生院(OGI)最近公布的一篇論文[1]稱:“過去10年中,緩存溢出一直是計(jì)算機(jī)的最大安全隱患。由于這種攻擊使得任何人都能夠完全控制某一臺主機(jī),因此構(gòu)成了對計(jì)算機(jī)安全的最大威脅。”

緩沖區(qū)溢出問題大部分是由于在程序中沒有進(jìn)行適當(dāng)?shù)倪吔鐧z查造成的。安全問題分析家認(rèn)為,解決緩存溢出問題的第一步是,人們必須更加小心地進(jìn)行計(jì)算機(jī)的編程。程序員只要增加能夠處理過長字符串的指令,就能夠防止對他們的產(chǎn)品的攻擊。安全分析家AlanPaller說:“造成問題的原因是程序員的粗心大意。你編寫了一個(gè)程序,讓他人輸入信息,為他們提供了一定數(shù)量的字符空間,但是你不檢查程序能不能接受更多的字符。像這樣的程序員是不稱職的,這也是產(chǎn)生問題的根源。他的錯(cuò)誤將要我們花費(fèi)很大的精力來加以解決?!?/p>

本節(jié)將首先介紹緩沖區(qū)攻擊的基本原理;然后給出針對緩沖區(qū)溢出的安全建議。

12.1.1背景知識

為了更好的了解緩沖區(qū)溢出的機(jī)理,我們先來介紹一些背景知識。首先看一看執(zhí)行狀態(tài)下的C語言程序結(jié)構(gòu)和處理器處理機(jī)器代碼的情況。

一個(gè)應(yīng)用程序在運(yùn)行時(shí),它在內(nèi)存中的映像可以分為三個(gè)部分:代碼段、數(shù)據(jù)段和堆棧段。代碼段對應(yīng)于運(yùn)行文件中的Text節(jié),如圖12-1所示,其中包括運(yùn)行代碼和只讀數(shù)據(jù)。這個(gè)段在內(nèi)存中一般被標(biāo)記為只讀,任何企圖修改這個(gè)段中數(shù)據(jù)的指令將引發(fā)一個(gè)SegmentationViolation錯(cuò)誤。數(shù)據(jù)段對應(yīng)于運(yùn)行文件中的Data節(jié)和BSS,其中存放的是各種數(shù)據(jù)(經(jīng)過初始化的和未經(jīng)初始化的)和靜態(tài)變量。

圖12-1程序內(nèi)存布局

圖中,堆棧。保存調(diào)用程序的參數(shù)信息,所需要的本地變量,其它幀指針等。堆棧的特點(diǎn)是其生長方向與內(nèi)存的生長方向相反,即堆棧的底端是內(nèi)存的高端,而堆棧的高端是內(nèi)存的低端。堆。動(dòng)態(tài)內(nèi)存分配區(qū)。BSS。符號塊起始地址,未初始化數(shù)據(jù)段(函數(shù)之外),如intfoo;floatbaz。數(shù)據(jù)段。初始化數(shù)據(jù)(函數(shù)之外),如inthit=9,charhead[]=“ugh”。文本段。就是機(jī)器指令,等于操作碼+操作數(shù)。

除此之外,CPU當(dāng)中還有一些很重要的寄存器是我們所關(guān)心的,我們重點(diǎn)看一下指令指針(InstructionPointer,IP),也被稱為程序計(jì)數(shù)器。IP寄存器指向下一條被執(zhí)行指令的地址。一般代碼是不能夠直接訪問IP值的。在每條指令被執(zhí)行之后IP值自動(dòng)增加一個(gè)值以指向下一條要執(zhí)行的指令地址。當(dāng)你要調(diào)用子程序時(shí),系統(tǒng)就需要知道下一條指令的地址以及如何返回原始調(diào)用處。調(diào)用指令通常規(guī)定了要往IP所加的值,并把它壓入堆棧。而調(diào)用函數(shù)中的返回指令會把堆棧值彈出給IP以恢復(fù)調(diào)用后下一條指令的執(zhí)行。圖12-1中的堆棧就是存儲和取出返回地址信息的地方。

現(xiàn)在大部分程序員都用高級語言進(jìn)行模塊化編程。在這些應(yīng)用程序中,不可避免地會出現(xiàn)各種函數(shù)調(diào)用,比如調(diào)用C運(yùn)行庫、Win32API等等。這些調(diào)用大部分都被編譯器編譯為Call語句。當(dāng)CPU在執(zhí)行這條指令時(shí),除了將IP變?yōu)檎{(diào)用函數(shù)的入口點(diǎn)以外,還要將調(diào)用后的返回地址壓入堆棧。這些函數(shù)調(diào)用往往還帶有不同數(shù)量的入口參數(shù)和局部變量,在這種情況下,編譯器往往會生成一些指令將這些數(shù)據(jù)也存入堆棧(有些也可通過寄存器傳遞)。我們稱由于一個(gè)函數(shù)調(diào)用所導(dǎo)致的需要在堆棧中存放的數(shù)據(jù)和返回地址為一個(gè)堆棧幀(StackFrame)。

下面我們通過一個(gè)簡單的例子來分析一下棧幀的結(jié)構(gòu):

voidproc(inti) //被調(diào)用函數(shù)

{

intlocal; //本地變量

local=i;

}

voidmain() //主函數(shù)

{

proc(1); //調(diào)用proc函數(shù),帶一個(gè)

參數(shù),參數(shù)值為1

}

這段代碼經(jīng)過編譯器編譯后為(以PC為例):

main:push1 //入口參數(shù)壓棧

callproc //主函數(shù)調(diào)用子函數(shù)處

...

proc:pushebp //子函數(shù)入口處,將基址寄存器壓棧

mov

ebp,esp

sub

esp,4 //esp指針減4,為局部變量分配空間

mov

eax,[ebp+08] //ebp+08即訪問參數(shù)l

mov

[ebp-4],eax //ebp-4即訪問局部變量local

add

esp,4 //收回局部變量空間

pop

ebp //恢復(fù)ebp值

ret

4 //調(diào)用返回

不難看出,這個(gè)程序在執(zhí)行proc過程時(shí),棧幀的結(jié)構(gòu)如圖12-2所示。

圖12-2棧幀結(jié)構(gòu)

由此可以看出,當(dāng)程序中發(fā)生函數(shù)調(diào)用時(shí),計(jì)算機(jī)依次完成如下操作:首先把入口參數(shù)壓入堆棧;然后保存指令寄存器(EIP)中的內(nèi)容作為返回地址(RET);再把基址寄存器(EBP)壓入堆棧;隨后將當(dāng)前的棧指針(ESP)拷貝到EBP做為新的基地址;最后為本地變量留出一定空間,同時(shí)將ESP減去適當(dāng)?shù)臄?shù)值。因此,棧幀的一般結(jié)構(gòu)如圖12-3所示。

圖12-3棧幀的一般結(jié)構(gòu)

12.1.2緩沖區(qū)溢出基本原理

首先我們看一下未執(zhí)行strcpy時(shí)(已經(jīng)調(diào)用函數(shù)function)堆棧中的情況,如圖12-4所示。

圖12-4調(diào)用function后的堆棧情況

當(dāng)執(zhí)行strcpy()函數(shù)時(shí),程序?qū)?56字節(jié)的字符‘A’(0x41)拷貝給buffer數(shù)組中,然而buffer數(shù)組只能容納16字節(jié)。由于C語言并不進(jìn)行邊界檢查,所以結(jié)果是buffer數(shù)組后面的240字節(jié)的內(nèi)容也被覆蓋掉了,這其中包括EBP、RET地址、large_string地址。此時(shí)RET地址變成了0x41414141h,所以當(dāng)函數(shù)結(jié)束調(diào)用返回時(shí),它將返回到0x41414141h地址處繼續(xù)執(zhí)行下一條指令。但由于這個(gè)地址并不在程序?qū)嶋H使用的虛存空間范圍內(nèi),所以系統(tǒng)就報(bào)“SegmentationViolation”錯(cuò)誤。這就是所謂的緩沖區(qū)溢出。

12.1.3緩沖區(qū)溢出攻擊方式

一般而言,有以下幾種緩沖區(qū)溢出攻擊的方式:

(1)如上舉例所示,我們僅僅通過向緩沖區(qū)中寫入任意超長的字符就可以導(dǎo)致程序崩潰,實(shí)現(xiàn)了另一種形式的拒絕服務(wù)攻擊。

(2)攻擊者可用任意數(shù)據(jù)覆蓋堆棧中變量的內(nèi)容。安全漏洞的一個(gè)經(jīng)典例子是基于口令的認(rèn)證。首先從本地?cái)?shù)據(jù)庫中讀取口令并存儲在本地變量中;然后用戶輸入口令,程序比較這兩個(gè)字符串。關(guān)鍵代碼如下:

現(xiàn)在用戶的輸入超過12個(gè)字符(32位對準(zhǔn))就會覆蓋數(shù)組origPassword[]的內(nèi)容。因此如果用戶輸入是opensesame!!opensesame!!,userPassword[]和origPassword[]的內(nèi)容就是同一個(gè)字符串opensesame!!,從而比較結(jié)果為二者相等。

(3)覆蓋堆棧中保存的寄存器。因此,通過輸入超長的字符從而覆蓋指令指針I(yè)P,攻擊者可以利用函數(shù)結(jié)尾的RET來執(zhí)行程序中的任意程序代碼。一般而言,不是利用程序本身的代碼,而是植入攻擊者自己的機(jī)器代碼(一般稱之為Shellcode)。要做到這一點(diǎn),只需把機(jī)器代碼寫到變量中,然后被拷貝到堆棧中,同時(shí)把保存的IP地址設(shè)成攻擊代碼的開始地址。如果變量長度不足以保存機(jī)器代碼,就要把機(jī)器代碼存儲在程序環(huán)境中——堆或任意用戶可訪問的地址空間。當(dāng)函數(shù)執(zhí)行完畢,RET從堆棧中獲得IP的值(已被攻擊者設(shè)置)并寫入CPU的IP寄存器中,則計(jì)算機(jī)就忠實(shí)的執(zhí)行攻擊代碼序列。

(4)此外,為了執(zhí)行第三方代碼也可以覆蓋函數(shù)指針。例如在下面這段代碼中,定義了兩個(gè)未初始化的靜態(tài)變量buff和funcPtr,它們都保存在BSS段中。由于strncpy()函數(shù)沒有進(jìn)行邊界檢查,因此,argv[1]提供的字符串長度有可能超過BUFFSIZE,從而導(dǎo)致函數(shù)指針可能被覆蓋。

這樣,攻擊者只要把Shellcode代碼放在全局、本地或環(huán)境變量中,并使函數(shù)指針指向這段程序代碼。當(dāng)用函數(shù)指針調(diào)用函數(shù)時(shí),執(zhí)行的將不是函數(shù)代碼而是攻擊代碼。

12.1.4有關(guān)Shellcode

一般而言,攻擊者利用緩沖區(qū)溢出漏洞并不是僅僅想使程序崩潰,而是想通過這種攻擊做更多的事。如通過緩沖區(qū)溢出提升權(quán)限,從而獲得對系統(tǒng)更多的訪問和控制權(quán)。這些目的的實(shí)現(xiàn)就是由所謂的Shellcode來完成的。

12.1.5安全建議

1.編寫正確的代碼

前面提到過,解決緩存溢出問題的第一步是,人們必須更加小心地進(jìn)行計(jì)算機(jī)的編程。程序員只要增加能夠處理過長字符串的指令,就能夠防止對他們的產(chǎn)品的攻擊。

針對這些易受緩沖區(qū)溢出攻擊的Libc函數(shù),ArashBaratloo、TimothyTsai和NavjotSingh(朗訊技術(shù)公司)開發(fā)出了封裝這些庫函數(shù)的Libsafe[4]。Libsafe是一個(gè)簡單的動(dòng)態(tài)載入庫。安裝Libsafe后,Libsafe解析那些不安全的Libc庫函數(shù)并用Libsafe中實(shí)現(xiàn)的安全函數(shù)代替,而Libsafe實(shí)現(xiàn)邊界檢查,從而保證代碼更安全。

微軟在strsafe.h[5]中提供了安全處理字符串的函數(shù)集。其設(shè)計(jì)目標(biāo)是:始終以NULL結(jié)束字符串、始終指定目標(biāo)緩存區(qū)大小、始終返回一致的狀態(tài)碼。

2.?dāng)?shù)組邊界檢查

根據(jù)緩沖區(qū)溢出的基本原理可知,要實(shí)現(xiàn)緩沖區(qū)溢出就必須擾亂程序的流程,使程序不按既定的流程運(yùn)行。如果給局部變量分配的內(nèi)存空間沒被溢出,改變程序運(yùn)行狀態(tài)也就無從談起。為此,我們可以利用一些編譯器或工具對程序進(jìn)行數(shù)組邊界檢查,即當(dāng)對數(shù)組進(jìn)行讀寫時(shí)要確保對數(shù)組元素的操作是在正確的范圍內(nèi)進(jìn)行。目前,CompaqC編譯器、RichardJones和PaulKelly[6]開發(fā)的一個(gè)gcc的補(bǔ)丁以及IBM開發(fā)的Purify等都能實(shí)現(xiàn)一定的數(shù)組邊界檢查功能。

3.程序指針完整性檢查

程序指針完整性檢查和邊界檢查有略微的不同。與防止程序指針被改變不同,程序指針完整性檢查在程序指針被引用之前檢測它是否有變。因此,即便一個(gè)攻擊者成功地改變了程序的指針,由于系統(tǒng)事先檢測到了指針的改變,因此這個(gè)指針將不會被使用。

StackGuard[7]通過不允許改動(dòng)活動(dòng)函數(shù)的返回地址RET來防止某些類型的緩沖區(qū)溢出攻擊。StackGuard在堆棧中函數(shù)返回地址后面存儲一些附加的字節(jié)(稱為canary),當(dāng)函數(shù)返回時(shí),首先檢查這個(gè)附加的字節(jié)是否被改動(dòng)過,如圖12-5所示圖12-5基于canary的堆棧防溢出

如果攻擊者企圖進(jìn)行緩沖區(qū)溢出攻擊,則他要覆蓋緩沖區(qū),修改附加字節(jié),從而在函數(shù)返回前被檢測到。但是,如果攻擊者預(yù)見到這些附加字節(jié)的存在,并且能在溢出過程中同樣地制造他們,那么他就能成功地躲過StackGuard的檢測。通常,我們有如下的兩種方案對付這種欺騙:

1)終止符號。也就是附加符號使用C語言的終止符號,如0(null),CR,LF,-1(EOF)等不能在常用的字符串函數(shù)中使用的符號,因?yàn)檫@些函數(shù)一旦遇到這些終止符號,就結(jié)束函數(shù)過程

(2)隨機(jī)符號。附加符號使用一個(gè)在函數(shù)調(diào)用時(shí)產(chǎn)生的一個(gè)32位的隨機(jī)數(shù)來實(shí)現(xiàn)保密,使得攻擊者不可能猜測到附加字節(jié)的內(nèi)容。而且,每次調(diào)用,附加字節(jié)的內(nèi)容都在改變,也無法預(yù)測。

實(shí)驗(yàn)數(shù)據(jù)表明,StackGuard對于各種系統(tǒng)的緩沖區(qū)溢出攻擊都有很好的保護(hù)作用,并能保持較好的兼容性和系統(tǒng)性能。有分析表明,StackGuard能有效抵御現(xiàn)在的和將來的基于堆棧的攻擊。

很多攻擊者開始利用指針來修改返回地址這種更一般的方法實(shí)現(xiàn)緩存溢出攻擊,如堆溢出、格式串攻擊等。這些技術(shù)可以成功避開上述提到的數(shù)組邊界檢查和StackGuard等溢出攻擊保護(hù)機(jī)制。

在內(nèi)存中的指針,并僅當(dāng)指針被加載到CPU寄存器里時(shí)才解密指針。攻擊者雖然可以覆蓋一個(gè)指針值,但由于不知道解密密鑰,因此無法偽造指針值。圖12-6顯示PointGuard防止溢出攻擊的過程。被攻擊者覆蓋的指針被送入PointGuard解密,得到一個(gè)隨機(jī)地址引用,這個(gè)隨機(jī)地址很有可能指向一個(gè)不在進(jìn)程地址空間的位置,從而導(dǎo)致受害程序崩潰。

圖12-6基于PointGuard防溢出攻擊

4.不可執(zhí)行的緩沖區(qū)技術(shù)

根據(jù)上面介紹的緩沖區(qū)溢出原理我們知道,為了利用緩沖區(qū)溢出漏洞達(dá)到攻擊的目的,往往需要向緩沖區(qū)中寫入可執(zhí)行代碼。因此,防止緩沖區(qū)溢出攻擊的一個(gè)有效方法就是通過使被攻擊程序的數(shù)據(jù)段地址空間不可執(zhí)行,從而使攻擊者不可能執(zhí)行植入到被攻擊程序輸入緩沖區(qū)中的代碼。這就是所謂的不可執(zhí)行的緩沖區(qū)技術(shù)。一些舊版本的操作系統(tǒng)就是這樣實(shí)現(xiàn)的,但是最近的UNIX和MSWindows系統(tǒng)為了實(shí)現(xiàn)好的性能和功能而允許在數(shù)據(jù)段中動(dòng)態(tài)的放入可執(zhí)行代碼。

由于絕大部分情況下合法程序并不需要在堆棧中存放可執(zhí)行代碼,因此,完全可以讓操作系統(tǒng)使程序的堆棧段不可執(zhí)行。目前,Linux和Solaris為此發(fā)布了安全補(bǔ)丁。幾乎所有的合法程序都不會在堆棧中存放代碼,因此這種做法幾乎不產(chǎn)生任何兼容性問題,但在Linux中有兩個(gè)特例,其可執(zhí)行的代碼必須被放入堆棧中:

(1)信號傳遞。Linux通過把傳遞信號的代碼放入進(jìn)程堆棧,然后引發(fā)中斷跳轉(zhuǎn)到該代碼處執(zhí)行來實(shí)現(xiàn)向進(jìn)程發(fā)送Unix信號。非執(zhí)行緩沖區(qū)的補(bǔ)丁在發(fā)送信號的時(shí)候是允許緩沖區(qū)可執(zhí)行的。

(2)

GCC的在線重用。研究發(fā)現(xiàn)gcc在堆棧區(qū)里放置了可執(zhí)行的代碼作為在線重用之用。

不可執(zhí)行緩沖區(qū)技術(shù)可以有效地對付把代碼植入自動(dòng)變量的緩沖區(qū)溢出攻擊,而對于其它形式的攻擊則沒有效果。

12.2格式化字符串(FormatString)

在2000年下半年,一種稱之為“格式化字符串”的漏洞開始威脅系統(tǒng)和網(wǎng)絡(luò)的安全。這種攻擊方式與緩沖區(qū)溢出類似,也是通過覆蓋緩沖區(qū)來達(dá)到攻擊的目的,只是這種攻擊方式是利用格式化函數(shù),如printf()的格式化字符串%n來覆蓋緩沖區(qū)。本節(jié)將首先介紹導(dǎo)致格式化字符串漏洞的原因;接著介紹利用格式化字符串漏洞可實(shí)現(xiàn)的攻擊方式;最后針對格式化字符串漏洞提出了一些安全建議。

12.2.1格式化函數(shù)和格式化字符串

格式化函數(shù)是一類ANSIC函數(shù),包括:

(1)

fprintf—將格式化的數(shù)據(jù)打印至文件;

(2)

printf—將格式化的數(shù)據(jù)打印至標(biāo)準(zhǔn)輸出“stdout”;

(3)

sprintf—將格式化的數(shù)據(jù)存儲到緩存中;

(4)

snprintf—將指定長度的格式化的數(shù)據(jù)存儲到緩存

中;

(5)

vfprintf—將va_arg結(jié)構(gòu)中的格式化數(shù)據(jù)打印到文件;

(6)

vprintf—將va_arg結(jié)構(gòu)中的格式化數(shù)據(jù)打印到標(biāo)準(zhǔn)輸出“stdout”;

(7)

vsprintf—將va_arg結(jié)構(gòu)中的格式化數(shù)據(jù)存儲到緩存中;

(8)

vsnprintf—將va_arg結(jié)構(gòu)中指定長度的格式化數(shù)據(jù)存儲到緩存中。

另外,還有與格式化函數(shù)相關(guān)的如setproctitle、syslog、err、verr、warn、vwarn等函數(shù)。

在格式化函數(shù)的參數(shù)中有一項(xiàng)就是所謂的格式化字符串,它控制格式化函數(shù)所要進(jìn)行的操作并指定要打印的參數(shù)的數(shù)據(jù)類型和格式。格式化函數(shù)一般的調(diào)用格式為:

printf("<格式化字符串>",

<參量表>);

其中,格式化字符串包括兩部分內(nèi)容:一部分是正常字符,這些字符將按原樣輸出;另一部分是格式化控制字符,以“%”開始,后跟一個(gè)或幾個(gè)規(guī)定字符,用來控制輸出內(nèi)容格式,如表12-1所示。參量表是需要輸出的一系列參數(shù),其個(gè)數(shù)必須與格式化字符串所說明的輸出參數(shù)個(gè)數(shù)一樣,各參數(shù)之間用“,”分開,且順序一一對應(yīng),否則將會出現(xiàn)意想不到的錯(cuò)誤。

12.2.2格式化字符串漏洞基本原理

從基礎(chǔ)知識介紹中我們知道,格式化字符串控制著格式化函數(shù)所要進(jìn)行的操作,并指定要打印的參數(shù)的數(shù)據(jù)類型和格式。格式化字符串是一包含文本及格式化參數(shù)的ASCII串,如:

printf(“Thenumberis:%s\n”,“2011”);

這里,要打印的文本是“Thenumberis:”,后面跟的是格式化參數(shù)%s,在輸出時(shí)將被后面的參數(shù)(2011)代替。因此,這條語句的執(zhí)行結(jié)果為Thenumberis:2011。在這個(gè)例子中,格式化字符串指定的參量個(gè)數(shù)和參量表中的參量個(gè)數(shù)均為1,接下來我們看一個(gè)格式化串指定的參量個(gè)數(shù)大于參量表中參量個(gè)數(shù)的例子:

這里,我們只提供了一個(gè)數(shù)據(jù)參數(shù)"string",但在格式串中有三個(gè)打印控制字符。根據(jù)上一節(jié)中所介紹的堆棧知識,我們可以得到程序在調(diào)用printf函數(shù)時(shí)的堆棧情況如圖12-7所示。

圖12-7程序堆棧幀

因此這個(gè)程序?qū)⒂腥缦逻\(yùn)行結(jié)果:

[root@ecmformat]$./test1

String:HelloWorld!

,arg2:0x6c6c6548,arg3:0x6f57206f

由上面的分析可以看出,arg2,arg3所顯示的是main()中數(shù)組string中的前兩個(gè)字的內(nèi)容。也就是說,printf()只根據(jù)格式控制字符串內(nèi)的控制字符(%)的個(gè)數(shù)來依次顯示堆棧中控制字符串參數(shù)后面地址的內(nèi)容,每次根據(jù)“%格式”移動(dòng)相應(yīng)的字節(jié)數(shù)(如%s為4個(gè)字節(jié),%f為8個(gè)字節(jié))。

一般來說,格式化字符串由程序員定制,因而不會出現(xiàn)太大的安全問題。但是,如果程序中的格式化字符串由用戶提供,用戶就可以定制格式化字符串實(shí)現(xiàn)格式化字符串攻擊。例如用某個(gè)值覆蓋函數(shù)的返回指針,那么程序返回時(shí)就會跳到指定的位置去運(yùn)行。

12.2.3格式化字符串攻擊

1.使程序崩潰

利用格式化字符串使程序崩潰是比較簡單的攻擊方法。幾乎在所有的UNIX系統(tǒng)中,當(dāng)程序通過一個(gè)指針訪問用戶不可用的緩存區(qū)時(shí),進(jìn)程將向內(nèi)核發(fā)送SIGSEGV信號,然后程序終止并生成內(nèi)核轉(zhuǎn)存文件core。通過gdb調(diào)試core文件可以獲得一些有用的信息,如程序所用的非法指針。因此最簡單的攻擊方法就是利用類似于下面的語句:

printf(“%s%s%s%s%s%s%s%s%s”);

使程序崩潰。

因?yàn)楦袷絽?shù)‘%s’顯示由堆棧提供的地址內(nèi)存中的字符串,而堆棧中有許多數(shù)據(jù),因此很可能會去讀取非法地址中的數(shù)據(jù)內(nèi)容,從而導(dǎo)致進(jìn)程崩潰。同樣,我們也可以利用‘%n’格式參數(shù)向一個(gè)地址寫數(shù)據(jù)而導(dǎo)致進(jìn)程崩潰。

2.查看堆棧及進(jìn)程內(nèi)存

更進(jìn)一步,我們可以利用格式化字符串來查看堆棧中的內(nèi)容??紤]下面的語句:

printf(“%08x.%08x.%08x.%08x.%08x\n”);

這條語句將以8位16進(jìn)制數(shù)的形式依次顯示堆棧中內(nèi)容,其輸出將具有如下形式:

40012980.080628c4.bffff7a4.0000005.08059c04

這有助于我們在開發(fā)攻擊程序時(shí)確定地址偏移量。

同樣,我們可以利用格式化字符串查看堆棧以外其它內(nèi)存的內(nèi)容。為做到這一點(diǎn),我們首先要找到一個(gè)格式參數(shù),它能顯示一個(gè)地址中的內(nèi)容,即此格式參數(shù)對應(yīng)的輸出參數(shù)列表中的項(xiàng)為一個(gè)指針,指針指向某個(gè)內(nèi)存區(qū)。很顯然,“%s”能做到這一點(diǎn)。然后必須將所要顯示內(nèi)存的地址存放到堆棧的正確位置。

有時(shí),格式化字符串本身會存放在堆棧中,看下面代碼:

在上面的代碼中,buf中存儲的是由第一個(gè)命令行參數(shù)argv[1]提供的格式化字符串。假設(shè)提供的格式化字符串為:“abcd%d%s”,則調(diào)用printf()時(shí),堆棧中的情況如圖12-8所示。

圖12-8堆棧情形

本來,按照格式控制字符串“abcd%d%s”,其后應(yīng)該緊跟兩個(gè)被輸出的參量,而現(xiàn)在沒有提供,因此printf()就會把圖12-6中&buf后面的內(nèi)存空間認(rèn)為是等待輸出的參量。從地址buf到“abcd”之間共有4個(gè)字符即4字節(jié),因此我們用1個(gè)%d來對應(yīng)這4個(gè)字節(jié),這樣,最后一個(gè)格式參數(shù)“%s”對應(yīng)了地址“abcd”。因此將顯示地址“abcd”中的內(nèi)容。如果地址ap到緩沖區(qū)buf之間還有其它數(shù)據(jù),則可以通過調(diào)節(jié)“%d”的個(gè)數(shù)來使“%s”對應(yīng)“abcd”。如果地址ap到地址“abcd”之間的字節(jié)長度不是4的倍數(shù),可以在地址“abcd”前添加字節(jié)使其成為4的倍數(shù);當(dāng)然,我們可以利用“%c”來使地址步進(jìn),只是此時(shí)步進(jìn)速度太慢。

3.覆蓋內(nèi)存區(qū)

格式化字符串攻擊更主要的是利用格式化字符串來覆蓋內(nèi)存區(qū),一般是覆蓋指令指針,改變程序的流程,使調(diào)用結(jié)束時(shí)執(zhí)行攻擊者想要執(zhí)行的代碼。

我們首先介紹類似于傳統(tǒng)的緩沖區(qū)溢出的格式化字符串漏洞。這種漏洞一般是由于格式化函數(shù)對輸出長度不作檢查,而輸出數(shù)據(jù)又是由用戶全部或部分提供而導(dǎo)致的。為了達(dá)到攻擊的目的,用戶一般提供具有以下形式的輸出數(shù)據(jù):

“abcdef%nd<RET><nops><shellcode>”

其中,abcde可以是任意字符,主要是為了對齊地址;‘%nd’中的n是指打印寬度;RET指返回地址;shellcode是用戶提供的可執(zhí)行二進(jìn)制代碼等。這樣可以用RET來覆蓋原來的指令指針EIP,從而使程序返回時(shí)跳轉(zhuǎn)到shellcode執(zhí)行。

接下來,我們介紹如何直接覆蓋返回地址。首先看以下程序:

在上述程序代碼中,第一個(gè)sprintf()利用“%400s”指定了打印數(shù)據(jù)的長度,因此不會產(chǎn)生溢出。但在第二個(gè)sprintf()中,程序員將本應(yīng)出現(xiàn)在第三個(gè)參數(shù)位置的buffer參數(shù)放在第二個(gè)參數(shù)的位置,而buffer的部分內(nèi)容是由用戶提供的,這就導(dǎo)致了安全隱患?,F(xiàn)在,我們給argv[1]提供如下形式的字符數(shù)據(jù):

%102dAAAA

此時(shí),單步調(diào)試該程序,其中主要的參數(shù)信息如圖12-9所示。

在第一個(gè)sprintf()函數(shù)調(diào)用執(zhí)行完以后,buffer數(shù)組中的值如圖12-10所示,其組成如下:

(1)長19個(gè)字節(jié)的字符串“ERRWrongcommand:”。

(2)長400個(gè)字節(jié)的填充,其中最后9個(gè)字節(jié)來自argv[1],也就是“%102dAAAA”,剩下是391個(gè)空格。

(3)一個(gè)結(jié)束字符。

(4)剩下92個(gè)數(shù)組元素沒有變化,仍然是0xCC。

此時(shí)對buffer數(shù)組的操作并沒有越界。

圖12-9堆棧布局

等執(zhí)行第二個(gè)sprintf()函數(shù)調(diào)用,也就是要把buffer數(shù)組內(nèi)容輸出到outbuf數(shù)組中,

此時(shí):

(1)圖12-10中,%102d(方框標(biāo)注部分)之前的元素總共410個(gè)字節(jié)原封不動(dòng)的拷貝給outbuf。

(2)

sprintf()在碰到%102d以后,在outbuf數(shù)組后繼續(xù)添加102個(gè)空格。

(3)再拷貝4個(gè)大寫A字符給outbuf數(shù)組。

(4)再添加結(jié)束字符‘\0’。

圖12-10buffer數(shù)組

顯然,在前兩步完成以后,已經(jīng)往outbuf數(shù)組拷貝了512個(gè)字節(jié),因此在第三步所拷貝的4個(gè)大寫A字符已經(jīng)超出outbuf數(shù)組范圍,從而覆蓋到了圖12-9所示的“保存的EBP”值,導(dǎo)致溢出。所以,如果繼續(xù)加大格式控制符寬度值,也就是把%102d改為%106的,則可以覆蓋到返回地址,徹底控制程序執(zhí)行流程。

除了開始的“%102d”外,這和前一章講述的緩存溢出漏洞完全一樣。因此,我們也可以使程序返回,跳轉(zhuǎn)到由我們提供的地址處,從而執(zhí)行我們提供的shellcode。

第二種實(shí)現(xiàn)內(nèi)存覆蓋的方法是利用‘%n’格式控制符,其作用是把已經(jīng)打印輸出的字符個(gè)數(shù)值寫入某個(gè)地址,如:

上述代碼將輸出“i=6”。利用%n格式控制符就可以同前面的輸出任意地址的內(nèi)容所采用的方法一樣,往任意地址寫入數(shù)據(jù)。

以下述程序?yàn)槔?/p>

這次我們給buf提供的數(shù)據(jù)為:“Writetothememory:abcd%d…%d…%d%n”;Windows系統(tǒng)下,在執(zhí)行printf()函數(shù)調(diào)用時(shí),堆棧的布局如圖12-11所示。

圖12-11堆棧示意圖

printf()函數(shù)執(zhí)行時(shí),將首先打印輸出“Writetothememory:abcd”,然后從調(diào)用參數(shù)的下方,即0x0012feb4,依次根據(jù)%d格式開始打印,只要有足夠多的%d,就能一直覆蓋到buf數(shù)組內(nèi)的元素。當(dāng)控制符到最后一個(gè)%n時(shí),如果剛好對應(yīng)abcd,那么就會以abcd為目的地址寫入數(shù)據(jù)。如果將abcd換成保存EIP的地址,調(diào)整打印長度使長度值等于存放shellcode的地址,則函數(shù)返回時(shí)就跳轉(zhuǎn)到shellcode去執(zhí)行。

一般來說,shellcode的地址是一個(gè)比較大的值。如果用%d來打印,為使打印長度達(dá)到這個(gè)地址值會使格式化字符串非常長。因此我們利用格式參數(shù)中的打印寬度即最后的格式參數(shù)為‘%nu’的形式:

這樣,我們就可以覆蓋返回地址了。但是在有些系統(tǒng)中,這種方法不一定有效。這是因?yàn)檫@些系統(tǒng)對n的值有限制,不允許打印超過一定值的0。為了實(shí)現(xiàn)上述目的,我們將地址分4次寫入存儲了返回地址的內(nèi)存中。在x86中整數(shù)以小端順序存放在4個(gè)字節(jié)中,如0xbfffd33c在內(nèi)存中為“\x3c\xd3\xff\xbf”。從而執(zhí)行下面代碼:

后,exp[0]中存放的是‘\x40’,即64。為了將地址寫入內(nèi)存中,我們采取類似于下面的代碼:

為了能更好的理解上述結(jié)果,我們來看一下addr[]和canary[]兩個(gè)數(shù)組元素的變化過程,如圖12-12所示。

圖12-12堆棧示意圖

上圖中,第1列是addr[],第二列是canary[]。第0行是在向內(nèi)存寫數(shù)據(jù)前的情形,此時(shí)addr[]全0,canary[]前4個(gè)字節(jié)置為‘A’。從第一行開始,我們依次進(jìn)行了4次覆蓋,每次覆蓋向右移一個(gè)字節(jié)。第5行是覆蓋后的結(jié)果。從圖中可以看出,我們成功的改寫了addr[],但是也同時(shí)破壞了canary[]的數(shù)據(jù)。

上面我們是每一個(gè)格式化串字寫一次數(shù)據(jù),我們還可以利用一個(gè)格式化串寫多次數(shù)據(jù):

printf(“%16u%n%16u%n%32u%n%64u%n”,

a,(int*)&addr[0],a,(int*)&addr[1],a,(int*)&addr[2],a,(int*)&addr[3]);

在這里要注意的一點(diǎn)是:第二個(gè)‘%n’前雖然是‘%16u’,打印長度只有16,但是在第一個(gè)‘%n’前已經(jīng)打印了16個(gè)字節(jié),因此將向addr[1]中寫入32。

我們已經(jīng)提到,上述的寫數(shù)據(jù)方式會破壞其它數(shù)據(jù)。為避免這個(gè)問題,我們可以采用‘%hn’格式參數(shù)?!甴’要求‘%n’以短整數(shù)的形式寫入內(nèi)存中。因此語句:

printf(“%.29010u%hn%.32010%hn”,a,(shortint*)&addr[0],a,(shortint*)&addr[2]);

將不會破壞其它數(shù)據(jù)。

12.2.4安全建議

從上面的分析可以知道,格式化字符串漏洞是源于函數(shù)接受了不可信源的數(shù)據(jù)并把這些數(shù)據(jù)作為格式化字符串。當(dāng)程序中存在:

類似語句時(shí)可能導(dǎo)致格式化字符串漏洞。為了避免格式化字符串漏洞,在編程時(shí)應(yīng)采用如下正確的使用方式:

更一般來說,當(dāng)采用:

方式時(shí),就可以使函數(shù)不再解析用戶數(shù)據(jù)中的格式化控制符,從而使程序更安全。

目前有很多相關(guān)工具可以對非信任源提供的字符串進(jìn)行分析掃描,如詞法分析工具pscan[9]可以按照下述規(guī)則:

如果函數(shù)的最后一個(gè)參數(shù)是格式字符串且不是靜態(tài)字符串,則給出報(bào)告。

進(jìn)行判斷。

Shankar[10]則擴(kuò)展現(xiàn)有的C語言類型系統(tǒng),提出利用附加的類型修飾符(TypeQualifier)把來自非信任源的輸入標(biāo)記為污點(diǎn)(Tainted),而且由污點(diǎn)源衍生出來的數(shù)據(jù)同樣會被標(biāo)記為污點(diǎn)。對于那些試圖將污點(diǎn)數(shù)據(jù)解釋為格式字符串的操作將給出警告。例如,對于以下演示代碼:

getchar()函數(shù)的返回值以及主函數(shù)的命令行參數(shù)argv[]都被標(biāo)記為污點(diǎn),如果任何污點(diǎn)類型的表達(dá)式被用作格式字符串,則用戶將會被警告程序中存在的潛在安全漏洞。

另一種防范格式化字符串漏洞的策略是通過動(dòng)態(tài)地改變C運(yùn)行環(huán)境、編譯器或者庫函數(shù)來實(shí)現(xiàn)的。如FormatGuard[11]是一個(gè)編譯器修改器,通過插入代碼實(shí)現(xiàn)動(dòng)態(tài)檢測,并且拒絕參數(shù)個(gè)數(shù)與格式轉(zhuǎn)換字符個(gè)數(shù)不匹配的格式化函數(shù)調(diào)用。但是應(yīng)用程序必須使用FormatGuard重新進(jìn)行編譯。

12.3整數(shù)安全

整數(shù)在程序設(shè)計(jì)中是必比不可少的重要組成部分,編程語言也為程序設(shè)計(jì)人員提供了不同長度的整數(shù)類型,如C語言的short

int、int和long

int。但是我們知道,整數(shù)在計(jì)算機(jī)中的表示是有范圍限制的,不管是哪種類型表示的整數(shù)總有一定的范圍,超出其范圍時(shí)我們稱為整數(shù)的溢出。

整數(shù)溢出是一種軟件行為,導(dǎo)致的原因是數(shù)字運(yùn)算的結(jié)果超出了系統(tǒng)所能處理的范圍。當(dāng)一個(gè)數(shù)字運(yùn)算得出了一個(gè)系統(tǒng)位寬無法存儲的大結(jié)果時(shí),該結(jié)果會被截取,會得到異常的結(jié)果值,這個(gè)溢出的值可以被用來實(shí)現(xiàn)一個(gè)關(guān)鍵的操作,諸如數(shù)組索引、內(nèi)存分配或內(nèi)存廢棄等。這類行為不僅可以讓程序崩潰,而且還可以被黑客利用來訪問系統(tǒng)中的特權(quán)內(nèi)存內(nèi)容。

查詢以往的漏洞公告,我們會發(fā)現(xiàn)關(guān)于整數(shù)溢出方面的漏洞非常多。如Windows系統(tǒng)平臺下的OutlookExpress、WindowsMail、MozillaFirefox、MicrosoftWindowsGDIWMF文件解析整數(shù)溢出漏洞、Windows資源管理器PNG圖形整數(shù)溢出漏洞、PuTTYSFTP客戶端包解析整數(shù)溢出漏洞等等,整數(shù)溢出已經(jīng)成為C和C++程序中漏洞的源泉。

12.3.1整數(shù)

計(jì)算機(jī)中的整數(shù)可以分為“不帶符號的整數(shù)”(或稱為正整數(shù))和“帶符號的整數(shù)”兩類。它們可以用8位、16位、32位甚至是64位來表示。

無符號整數(shù)可以直接用其二進(jìn)制來表示,而有符號整數(shù)表示通常采用:原碼(Sign-and-magnitude)、反碼(Ones'complement)、補(bǔ)碼(Two'scomplement)表示法。

原碼表示法利用最高位表示數(shù)的符號位,0表示正,1表示負(fù),而剩下的比特位表示該數(shù)的絕對值。

反碼表示法對于正數(shù),和原碼編碼一樣,對于負(fù)數(shù),它恰好等于對應(yīng)正數(shù)編碼取反(應(yīng)該是因此而取名為反碼)。

補(bǔ)碼表示法對于正數(shù),編碼與原碼(反碼)一樣,對于負(fù)數(shù),它等于反碼+1。

例如,8位整數(shù)-64的三種表示方法如下:

(-64)原?=?11000000;

(-64)反?=?10111111;

(-64)補(bǔ)?=?11000000;

不管是哪種表示方法,整數(shù)都有其表示范圍。

帶符號整型用來表示正值和負(fù)值,值的范圍由該類型所占數(shù)據(jù)位數(shù)和編碼技術(shù)決定。在一個(gè)使用補(bǔ)碼表示法的計(jì)算機(jī)上,帶符號整數(shù)的取值范圍是-2n-1到2n-1-1。

無符號整數(shù)的取值范圍是0到2n-1,其中n都代表該類型所占的位數(shù)大小。

12.3.2整數(shù)類型轉(zhuǎn)換

C語言規(guī)定,不同類型的數(shù)據(jù)需要轉(zhuǎn)換成同一類型后才可進(jìn)行計(jì)算。數(shù)據(jù)類型轉(zhuǎn)換有兩種形式,即隱式類型轉(zhuǎn)換和顯示類型轉(zhuǎn)換。

所謂隱式類型轉(zhuǎn)換就是在編譯時(shí)由編譯程序按照一定規(guī)則自動(dòng)完成,而不需人為干預(yù)。因此,在表達(dá)式中如果有不同類型的數(shù)據(jù)參與同一運(yùn)算時(shí),編譯器就在編譯時(shí)自動(dòng)按照規(guī)定的規(guī)則將其轉(zhuǎn)換為相同的數(shù)據(jù)類型。

C語言規(guī)定的隱式轉(zhuǎn)換原則是由低級向高級轉(zhuǎn)換,以數(shù)據(jù)安全為第一要?jiǎng)t。例如,如果一個(gè)操作符帶有兩個(gè)類型不同的操作數(shù)時(shí),那么在操作之前應(yīng)先將較低的類型轉(zhuǎn)換為較高的類型,然后進(jìn)行運(yùn)算,運(yùn)算結(jié)果是較高的類型。

和整數(shù)有關(guān)的轉(zhuǎn)換規(guī)則有:

(1)字符型必須先轉(zhuǎn)換為整數(shù)型,short型必須轉(zhuǎn)換為int型,稱為整型提升。

(2)只能從較小的整數(shù)類型隱式地轉(zhuǎn)換為較大的整數(shù)類型,不能從較大的整數(shù)類型隱式地轉(zhuǎn)換為較小的整數(shù)類型。

(3)如果無符號整型操作數(shù)的級別大于或等于另一個(gè)操作數(shù)類型的級別,則帶符號整型操作數(shù)將被轉(zhuǎn)換為無符號整型操作數(shù)的類型。例如,下述代碼的執(zhí)行結(jié)果并不會打印OK!

(4)無符號的整型操作數(shù)變量可以轉(zhuǎn)換為有符號的整型,只要無符號操作數(shù)的大小在有符號操作數(shù)類型的范圍之內(nèi)。

(5)賦值時(shí),一律是賦值運(yùn)算符右邊值按照左邊類型進(jìn)行賦值轉(zhuǎn)換。

顯式類型轉(zhuǎn)換顯示類型轉(zhuǎn)換又叫強(qiáng)制類型轉(zhuǎn)換,它不是按照前面所述的轉(zhuǎn)換規(guī)則進(jìn)行轉(zhuǎn)換,而是直接將某數(shù)據(jù)轉(zhuǎn)換成指定的類型。例如:

12.3.3整數(shù)溢出漏洞

所謂漏洞就是一系列允許違反顯式或者隱式安全策略的情形。整數(shù)溢出并不直接的改寫內(nèi)存或者直接改變程序的控制流程,相比普通的漏洞利用要巧妙的多。其問題根源在于對整數(shù)的計(jì)算結(jié)果很難進(jìn)行判斷對錯(cuò),因此對于攻防雙方來說都不容易發(fā)現(xiàn)這種漏洞。但還是有很多情形,我們可以強(qiáng)迫一個(gè)變量包含錯(cuò)誤的值,從而導(dǎo)致后續(xù)代碼出現(xiàn)安全問題。

接下來我們就一些比較典型的由整數(shù)錯(cuò)誤運(yùn)算邏輯所導(dǎo)致的漏洞進(jìn)行分析探討。

1.無符號整數(shù)的下溢和上溢

無符號整數(shù)的下溢問題是由于無符號整數(shù)不能識別負(fù)數(shù)所導(dǎo)致的。示例代碼如下:

在上述代碼中,在調(diào)用new分配內(nèi)存后,程序未對函數(shù)調(diào)用結(jié)果的正確性進(jìn)行判斷。如果cbSize取值為0的話,則cbSize–1等于-1。但是memset()函數(shù)中第三個(gè)參數(shù)本身是無符號數(shù),因此會將-1視為正的0xffffffff,函數(shù)執(zhí)行之后程序崩潰。

無符號整數(shù)的上溢問題也不難理解,示例代碼如下:

本例子中代碼看起來沒什么問題,該檢測的地方也都檢測了。但這段代碼卻可能出現(xiàn)整數(shù)上溢問題,len1和len2都是無符號整數(shù),如果len1=8,len2=0xffffffff,由于加操作的限制,8+0xffffffff+1產(chǎn)生的結(jié)果是8。也就是說,new運(yùn)算只分配8個(gè)字節(jié)的內(nèi)存,而后面卻要進(jìn)行多達(dá)0xffffffff的字符串拷貝操作,結(jié)果導(dǎo)致程序崩潰。

2.符號錯(cuò)誤

符號錯(cuò)誤問題可以是多種多樣的,可能是有符號整數(shù)之間的比較、或者是有符號整數(shù)的運(yùn)算、也可以是無符號整數(shù)和有符號整數(shù)的對比所引起的。

這里舉一個(gè)典型的例子:

上面代碼的問題在于memcpy()函數(shù)的第三個(gè)參數(shù)len,其類型是無符號整數(shù)。但是在之前的數(shù)據(jù)邊界檢測卻使用了有符號整數(shù)。假設(shè)提供一個(gè)負(fù)數(shù)給len,這樣可以繞過[1]的范圍檢測,但是這個(gè)值卻被使用在[2]的memcpy()函數(shù)的參數(shù)里面。由于負(fù)數(shù)len被轉(zhuǎn)換成一個(gè)非常大的正整數(shù),導(dǎo)致szBuf緩沖區(qū)后面的數(shù)據(jù)被重寫,進(jìn)而使得程序崩潰。

以下是NetBSD1.4.2及之前的版本所使用的范圍檢查代碼:

代碼中的變量off和len都是有符號整型,而sizeof運(yùn)算符返回的則是一個(gè)無符號整型,因此按照前面的類型轉(zhuǎn)換規(guī)則,len-sizeof(type-name)都應(yīng)當(dāng)轉(zhuǎn)換成無符號整型進(jìn)行計(jì)算。當(dāng)len小于sizeof(type-name)時(shí),減法操作造成下溢,得到一個(gè)很大的正值,從而使得上述if判斷邏輯失效。

3.截?cái)嗟膯栴}

截?cái)鄦栴}主要發(fā)生在大范圍整數(shù)(如32位)拷貝給小范圍整數(shù)(如16位)的時(shí)候。同樣來看以下代碼:

如果cbBuf是0x00010020,那么無符號短整型變量cbCalculatedBufSize取值只有0x20,因?yàn)橹粡?x00010020復(fù)制了低16位。因此,給buf僅分配了0x20個(gè)字節(jié)內(nèi)存空間,由此在memcpy()函數(shù)調(diào)用中將0x00010020個(gè)字節(jié)復(fù)制到新分配的目標(biāo)緩沖區(qū)中,導(dǎo)致溢出。

如果整數(shù)溢出發(fā)生,之后的所有相關(guān)操作的結(jié)果都將發(fā)生變化。與緩沖區(qū)溢出不同的是,整數(shù)溢出發(fā)生時(shí)不會馬上發(fā)生異常,即使程序執(zhí)行結(jié)果與預(yù)期的不同,也很不容易發(fā)現(xiàn)問題所在。

前面提到,整數(shù)溢出在很多時(shí)候會導(dǎo)致緩沖區(qū)溢出漏洞的發(fā)生,包括堆棧溢出和堆溢出。但并不是所有由整數(shù)溢出導(dǎo)致的緩沖區(qū)溢出都是可以利用的。相反,大多數(shù)情況是不能利用的,原因就在于其中涉及到諸如近乎4G這樣的大內(nèi)存操作,會發(fā)生段錯(cuò)誤。

12.3.4安全建議

所有的整數(shù)溢出漏洞都是由整數(shù)范圍錯(cuò)誤所導(dǎo)致的。因此,防止整數(shù)漏洞的第一道防線就是進(jìn)行范圍檢查,可以顯式進(jìn)行,也可以通過使用強(qiáng)類型達(dá)成。很多整數(shù)輸入都定義有明確的范圍,或者擁有一個(gè)合理的上下限。例如可以限制一個(gè)代表人的年齡的變量其取值范圍在0~150之間。

另一種解決方案就是利用整數(shù)安全庫,確保整數(shù)運(yùn)算不受非信任源的數(shù)據(jù)影響,如SafeIntC++類庫和RCSint庫。

一如既往的利用各種工具、過程和技術(shù)來發(fā)現(xiàn)和防止整數(shù)漏洞也是非常有意義的。靜態(tài)分析和代碼審核對于發(fā)現(xiàn)各類安全錯(cuò)誤同樣非常有效。

12.4條件競爭

當(dāng)一個(gè)安全程序中存在非原子方式運(yùn)行的調(diào)用時(shí)就會出現(xiàn)所謂的條件競爭。本節(jié)將分析一個(gè)實(shí)際的例子來說明條件競爭并介紹如何避免條件競爭。

12.4.1用戶ID

為了更好的理解條件競爭,我們首先介紹UNIX系統(tǒng)中的用戶ID。

1.UserID

每個(gè)用戶都被賦予一個(gè)唯一的號,簡稱UID(UserID)。UID0屬于根root,任何以UID0工作的用戶將不會有任何的安全限制。普通用戶的UID值一般從100開始,如500。所有小于此值的用戶ID屬于系統(tǒng)進(jìn)程。用戶的UID數(shù)值越小代表其權(quán)限越高。

2.GroupID

Unix系統(tǒng)有多個(gè)組。如管理員屬于root組或一般用戶wheel組。用戶一般隸屬于創(chuàng)建此用戶的用戶組或標(biāo)志其行為的組,如fb4、wwwadmin、students、accounting等。一個(gè)用戶可屬于1~NGROUPS_MAX(32)個(gè)組。

3.SetUID和SetGID

通常,有些程序需要更高的權(quán)限以完成某一任務(wù),例如普通用戶在改變自己的口令時(shí),需要往口令文件寫入新的口令值,而通常口令字文件只有超級用戶才有權(quán)限這么做,這時(shí)要么由擁有權(quán)限的用戶(如超級用戶)來執(zhí)行,要么通知系統(tǒng),程序在執(zhí)行時(shí)需要分配一定的權(quán)限。前一種方法非常費(fèi)力并很不安全(每次更改自己的口令都要超級用戶在場),所以要么所有人都知道該口令(即一般用戶都知道超級用戶的口令),要么知道口令的人(超級用戶)不得不為了另外的用戶完成任務(wù)而登錄系統(tǒng)。

為了實(shí)現(xiàn)這種目的,Unix文件系統(tǒng)實(shí)現(xiàn)有兩個(gè)文件隊(duì)列。如果某個(gè)程序帶有SetUID標(biāo)志,則表示調(diào)用該程序的其它程序(如普通權(quán)限用戶)可以暫時(shí)獲得該程序所屬用戶的(有效)UID,而不是執(zhí)行該程序用戶的UID(真實(shí)UID)。這同樣適用于SetGID標(biāo)志。

4.真實(shí)UID/GID和有效UID/GID

真實(shí)UID是用戶的UID。如,用戶Thomas的UID為543,說明Thomas是一般用戶。現(xiàn)在如果他執(zhí)行一個(gè)來自root用戶的SetUID程序,則程序的有效UID為0,真實(shí)UID為543。

12.4.2條件競爭

當(dāng)特權(quán)應(yīng)用進(jìn)程打開文件時(shí),系統(tǒng)需要首先檢查用戶是否有資格打開文件。很多情況下會采用如下錯(cuò)誤的使用方式:

在上面的代碼段中,access(?)調(diào)用利用用戶的真實(shí)UID和真實(shí)GID,而不是SetUID或SetGID程序的有效UID/GID來檢驗(yàn)用戶對目標(biāo)的訪問權(quán)限。但是,open(?)調(diào)用確是使用用戶的有效UID/GID來檢驗(yàn)用戶的訪問權(quán)限。在access(?)和open(?)調(diào)用之間存在一段時(shí)間。這樣,攻擊者就可以在access(?)調(diào)用后刪除文件/home/long/vulfile,然后讓它與文件/etc/security/shadow建立鏈接。從而當(dāng)調(diào)用open(?)時(shí)就會通過鏈接打開文件/etc/security/shadow而不是文件/home/long/vulfile。至此,攻擊者就可以處理它本不能訪問的數(shù)據(jù)。

從上面的分析可以得到整個(gè)攻擊情景:用戶long對文件/home/long/vulfile具有寫權(quán)限。程序中通過access("/home/attacker/vulfile",W_OK)檢查了用戶的寫權(quán)限。這時(shí),long刪除了文件/home/long/vulfile,然后與一個(gè)long沒有寫權(quán)限的文件設(shè)置了鏈接。這樣,open(2)根據(jù)鏈接就打開了被保護(hù)的文件。

12.4.3安全建議

針對上面的安全隱患,在編程時(shí)可采取以下幾種方法:

1.使用faccess()

缺點(diǎn):

(1)很多操作系統(tǒng)并沒有實(shí)現(xiàn)faccess()。

(2)在faccess()前需首先調(diào)用open(2)。在UNIX系統(tǒng)中,open(2)可用于打開設(shè)備文件。在打開設(shè)備文件時(shí)可能已經(jīng)對相應(yīng)的設(shè)備執(zhí)行了某種操作。如:當(dāng)打開磁帶設(shè)備文件時(shí),磁帶必須倒帶;在反復(fù)備份的情況下就有可能導(dǎo)致先前備份數(shù)據(jù)的丟失。

2.使用標(biāo)志位O_NOFOLLOW

在open(2)調(diào)用中使用選項(xiàng)O_NOFOLLOW可以禁止根據(jù)符號鏈接來打開文件。

缺點(diǎn):攻擊者可以采用硬鏈接來達(dá)到目的。

3.使用fork()

唯一可移植的、也是最安全的方法就是使用fork(2)產(chǎn)生子進(jìn)程。子進(jìn)程永久的放棄特權(quán),然后打開文件,在將文件描述符返回給父進(jìn)程后終止子進(jìn)程。

12.5臨時(shí)文件

在編程過程中,還應(yīng)注意的一個(gè)問題是關(guān)于臨時(shí)文件。對臨時(shí)文件的錯(cuò)誤處理方式也將導(dǎo)致安全問題。

一般來說,臨時(shí)文件是任何人都可寫的目錄(在UNIX下是/tmp、/var/tmp)。UNIX系統(tǒng)可以通過設(shè)置文件屬性(chmodo+t/tmp:表示用戶只能移動(dòng)自己擁有的文件)來防止刪除公共目錄下的文件。但是設(shè)置的文件屬性并不能提供對子目錄的保護(hù)。也就是說,如果任何人都可以對子目錄執(zhí)行寫操作,那么所有人就都可以修改這個(gè)目錄下的所有文件。因此,從安全角度來講,必須對每個(gè)子目錄明確的設(shè)置文件屬性。

除了對文件的非法刪除外,對于臨時(shí)文件還存在的安全隱患就是上一節(jié)講到的條件競爭或者說易受到鏈接攻擊。下面是兩種生成臨時(shí)文件方法的代碼段:

由于這兩種方法都是以非原子方式運(yùn)行并遵循符號鏈接,因此,二者都是不安全的。上面代碼中的tmpnam(3)、tempnam(3)及mktemp(3)只保證產(chǎn)生的文件名在調(diào)用時(shí)不存在。但是在生成文件名和open(2)之間,攻擊者可以為生成的文件名創(chuàng)建鏈接,從而導(dǎo)致條件競爭。

為了避免對臨時(shí)文件操作時(shí)的條件競爭,可以采用如下方法:

系統(tǒng)調(diào)用mkstemp(3)可以生成唯一的文件名并以安全的方式打開這個(gè)文件。可是mkstemp(3)不是POSIX標(biāo)準(zhǔn)(BSD4.3),而且舊版本的mkstemp會設(shè)置文件的訪問權(quán)限為0666,也就是說,任何人都可以對文件進(jìn)行讀寫操作。為了使代碼更具移植性,可以采用如下方法:

12.6動(dòng)態(tài)內(nèi)存分配和釋放

12.6.1背景知識

首先要說明的是本節(jié)所述的動(dòng)態(tài)內(nèi)存分配和釋放都是基于Linux系統(tǒng)實(shí)現(xiàn)的。

malloc/calloc/realloc/free是用來分配和釋放動(dòng)態(tài)內(nèi)存的。malloc()定義一個(gè)內(nèi)部結(jié)構(gòu)malloc_chunk來定義分配和釋放的內(nèi)存塊:

structmalloc_chunk

{

根據(jù)這個(gè)結(jié)構(gòu),我們可以得到內(nèi)存塊的結(jié)構(gòu)圖如圖12-13所示。

圖12-13內(nèi)存塊結(jié)構(gòu)圖

當(dāng)分配內(nèi)存時(shí),malloc()函數(shù)返回給用戶mem指針(即chunk+8),而chunk指針只是malloc()內(nèi)部使用(此時(shí)沒有使用fd和bk指針)。因此,當(dāng)用戶要求分配size字節(jié)內(nèi)存時(shí),實(shí)際上分配了至少size+8個(gè)字節(jié),只是用戶可用的只有size字節(jié)而已。當(dāng)內(nèi)存塊為空閑(或已被釋放)時(shí),內(nèi)存塊通過一個(gè)雙向循環(huán)鏈表存放,fd和bk分別是雙向鏈表的前向和后向指針。

從上圖可以看到,“當(dāng)前塊大小size”的最低位是一個(gè)“P”標(biāo)志。當(dāng)P位置1表示上一內(nèi)存塊正在使用,此時(shí),prev_size一般為0;如果P位清零表示上一內(nèi)存塊空閑,prev_size填充的是上一內(nèi)存塊的大小。標(biāo)志位M表示此內(nèi)存塊是否由mmap()分配的。

接下來看free(mem)如何釋放內(nèi)存塊。首先將mem轉(zhuǎn)換為chunk(mem-8),并調(diào)用chunk_free()來釋放chunk所指的內(nèi)存塊。接下來程序檢查其相鄰的內(nèi)存塊是否空閑:如果是空閑的,則將相鄰的空閑內(nèi)存塊合并;如果不是,就只設(shè)置后一個(gè)相鄰塊的prev_size和size(清PREV_INUSE標(biāo)志)。最后將得到的空閑塊加入雙向鏈表中。

在釋放內(nèi)存過程中,有一步是合并相鄰的空閑內(nèi)存塊。為了完成合并,在合并之前需要先將相鄰的空閑塊從鏈表中刪除。這是通過宏unlink來實(shí)現(xiàn):

12.6.2安全隱患

從上面的討論可以看出,當(dāng)釋放動(dòng)態(tài)內(nèi)存時(shí)可能會調(diào)用unlink宏。而unlink宏中有兩個(gè)寫內(nèi)存的操作??梢酝ㄟ^使用函數(shù)指針、返回地址等覆蓋指針fd和bk,從而改變程序的流程。

為了保證程序的安全,應(yīng)該保證free()調(diào)用時(shí)釋放的內(nèi)存塊確實(shí)是已分配的。

參考文獻(xiàn)

[1]CrispinCowan,PerryWagle,CaltonPu.BufferOverflows:AttacksandDefensesfortheVulnerabilityoftheDecade.DARPAInformationSurvivabilityConferenceandExpo(DISCEX),Jan2000

[2]ThomasBiege.Security-specificProgrammingErrors.may2001./en/content.php?&content/security/

[3]JasonJordan.WindowsNTBufferOverflow‘sFromStarttoFinish.

http://www.technotronic.com/jason/bo.html

[4]AvayaLabs,Libsafe.

[5]MichaelHoward,Strsafe.h:SaferStringHandlinginC,/en-us/library/ms995353.aspx

[6]JonesRW,KellyP.Backwards-compatibleboundscheckingforarraysandpointersinCprograms.ThirdIntl.Workshoponautomateddebugging,1997

[7]CrispinCowan,CaltonPu,DaveMaier.StackGuard:AutomaticAdaptiveDetectionandPreventionofBuffer-OverflowAttacks.7thUSENIXSecurityConference,pages63–77,SanAntonio,TX,Jan1998

[8]CowanC,BeattieS,JohansenJ,etal.PointGuard:ProtectingPointersFromBufferOverflowVulnerabilites.The12thUSENIXSecuritySymposium,WashingtonDC,2003

[9]AlanDeKok.PScan:AlimitedproblemscannerforCsourcefiles.

/pscan/

[10]UmeshShankar,KunalTalwar,JeffreyS.FosterandDavidWagnerDetectingFormatStringVulnerabilitieswithTypeQualifiers.Proceedingsofthe10thconferenceonUSENIXSecuritySymposium,August13-17,2001,pp:201-218

[11]CowanC,BarringerM,BeattieS,etal.FormatGuard:AutomaticProtectionfromprintfFormatStringVulnerabilities.InProceedingsoftheTenthUSENIXSecuritySymposium.Washington,DC,Aug

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論