劉浩+賀文華+彭智朝+賀勁松
摘要:緩沖區(qū)溢出是一種非常普遍、非常危險(xiǎn)的漏洞,常被黑客和病毒利用,是信息安全的重要隱患之一。因此,在“信息安全理論與技術(shù)”課程教學(xué)中,緩沖區(qū)溢出安全編程的教與學(xué)一直受師生們的重視?;诰彌_區(qū)溢出的工作原理與攻擊技術(shù),通過(guò)師生共同探討,給出了一些關(guān)于C語(yǔ)言程序編寫(xiě)過(guò)程中防御緩沖區(qū)溢出的方法,以提高安全編程能力。
關(guān)鍵詞:緩沖區(qū)溢出;堆棧;安全編程;Bss;heap
中圖分類(lèi)號(hào):TP309 文獻(xiàn)標(biāo)識(shí)碼:A 文章編號(hào):1009-3044(2017)14-0102-04
1概述
自從二十世紀(jì)末以來(lái),由于其破壞性大與廣泛性,緩沖區(qū)溢出漏洞得到了信息安全領(lǐng)域?qū)W者們的普遍關(guān)注。當(dāng)前,相關(guān)研究統(tǒng)計(jì)表明,全球每年發(fā)生的安全威脅事件以指數(shù)增長(zhǎng)。由于緩沖區(qū)溢出漏洞不受操作系統(tǒng)不同的限制、能作用于不同的應(yīng)用程序之上,作為網(wǎng)絡(luò)攻擊一種主要形式,緩沖區(qū)溢出攻擊次數(shù)超過(guò)了所有網(wǎng)絡(luò)系統(tǒng)攻擊總數(shù)的五分之四。
在程序設(shè)計(jì)中,緩沖區(qū)就是應(yīng)用程序用來(lái)保存用戶輸入數(shù)據(jù)和代碼的臨時(shí)數(shù)據(jù)的內(nèi)存空間。作為一種系統(tǒng)攻擊的手段,緩沖區(qū)溢出(Buffer Overflow),就是在程序中的緩沖區(qū)內(nèi)寫(xiě)入超出正常長(zhǎng)度的內(nèi)容,使緩沖區(qū)產(chǎn)生溢出,破壞程序的堆棧,讓程序跳轉(zhuǎn)去執(zhí)行別的指令,從而達(dá)到系統(tǒng)攻擊的目標(biāo)。
正常情況,利用緩沖區(qū)溢出漏洞攻擊者并不只是想讓程序本身崩潰,而是想通過(guò)這種攻擊來(lái)達(dá)到提升權(quán)限,獲得對(duì)系統(tǒng)更多的訪問(wèn)和控制權(quán)。一般而言,緩沖區(qū)溢出本身并不會(huì)導(dǎo)致系統(tǒng)安全問(wèn)題,但如果該溢出能夠跳轉(zhuǎn)到以超級(jí)權(quán)限(如root權(quán)限)運(yùn)行命令的區(qū)域,使程序去運(yùn)行一個(gè)shell或執(zhí)行某些特權(quán)代碼,那么該程序?qū)⒁猿?jí)用戶的權(quán)限控制了計(jì)算機(jī)系統(tǒng)。顯然,緩沖區(qū)溢出漏洞是計(jì)算機(jī)系統(tǒng)的重大安全隱患。緩沖區(qū)溢出的根本原因就是程序設(shè)計(jì)時(shí)沒(méi)有考慮用戶輸入?yún)?shù)與運(yùn)行邊界的相關(guān)檢查。
當(dāng)應(yīng)用程序運(yùn)行時(shí),其內(nèi)存中的映像被分為數(shù)據(jù)段、代碼段以及堆棧段三個(gè)部分。數(shù)據(jù)段包括運(yùn)行文件中的BSS Sec-tion與Data Section,其用于存放程序運(yùn)行的靜態(tài)變量與各種數(shù)據(jù)。代碼段就是運(yùn)行文件中的Text Section(圖1),其中包括只讀數(shù)據(jù)與運(yùn)行代碼。一般該段在內(nèi)存中被標(biāo)記為只讀,所有企圖修改該數(shù)據(jù)的指令都將引發(fā)一個(gè)Segmentation Violation錯(cuò)誤。
具體到C程序中,如圖1所示,.stack、.heap、.bss以及.data區(qū)都被分配在緩沖區(qū),并且和程序執(zhí)行流相關(guān)的函數(shù)返回地址、函數(shù)指針等數(shù)據(jù)結(jié)構(gòu)也會(huì)被分配在區(qū)域,因此,一旦發(fā)生到緩沖區(qū)溢出攻擊時(shí),控制程序執(zhí)行流的敏感數(shù)據(jù)結(jié)構(gòu)則有可能發(fā)生改變,將導(dǎo)致正在執(zhí)行的程序發(fā)生轉(zhuǎn)向,從而去執(zhí)行非法代碼。
依據(jù)程序數(shù)據(jù)在內(nèi)存中增長(zhǎng)方式的不同,緩沖區(qū)溢出可分堆溢出和棧溢出兩種情況。所謂堆溢出,就是數(shù)據(jù)分配從低地址向高地址方向增長(zhǎng),溢出點(diǎn)可能發(fā)生在.bss、.daat、.heap區(qū),基于堆溢出的攻擊有覆蓋分配在該區(qū)域的函數(shù)指針等。反之,棧溢出就是數(shù)據(jù)分配從高地址向低地址方向增長(zhǎng),溢出點(diǎn)發(fā)生在.stack區(qū),基于棧溢出的攻擊有覆蓋函數(shù)返回地址等。
在模塊化程序設(shè)計(jì)中,各種函數(shù)調(diào)用會(huì)經(jīng)常出現(xiàn),如調(diào)用Win32 API、C運(yùn)行庫(kù)等。并且編譯器幾乎都會(huì)將這些調(diào)用編譯為Call語(yǔ)句,執(zhí)行該指令時(shí),IP被設(shè)為調(diào)用函數(shù)的入口地此,調(diào)用后的返回地址會(huì)被壓入堆棧,而且針對(duì)函數(shù)調(diào)用帶有的局部變量與入口參數(shù),這些數(shù)據(jù)也會(huì)被編譯器生成為一些指令存入堆棧(也有通過(guò)寄存器傳遞的)。通常稱(chēng)因一個(gè)函數(shù)調(diào)用所導(dǎo)致必需在堆棧中存放的返回地址與相關(guān)數(shù)據(jù)等構(gòu)成一個(gè)堆棧幀(Stack Frame)。
2緩沖區(qū)溢出基本原理
緩沖區(qū)溢出攻擊的基本原理就是向程序中輸入超出正常長(zhǎng)度的內(nèi)容,由于越過(guò)緩沖區(qū)長(zhǎng)度界限造成緩沖區(qū)的溢出,程序的堆棧會(huì)被破壞而出現(xiàn)特殊的問(wèn)題,使程序運(yùn)行跳轉(zhuǎn)去執(zhí)行其它指令。
下面我們舉一個(gè)緩沖區(qū)溢出例子來(lái)說(shuō)明其基本原理:
首先我們看一下未執(zhí)行strcpy時(shí)(已經(jīng)調(diào)用函數(shù)funcl)堆棧中的情況(如圖2所示)。
該程序是一個(gè)典型的緩沖區(qū)溢出編碼錯(cuò)誤。并沒(méi)有經(jīng)過(guò)邊界檢查,函數(shù)將一個(gè)字符串拷貝至另一內(nèi)存區(qū)域。在執(zhí)行strcpy時(shí),256字節(jié)的‘A(ox41)被程序拷入buffer中,但是buffer的長(zhǎng)度只有16字節(jié),那么buffer后面的240字節(jié)內(nèi)容將被覆蓋掉,這些字節(jié)包括RET地址、EBP、large_string地址。字符‘A的十六進(jìn)制為0×41,因此函數(shù)的返回地址被變?yōu)榱?×41414141,明顯超出了程序的地址空間,所以系統(tǒng)將報(bào)“Segmentation Vio-lation”錯(cuò)誤。這就是所謂的緩沖區(qū)溢出。
3緩沖區(qū)溢出攻擊方式
當(dāng)非法用戶操作程序時(shí),若所進(jìn)行的操作超出了程序的運(yùn)行范圍,程序所用的數(shù)據(jù)會(huì)被添加到分配給該緩沖區(qū)內(nèi)存塊之外,將導(dǎo)致緩沖區(qū)溢出,這時(shí)候就會(huì)出現(xiàn)數(shù)據(jù)泄漏或侵占其它的數(shù)據(jù)空間。
1)向緩沖區(qū)寫(xiě)人超出正常長(zhǎng)度的字符。
如上例通過(guò)向緩沖區(qū)中寫(xiě)入超出正常長(zhǎng)度的字符來(lái)產(chǎn)生緩沖區(qū)溢出,導(dǎo)致程序崩潰。
2)攻擊者可用任意數(shù)據(jù)覆蓋堆棧中變量的內(nèi)容。
安全漏洞的一個(gè)經(jīng)典例子是基于口令的認(rèn)證,首先從本地?cái)?shù)據(jù)庫(kù)中讀取口令并存儲(chǔ)在本地變量中,然后用戶輸入口令,程序比較這兩個(gè)字符串,從而比較結(jié)果為二者相等。
3)覆蓋堆棧中保存的寄存器。
通過(guò)輸入超長(zhǎng)的字符從而覆蓋指令指針I(yè)P,攻擊者可以利用函數(shù)結(jié)尾的RET來(lái)執(zhí)行程序中的任意程序代碼。一般而言,不是利用程序本身的代碼,而是植入攻擊者自己的機(jī)器代碼(一般稱(chēng)之為Shellcode,即外殼代碼)。為此把機(jī)器代碼寫(xiě)到變量中并復(fù)制到堆棧中,把保存的IP地址改變?yōu)楣舸a的開(kāi)始地址。當(dāng)函數(shù)執(zhí)行完畢返回時(shí),RET從堆棧中獲得IP的值并寫(xiě)入CPU的IP寄存器,于是運(yùn)行攻擊代碼。
4)覆蓋函數(shù)指針以執(zhí)行第三方代碼。
攻擊者把機(jī)器代碼Shellcode放在一全局或本地變量或編程環(huán)境中,并使函數(shù)指針指向這段程序代碼。當(dāng)用函數(shù)指針調(diào)用函數(shù)時(shí),執(zhí)行的將不是函數(shù)代碼而是攻擊代碼。
4安全編程
1)編寫(xiě)正確的代碼
前面提到過(guò),解決緩沖區(qū)溢出問(wèn)題的第一步是,人們必須更加小心地進(jìn)行計(jì)算機(jī)的編程。程序員只要增加能夠處理過(guò)長(zhǎng)字符串的指令,就能夠防止對(duì)自己產(chǎn)品的攻擊。下面我們共同探討容易導(dǎo)致緩沖區(qū)溢出的系統(tǒng)調(diào)用,并給出正確的、安全的使用方法。
①gets(char*s)
本函數(shù)的功能是從標(biāo)準(zhǔn)輸入來(lái)讀入數(shù)據(jù)到靜態(tài)緩沖區(qū)中,有名的bug是Morris Internet Worm在fingerd中開(kāi)發(fā)的,利用此漏洞可通過(guò)網(wǎng)絡(luò)在計(jì)算機(jī)上執(zhí)行命令。正確的fgets(char *s,im size,F(xiàn)ILE*stream)使用方式是通過(guò)嚴(yán)格規(guī)定輸入數(shù)據(jù)長(zhǎng)度從而安全的讀取數(shù)據(jù)。如本例中通過(guò)使用sizeof(Array Buff)等指定數(shù)據(jù)長(zhǎng)度,如12字節(jié),fgets()將讀入1~12個(gè)字節(jié)并在最后加一個(gè)NULL字符。
然而,實(shí)際編程中程序員容易把整個(gè)if語(yǔ)句直接寫(xiě)成了i++的錯(cuò)誤編程方式。
如前所述,還有很多函數(shù)不進(jìn)行邊界檢查,包括scanf(3)、strcpy(3)/strcat(3)、getwd(3)等等,在此不再一一敘述,請(qǐng)同學(xué)們自行探索研究。
所謂靜態(tài)發(fā)現(xiàn)技術(shù),就是為了降低程序被攻擊的可能性,在程序設(shè)計(jì)過(guò)程中,根據(jù)一定的約束規(guī)則來(lái)發(fā)現(xiàn)源碼里潛在的漏洞之處,便于程序員發(fā)現(xiàn)并改進(jìn)。顯然使用靜態(tài)發(fā)現(xiàn)技術(shù),系統(tǒng)需要維護(hù)一個(gè)不斷更新的與漏洞有關(guān)的規(guī)則庫(kù)。靜態(tài)發(fā)現(xiàn)技術(shù)常用的工具有BOON、Flawfinder、ITS4、RAST等。
2)使用Libsafe
朗訊技術(shù)公司的Arash Baratloo、Timothy Tsai和Navjot Singh等針對(duì)這些易受緩沖區(qū)溢出攻擊的Libc函數(shù)進(jìn)行二次開(kāi)發(fā),開(kāi)發(fā)出了封裝這些庫(kù)函數(shù)的動(dòng)態(tài)載入庫(kù)Libsafe[4],來(lái)解析那些不安全的Libc庫(kù)函數(shù),并用Libsafe中實(shí)現(xiàn)的安全函數(shù)替代,讓Libsafe實(shí)現(xiàn)邊界檢查,以達(dá)到確保任一緩沖區(qū)溢出都被控制在堆棧幀之內(nèi),從而保證了代碼的安全,解決了緩沖區(qū)溢出攻擊問(wèn)題。
3)不可執(zhí)行的緩沖區(qū)技術(shù)
根據(jù)緩沖區(qū)溢出的基本原理,所謂不可執(zhí)行的緩沖區(qū)技術(shù),就是使可能被攻擊程序的數(shù)據(jù)段地址空間不可執(zhí)行,植入到被攻擊程序輸入緩沖區(qū)中的代碼不可能被非法用戶執(zhí)行。
合法程序并不需要在堆棧中存放可執(zhí)行代碼,因此完全可以讓操作系統(tǒng)使程序的堆棧段不可執(zhí)行。目前,Solaris與Linux為此發(fā)布了安全補(bǔ)丁。正常情況下,合法程序幾乎都不會(huì)在堆棧中存放代碼,那么這種做法也就不會(huì)產(chǎn)生有關(guān)兼容性方面的問(wèn)題。然而Linux系統(tǒng)中有特例的情況,其可執(zhí)行的代碼必須被存放在堆棧中,在此不再一一敘述,請(qǐng)同學(xué)們自行探索研究。
不可執(zhí)行緩沖區(qū)技術(shù)能夠有效地抑制把代碼植入自動(dòng)變量的緩沖區(qū)溢出攻擊,但是對(duì)于其它形式的攻擊卻無(wú)效果。
4)數(shù)組邊界檢查
根據(jù)緩沖區(qū)溢出的基本原理可知,要實(shí)現(xiàn)緩沖區(qū)溢出攻擊則需要改變程序的執(zhí)行流程,使程序代碼不按約定的流程執(zhí)行。如果給局部變量分配的內(nèi)存空間沒(méi)被溢出,改變程序運(yùn)行狀態(tài)也就無(wú)從談起。為此,我們可以利用一些編譯器或工具對(duì)程序進(jìn)行數(shù)組邊界檢查,就是在對(duì)數(shù)組進(jìn)行讀寫(xiě)操作時(shí),必須將對(duì)數(shù)組的操作控制在正確的內(nèi)存范圍內(nèi)。最簡(jiǎn)單的方法就是檢查所有對(duì)數(shù)組的操作。當(dāng)前,Paul Kelly與Richard Jones聯(lián)合開(kāi)發(fā)的GCC補(bǔ)丁、Purify以及Compaq C編譯器等都能實(shí)現(xiàn)對(duì)數(shù)組邊界的檢查功能。
5)程序指針完整性檢查
相對(duì)于邊界檢查,所謂程序指針完整性檢查,就是在程序指針被引用之前檢測(cè)它是否有改變。若非法用戶改變了程序的指針,并且系統(tǒng)事先檢測(cè)到了指針的改變,那么該指針將不會(huì)再使用。目前有以下三個(gè)研究方向。
FreeBSD系統(tǒng)有一套能通過(guò)監(jiān)測(cè)CPU堆棧來(lái)確定緩沖區(qū)溢出的libc,可有效地保護(hù)libc中當(dāng)前有效的記錄函數(shù),有效地防衛(wèi)了基于libc庫(kù)函數(shù)的攻擊,然而不能抑制其它方式的攻擊。
StackGuard通過(guò)不允許改動(dòng)活動(dòng)函數(shù)的返回地址RET來(lái)防止某些類(lèi)型的緩沖區(qū)溢出攻擊。實(shí)現(xiàn)方式有函數(shù)返回前檢測(cè)返回地址RET的改動(dòng)和禁止對(duì)返回地址RET寫(xiě)。分析與實(shí)驗(yàn)數(shù)據(jù)表明,對(duì)于各種系統(tǒng)的緩沖區(qū)溢出攻擊,StackGuard都有很好的保護(hù)作用,并具有較好的系統(tǒng)性能與兼容性。并且,StackGuard能有效抵御各類(lèi)不同基于堆棧的攻擊。
PointGuard通過(guò)在所有的代碼指針之后放置附加字節(jié)來(lái)檢驗(yàn)指針在被調(diào)用之前的合法性,實(shí)質(zhì)上是StackGuard的推廣。此外動(dòng)態(tài)防御技術(shù)還有ProPolice、StackShield、PaX等。
5結(jié)束語(yǔ)
作為一種危害性大、應(yīng)用廣的安全漏洞,緩沖區(qū)溢出攻擊被非法用戶廣泛應(yīng)用于不同的操作系統(tǒng)與應(yīng)用軟件中。通過(guò)緩沖區(qū)溢出攻擊,能導(dǎo)致程序運(yùn)行失敗、重新啟動(dòng)以及系統(tǒng)當(dāng)機(jī)等后果。尤其是若利用它執(zhí)行非授權(quán)指令,非法用戶甚至能夠取得系統(tǒng)特權(quán),進(jìn)而執(zhí)行一些非法操作。由于這種攻擊能使得非法用戶完全控制某一臺(tái)主機(jī),因此構(gòu)成了對(duì)計(jì)算機(jī)系統(tǒng)很大的安全威脅與隱患。
在“信息安全理論與技術(shù)”課程教學(xué)中,我們十分重視緩沖區(qū)溢出安全編程的教與學(xué)。通過(guò)分析緩沖區(qū)溢出的基本原理和攻擊技術(shù),師生共同探討,舉一反三,理論與上機(jī)操作相結(jié)合,提出了一些在C語(yǔ)言程序編寫(xiě)過(guò)程中防御緩沖區(qū)溢出的方法,以提高學(xué)生安全編程能力和軟件開(kāi)發(fā)水平。