成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

安全漏洞是如何造成的:緩沖區(qū)溢出

譯文
安全 漏洞
自1988年莫里斯蠕蟲誕生以來(lái),緩沖區(qū)溢出漏洞就威脅著從Linux到Windows的各類系統(tǒng)環(huán)境。

自1988年莫里斯蠕蟲誕生以來(lái),緩沖區(qū)溢出漏洞就威脅著從Linux到Windows的各類系統(tǒng)環(huán)境。

緩沖區(qū)溢出漏洞長(zhǎng)久以來(lái)一直是計(jì)算機(jī)安全領(lǐng)域的一大特例。事實(shí)上,世界上首個(gè)能夠自我傳播的互聯(lián)網(wǎng)蠕蟲——誕生于1988年的莫里斯蠕蟲——就是通過(guò)Unix系統(tǒng)中的守護(hù)進(jìn)程利用緩沖區(qū)溢出實(shí)現(xiàn)傳播的。而在二十七年后的今天,緩沖區(qū)溢出仍然在一系列安全隱患當(dāng)中扮演著關(guān)鍵性角色。聲威顯赫的Windows家族就曾在2000年初遭遇過(guò)兩次基于緩沖區(qū)溢出的成規(guī)模安全侵襲。而就在今年5月,某款Linux驅(qū)動(dòng)程序中遺留的潛在緩沖區(qū)溢出漏洞更是讓數(shù)百萬(wàn)臺(tái)家庭及小型辦公區(qū)路由設(shè)備身陷風(fēng)險(xiǎn)之中。

[[147704]]

但頗為諷刺的是,作為一種肆虐多年的安全隱患,緩沖區(qū)溢出漏洞的核心卻只是由一種實(shí)踐性結(jié)果衍生出的簡(jiǎn)單bug。計(jì)算機(jī)程序會(huì)頻繁使用多組讀取自某個(gè)文件、網(wǎng)絡(luò)甚至是源自鍵盤輸入的數(shù)據(jù)。程序?yàn)檫@些數(shù)據(jù)分配一定量的內(nèi)存塊——也就是緩沖區(qū)——作為存儲(chǔ)資源。而所謂緩沖區(qū)漏洞的產(chǎn)生原理就是,寫入或者讀取自特定緩沖區(qū)的數(shù)據(jù)總量超出了該緩沖區(qū)所能容納量的上限。

事實(shí)上,這聽(tīng)起來(lái)像是一種相當(dāng)愚蠢、毫無(wú)技術(shù)含量的錯(cuò)誤。畢竟程序本身很清楚緩沖區(qū)的具體大小,因此我們似乎能夠很輕松地確保程序只向緩沖區(qū)發(fā)送不超出上限的數(shù)據(jù)量。這么想確實(shí)沒(méi)錯(cuò),但緩沖區(qū)溢出仍在不斷出現(xiàn),并始終成為眾多安全攻擊活動(dòng)的導(dǎo)火線。

為了了解緩沖區(qū)溢出問(wèn)題的發(fā)生原因——以及為何其影響如此嚴(yán)重——我們需要首先談?wù)劤绦蚴侨绾问褂脙?nèi)存資源以及程序員是如何編寫代碼的。(需要注意的是,我們將以堆棧緩沖區(qū)溢出作為主要著眼對(duì)象。雖然這并不是惟一一種溢出問(wèn)題,但卻擁有著典型性地位以及極高的知名度。)

堆疊起來(lái)

緩沖區(qū)溢出只會(huì)給原生代碼造成影響——也就是那些直接利用處理器指令集編寫而成的程序,而不會(huì)影響到利用Java或者Python等中間開(kāi)發(fā)機(jī)制構(gòu)建的代碼。不同操作系統(tǒng)有著自己的特殊處理方式,但目前各類常用系統(tǒng)平臺(tái)則普遍遵循基本一致的運(yùn)作模式。要了解這些攻擊是如何出現(xiàn)的,進(jìn)而著手阻止此類攻擊活動(dòng),我們首先要了解內(nèi)存資源的使用機(jī)制。

在這方面,最重要的核心概念就是內(nèi)存地址。內(nèi)存當(dāng)中每個(gè)獨(dú)立的字節(jié)都擁有一個(gè)與之對(duì)應(yīng)的數(shù)值地址。當(dāng)處理器從主內(nèi)存(也就是RAM)中加載或者向其中寫稿數(shù)據(jù)時(shí),它會(huì)利用內(nèi)存地址來(lái)確定讀取或?qū)懭胨赶虻奈恢谩O到y(tǒng)內(nèi)存并不單純用于承載數(shù)據(jù),它同時(shí)也被用于執(zhí)行那些構(gòu)建軟件的可執(zhí)行代碼。這意味著處于運(yùn)行中的程序,其每項(xiàng)功能都會(huì)擁有對(duì)應(yīng)的地址。

在計(jì)算機(jī)制發(fā)展的早期階段,處理器與操作系統(tǒng)使用的是物理內(nèi)存地址:每個(gè)內(nèi)存地址都會(huì)直接與RAM中的特定位置相對(duì)應(yīng)。盡管目前某些現(xiàn)代操作系統(tǒng)仍然會(huì)有某些組成部分繼續(xù)使用這類物理內(nèi)存地址,但現(xiàn)在所有操作系統(tǒng)都會(huì)在廣義層面采用另一種機(jī)制——也就是虛擬內(nèi)存。

在虛擬內(nèi)存機(jī)制的幫助下,內(nèi)存地址與RAM中物理位置直接對(duì)應(yīng)的方式被徹底打破。相反,軟件與處理器會(huì)利用虛擬內(nèi)存地址保證自身運(yùn)轉(zhuǎn)。操作系統(tǒng)與處理器配合起來(lái)共同維護(hù)著一套虛擬機(jī)內(nèi)存地址與物理內(nèi)存地址之間的映射機(jī)制。

這種虛擬化方式帶來(lái)了一系列非常重要的特性。首先也是最重要的,即“受保護(hù)內(nèi)存”。具體而言,每項(xiàng)獨(dú)立進(jìn)程都擁有屬于自己的地址集合。對(duì)于一個(gè)32位進(jìn)程而言,這部分對(duì)應(yīng)地址從0開(kāi)始(作為首個(gè)字節(jié))一直到4294967295(在十六進(jìn)制下表示為0xffff'ffff; 232 - 1)。而對(duì)于64位進(jìn)程,其能夠使用的地址則進(jìn)一步增加至18446744073709551615(十六進(jìn)制中的0xffff'ffff'ffff'ffff, 264 - 1)。也就是說(shuō),每個(gè)進(jìn)程都擁有自己的地址0,自己的地址1、地址2并以此類推。

(在文章的后續(xù)部分,除非另行強(qiáng)調(diào),否則我將主要針對(duì)32位系統(tǒng)進(jìn)行講解。其實(shí)32位與64位系統(tǒng)的工作機(jī)理是完全相同的,因此單獨(dú)著眼于前者不會(huì)造成任何影響,這只是為了盡量讓大家將注意力集中在單一對(duì)象身上。)

由于每個(gè)進(jìn)程都擁有自己的一套地址,而這種規(guī)劃就以一種非常簡(jiǎn)單的方式防止了不同進(jìn)程之間相互干擾:一個(gè)進(jìn)程所能使用的全部參考內(nèi)存地址都將直接歸屬于該進(jìn)程。在這種情況下,進(jìn)程也能夠更輕松地完成對(duì)物理內(nèi)存地址的管理。值得一提的是,雖然物理內(nèi)存地址幾乎遵循同樣的工作原理(即以0為起始字節(jié)),但實(shí)際使用中可能帶來(lái)某些問(wèn)題。舉例來(lái)說(shuō),物理內(nèi)存地址通常是非連續(xù)的;地址0x1ff8'0000被用于處理器的系統(tǒng)管理模式,而另有一小部分物理內(nèi)存地址會(huì)作為保留而無(wú)法被普通軟件所使用。除此之外,由PCIe卡提供的內(nèi)存資源一般也要占用一部分地址空間。而在虛擬地址機(jī)制中,這些限制都將不復(fù)存在。

那么進(jìn)程會(huì)在自己對(duì)應(yīng)的地址空間中藏進(jìn)什么小秘密呢?總體來(lái)講,大致有四種覺(jué)類別,我們會(huì)著重討論其中三種。這惟一一種不值得探討的也就是大多數(shù)操作系統(tǒng)所必不可少的“操作系統(tǒng)內(nèi)核”。出于性能方面的考量,內(nèi)存地址空間通常會(huì)被拆分為兩半,其中下半部分為程序所使用、上半部分由作為系統(tǒng)內(nèi)核的專用地址空間。內(nèi)核所占用的這一半內(nèi)存無(wú)法訪問(wèn)程序那一半的內(nèi)容,但內(nèi)核自身卻可以讀取程序內(nèi)存,這也正是數(shù)據(jù)向內(nèi)核功能傳輸?shù)膶?shí)現(xiàn)原理。

我們首先需要關(guān)注的就是構(gòu)建程序的各類可執(zhí)行代碼與庫(kù)。主可執(zhí)行代碼及其全部配套庫(kù)都會(huì)被載入到對(duì)應(yīng)進(jìn)程的地址空間當(dāng)中,而且所有組成部分都擁有自己的對(duì)應(yīng)內(nèi)存地址。

其次就是程序用于存儲(chǔ)自身數(shù)據(jù)的內(nèi)存,這部分內(nèi)存資源通常被稱為heap、也就是內(nèi)存堆。舉例來(lái)說(shuō),內(nèi)存堆可以用于存儲(chǔ)當(dāng)前正在編輯的文檔、瀏覽的網(wǎng)頁(yè)(包括其中的全部JavaScript對(duì)象、CSS等等)或者當(dāng)前游戲的地圖資源等等。

第三也是最重要的一項(xiàng)概念即call stack,即調(diào)用堆——也簡(jiǎn)稱為棧。內(nèi)存棧可以說(shuō)是最復(fù)雜的相關(guān)概念了。進(jìn)程中的每個(gè)分線程都擁有自己的內(nèi)存棧。棧其實(shí)就是一個(gè)內(nèi)存塊,用于追蹤某個(gè)線程當(dāng)前正在運(yùn)行的函數(shù)以及所有前趨函數(shù)——所謂前趨函數(shù),是指那些當(dāng)前函數(shù)需要調(diào)用的其它函數(shù)。舉例來(lái)說(shuō),如果函數(shù)a調(diào)用函數(shù)b,而函數(shù)b又調(diào)用函數(shù)c,那么棧內(nèi)所包含的信息則依次為a、b和c。

安全漏洞是如何造成的:緩沖區(qū)溢出

在這里我們可以看到棧的基本布局,首先是名為name的64字符緩沖區(qū),接下來(lái)依次為幀指針以及返回地址。esp擁有此內(nèi)存棧的上半部分地址,ebp則擁有內(nèi)存棧的下半部分地址。

調(diào)用堆棧屬于通用型“棧”數(shù)據(jù)結(jié)構(gòu)的一個(gè)特殊版本。棧是一種用于存儲(chǔ)對(duì)象且大小可變的結(jié)構(gòu)。新對(duì)象能夠被加入到(即’push‘)該棧的一端(一般為對(duì)應(yīng)內(nèi)存棧的’top‘端,即頂端),也可從棧中進(jìn)行移除(即’pop’)。只有內(nèi)存棧頂端的部分能夠通過(guò)push或者pop進(jìn)行修改,因此棧會(huì)強(qiáng)制執(zhí)行一種排序機(jī)制:最近添加進(jìn)入的項(xiàng)目也會(huì)被首先移除。而首個(gè)添加進(jìn)入的項(xiàng)目則會(huì)被最后移除。

調(diào)用堆棧最為重要的任務(wù)就是存儲(chǔ)返回地址。在大多數(shù)情況下,當(dāng)一款程序調(diào)用某項(xiàng)函數(shù)時(shí),該函數(shù)會(huì)按照既定設(shè)計(jì)發(fā)生作用(包括調(diào)用其它函數(shù)),并隨后返回至調(diào)用它的函數(shù)處。為了能夠切實(shí)返回至正確的調(diào)用函數(shù),必須存在一套記錄系統(tǒng)來(lái)注明進(jìn)行調(diào)用的源函數(shù):即應(yīng)當(dāng)在函數(shù)調(diào)用指令執(zhí)行之后從指令中恢復(fù)回來(lái)。這條指令所對(duì)應(yīng)的地址就被稱為返回地址。棧用于維護(hù)這些返回地址,就是說(shuō)每當(dāng)有函數(shù)被調(diào)用時(shí),返回地址都會(huì)被push到其內(nèi)存棧當(dāng)中。而在函數(shù)返回之后,對(duì)應(yīng)返回地址則從內(nèi)存棧中被移除,處理器隨后開(kāi)始在該地址上執(zhí)行指令。

棧的功能非常重要,甚至可以說(shuō)是整個(gè)流程的核心所在,而處理器也會(huì)以內(nèi)置方式支持這些處理概念。以x86處理器為例,在x86所定義的各個(gè)寄存器當(dāng)中(所謂寄存器,是指處理器內(nèi)的小型存儲(chǔ)位置,其能夠直接由處理器指令進(jìn)行訪問(wèn)),最為重要的兩類就是eip(即指令指針)以及esp(即棧指針)。

esp始終容納有棧頂端的對(duì)應(yīng)地址。每一次有數(shù)據(jù)被添加到該棧中時(shí),esp中的值都會(huì)降低。而每當(dāng)有數(shù)據(jù)從棧中被移除時(shí),esp的值則相應(yīng)增加。這意味著該棧的值出現(xiàn)“下降”時(shí),則代表有更多數(shù)據(jù)被添加到了該棧當(dāng)中,而esp中的存儲(chǔ)地址則會(huì)不斷向下方移動(dòng)。不過(guò)盡管如此,esp所使用的參考內(nèi)存位置仍然被稱為該內(nèi)存棧的“頂端”。

eip 為現(xiàn)有執(zhí)行指令提供內(nèi)存地址,而處理器則負(fù)責(zé)維護(hù)eip本身的正常運(yùn)作。處理器會(huì)從內(nèi)存當(dāng)中根據(jù)eip增量讀取指令流,從而保證始終能夠獲得正確的指令地址。x86擁有一項(xiàng)用于函數(shù)調(diào)用的指令,名為call,另一項(xiàng)用于從函數(shù)處返回的指令則名為ret。

call 會(huì)獲取一個(gè)操作數(shù),也就是欲調(diào)用函數(shù)的地址(當(dāng)然,我們也可以利用其它方式來(lái)獲取欲調(diào)用函數(shù)的地址)。當(dāng)執(zhí)行call指令時(shí),棧指針esp會(huì)通過(guò)4個(gè)字節(jié)(32位)來(lái)表現(xiàn),而緊隨call之后的指令地址——也就是返回地址——則會(huì)被寫入至當(dāng)前esp的參考內(nèi)存位置。換句話說(shuō),返回地址會(huì)被添加至內(nèi)存棧中。接下來(lái),eip會(huì)將該地址指定為call的操作數(shù),并以該地址為起始位置進(jìn)行后續(xù)操作。

ret 的作用則完全相反。簡(jiǎn)單的ret指令不會(huì)獲取任何操作數(shù)。處理器首先從esp當(dāng)中的內(nèi)存地址處讀取值,而后對(duì)esp進(jìn)行4字節(jié)的數(shù)值增量——這意味著其將返回地址從內(nèi)存棧中移除出去。這時(shí)eip接受值設(shè)定,并以此為起始位置進(jìn)行后續(xù)操作。

【視頻】

在實(shí)際操作中了解call與ret。

如果調(diào)用堆棧當(dāng)中只包含一組返回地址序列,那么問(wèn)題當(dāng)然就很簡(jiǎn)單了。但真正的難點(diǎn)在于,其它數(shù)據(jù)也會(huì)被添加到該內(nèi)存棧當(dāng)中。內(nèi)存棧的自身定位就是速度快且效率高的數(shù)據(jù)存儲(chǔ)位置。存儲(chǔ)在內(nèi)存堆上的數(shù)據(jù)相對(duì)比較復(fù)雜;程序需要全程追蹤內(nèi)存堆內(nèi)的當(dāng)前可用空間、當(dāng)前所使用數(shù)據(jù)片段各自占用多大空間外加其它一系列需要關(guān)注的指標(biāo)。不過(guò)內(nèi)存棧本身則非常簡(jiǎn)單;要為某些數(shù)據(jù)騰出空間,只需要降低棧指針即可。而在數(shù)據(jù)不需要繼續(xù)駐留在內(nèi)存中時(shí),則增加棧指針。

這種便捷性讓內(nèi)存棧成為一套邏輯空間,能夠存儲(chǔ)歸屬于函數(shù)的各類變量。每項(xiàng)函數(shù)擁有256字節(jié)的緩沖空間來(lái)讀取用戶的輸入內(nèi)容。簡(jiǎn)單來(lái)講,我們只需要在棧指針中減去256這一數(shù)值就能創(chuàng)建出該緩沖區(qū)。而在函數(shù)執(zhí)行結(jié)束時(shí),向棧指針內(nèi)添加添加256就能丟棄這個(gè)緩沖區(qū)。

安全漏洞是如何造成的:緩沖區(qū)溢出

當(dāng)我們正確使用程序時(shí),鍵盤輸入內(nèi)容會(huì)被存儲(chǔ)至name緩沖區(qū)中,隨后為null(即0)字節(jié)。幀指針與返回地址則保持不變。

但這種處理方式也存在局限。內(nèi)存棧并不適合保存規(guī)模龐大的對(duì)象;內(nèi)存的整體可用容量通常在線程創(chuàng)建之時(shí)就被確定下來(lái)了,而且通常大小為1 MB。因此,那些大型對(duì)象必須被保存在內(nèi)存堆中。棧也不適合保存那些需要長(zhǎng)久存在,甚至生命周期比單一函數(shù)調(diào)用更長(zhǎng)的對(duì)象。由于每個(gè)分配的內(nèi)存棧都會(huì)在函數(shù)執(zhí)行完成后被撤銷,因此任何存在于該棧中的對(duì)象將無(wú)法在函數(shù)結(jié)束后繼續(xù)駐留。不過(guò)存在于內(nèi)存堆中的對(duì)象則不受此類限制,它們能夠獨(dú)立于函數(shù)之外實(shí)現(xiàn)長(zhǎng)期駐留。

內(nèi)存棧存儲(chǔ)機(jī)制并不只適用于程序員在程序中明確創(chuàng)建的命名變量,同時(shí)亦可用于存儲(chǔ)其它任何程序可能需要的數(shù)值。從傳統(tǒng)上講,這算是x86架構(gòu)的一大問(wèn)題。X86處理器并不能提供太多寄存器(寄存器的總體數(shù)量只有8個(gè),而且其中一部分,例如eip與esp,還需要留作特定用途),因此函數(shù)幾乎無(wú)法在寄存器中長(zhǎng)期保留所有數(shù)值。為了在不影響現(xiàn)有數(shù)值以供今后檢索的同時(shí)釋放寄存器空間,編譯器會(huì)將寄存器中的數(shù)值添加到內(nèi)存棧當(dāng)中。在此之后,相關(guān)數(shù)值可以pop方式從棧內(nèi)轉(zhuǎn)移回寄存器。用編譯器的術(shù)語(yǔ)來(lái)講,這種節(jié)約寄存器空間并保證數(shù)值可重復(fù)使用的操作被稱為spilling。

最后,內(nèi)存棧通常被用于向函數(shù)傳遞參數(shù)。調(diào)用函數(shù)會(huì)將每個(gè)參數(shù)添加到內(nèi)存棧中,而受調(diào)用函數(shù)之后則能夠?qū)⑦@些參數(shù)移除出去。這并不是惟一一種參數(shù)傳遞方式——舉例來(lái)說(shuō),也可以在寄存器內(nèi)部進(jìn)行參數(shù)傳遞——但卻是最為靈活的方式。

函數(shù)在內(nèi)存棧上的所有具體內(nèi)容——包括其本地變量、spilling寄存器操作以及任何準(zhǔn)備傳遞給其它函數(shù)的參數(shù)——被整體稱為一個(gè)“棧幀”。由于棧幀中的數(shù)據(jù)會(huì)被廣泛使用,因此需要一種能夠?qū)崿F(xiàn)快速引用的辦法與之配合。

棧指針也能完成這項(xiàng)任務(wù),但它的實(shí)現(xiàn)方式有些尷尬:棧指針總會(huì)指向內(nèi)存棧的頂端,因此它需要在添加與移除的數(shù)據(jù)之間來(lái)回移動(dòng)。舉例來(lái)說(shuō),某個(gè)變量可能以esp + 4地址作為起始位置,而在有另外兩個(gè)數(shù)值被添加到棧中時(shí),就意味著該變量現(xiàn)在的訪問(wèn)位置變成了esp + 12。而一旦某個(gè)數(shù)值被移除出去,那么該變量的位置又變成了esp + 8。

這倒不是什么無(wú)法克服的障礙,編譯器本身能夠很輕松地加以解決。不過(guò)這仍然無(wú)法真正回避棧指針以“內(nèi)存棧頂端”作為起始位置的問(wèn)題,特別是在手工編碼的匯編程序當(dāng)中。

為了簡(jiǎn)化實(shí)現(xiàn)流程,最常見(jiàn)的辦法就是使用一個(gè)次級(jí)指針——其需要始終將數(shù)據(jù)保存在每個(gè)棧幀的底部(起始)位置——我們往往將該值稱為幀指針。在x86架構(gòu)中,甚至還有名為ebp的專門寄存器用于存儲(chǔ)這一值。由于這種機(jī)制不會(huì)對(duì)特定函數(shù)造成任何內(nèi)部變更,因此我們可以利用它作為訪問(wèn)函數(shù)變量的一種固定方式:位于ebp – 4位置的值在整個(gè)函數(shù)中始終保持自己的ebp – 4位置。這種效果不僅有助于程序員理解,同時(shí)也能夠顯著簡(jiǎn)化調(diào)試程序的處理流程。

安全漏洞是如何造成的:緩沖區(qū)溢出

以上截圖來(lái)自Visual Studio,其中顯示了某簡(jiǎn)單x86程序完成上述操作的過(guò)程。在x86處理器當(dāng)中,名為esp的寄存器負(fù)責(zé)容納頂端內(nèi)存棧中的地址——在本示例中為0x0018ff00,以藍(lán)色高亮表示(在x86架構(gòu)中,內(nèi)存棧實(shí)際上會(huì)不斷向下推進(jìn)并指向地址0,但其仍然會(huì)以棧頂端為起點(diǎn)進(jìn)行地址調(diào)用)。該函數(shù)只擁有一個(gè)棧變量,即name,以粉色高亮表示。其緩沖區(qū)大小固定為32字節(jié)。由于屬于惟一一個(gè)變量,因此其位置同樣為0x0018ff00,與該內(nèi)存棧的頂端保持一致。

x86還擁有一個(gè)名為ebp的寄存器,以紅色高亮表示,其通常專門用于保存幀指針的位置。幀指針的位置緊隨棧變量之后。幀指針之后則為返回地址,以綠色高亮表示。返回地址所引用的代碼片段地址為0x00401048。在這條指令之后的是call指令,很明顯返回地址會(huì)從調(diào)用函數(shù)剩余的地址位置處執(zhí)行恢復(fù)。

安全漏洞是如何造成的:緩沖區(qū)溢出

遺憾的是,gets()實(shí)在是個(gè)極其愚蠢的函數(shù)。如果我們按住鍵盤上的A鍵,那么該函數(shù)會(huì)不間斷地一直向name緩沖區(qū)內(nèi)寫入“A”。在此過(guò)程中,該函數(shù)一直向內(nèi)存中寫入數(shù)據(jù),覆蓋幀指針、返回地址以及其它一切能夠被覆蓋的內(nèi)容。

在以上截圖當(dāng)中,name屬于會(huì)定期被覆蓋的緩沖區(qū)類型。其大小固定為64字符。在這里的示例中,它被填寫進(jìn)一大堆數(shù)字,并最終以null結(jié)尾。從上圖中可以清楚地看到,如果name緩沖區(qū)的寫入內(nèi)容超出了64字節(jié),那么該內(nèi)存棧中的其它數(shù)值也會(huì)受到影響。如果有額外的4字節(jié)內(nèi)容被寫入,那么該幀指針就會(huì)被破壞。而如果寫入的內(nèi)容為額外8個(gè)字節(jié),那么幀指針與返回地址將雙雙被覆蓋。

很明顯,這會(huì)導(dǎo)致程序數(shù)據(jù)遭到破壞,但緩存區(qū)溢出還會(huì)造成其它更加嚴(yán)重的后果:通常會(huì)影響到代碼執(zhí)行。之所以會(huì)出現(xiàn)這種情況,是因?yàn)榫彌_區(qū)溢出不僅會(huì)覆蓋數(shù)據(jù),同時(shí)也可能覆蓋內(nèi)存棧中的返回地址乃至其它更為重要的內(nèi)容。返回地址負(fù)責(zé)控制處理器在完成當(dāng)前函數(shù)之后,接下來(lái)執(zhí)行哪些指令。返回地址正常來(lái)說(shuō)應(yīng)該處于調(diào)用函數(shù)之內(nèi)的某個(gè)位置,但如果由于緩沖區(qū)溢出而被覆蓋,那么返回地址的指向位置將變得隨機(jī)而不可控制。如果攻擊者能夠利用這種緩沖區(qū)溢出手段,則能夠選定處理器接下來(lái)要執(zhí)行的代碼位置。

在這一過(guò)程中,攻擊者可能并沒(méi)有什么理想的、便捷的“設(shè)備入侵”方法可供選擇,但這并不會(huì)影響惡意活動(dòng)的發(fā)生。用于覆蓋返回地址的緩沖區(qū)同時(shí)也可以被用于保存一小段可執(zhí)行代碼,也就是所謂shellcode,其隨后將能夠下載一段惡意可執(zhí)行代碼、開(kāi)啟某個(gè)網(wǎng)絡(luò)連接或者是實(shí)現(xiàn)其它一些攻擊手段。

從傳統(tǒng)角度講,這確實(shí)是種令人有些意外的、小處引發(fā)的大問(wèn)題:總體而言,每款程序在每次運(yùn)行時(shí)都會(huì)使用同樣的內(nèi)存地址——即使在經(jīng)過(guò)重啟之后也不例外。這意味著內(nèi)存棧上的緩沖區(qū)位置將永遠(yuǎn)不會(huì)變化,所以用于覆蓋返回地區(qū)的值也可以不斷重復(fù)加以使用。攻擊者只需要一次性找出對(duì)應(yīng)地址,就能夠在任何運(yùn)行著存在漏洞的代碼的計(jì)算機(jī)上再度實(shí)施攻擊。#p#

攻擊者的工具箱

在理想狀態(tài)下——當(dāng)然,這是從攻擊者的角度出發(fā)的——被覆蓋的返回地址可以就是緩沖區(qū)的所在位置。當(dāng)程序從文件或者網(wǎng)絡(luò)處讀取輸入數(shù)據(jù)時(shí),往往就會(huì)符合這一條件。

不過(guò)在其它情況下,攻擊者則需要?jiǎng)佑靡稽c(diǎn)小技巧。在負(fù)責(zé)處理我們能夠直接閱讀的文本內(nèi)容的函數(shù)中,0字節(jié)(或者稱為‘null’)通常會(huì)被特殊處理;它表示一條字符串的結(jié)尾,而用于操作這些字符串的函數(shù)——包括復(fù)制、比較以及整合等——將會(huì)在接觸到null字符后直接中止。這意味著如果該shellcode中包含有null字符,那么執(zhí)行程度到這里一定會(huì)停止。

【視頻】

查看整個(gè)緩沖區(qū)溢出過(guò)程。在這段視頻中,我們將shellcode添加到了緩沖區(qū)內(nèi),而后通過(guò)執(zhí)行以棋牌室返回地址。我們的shellcode運(yùn)行了Windows計(jì)算器程序。

安全漏洞是如何造成的:緩沖區(qū)溢出

為了利用這種溢出手段而非單純向內(nèi)存棧中寫入大量“A”以破壞一切內(nèi)容,攻擊者需要在緩沖區(qū)中添加shellcode:這是一小段可執(zhí)行代碼,其能夠執(zhí)行攻擊者所選定的一系列操作。在此之后,返回地址會(huì)被緩沖區(qū)所引用的地址所覆蓋,進(jìn)而在從某函數(shù)調(diào)用返回后將處理器定向至shellcode執(zhí)行位置。

為了實(shí)際這一目標(biāo),攻擊者可以選擇多種技術(shù)手段。代碼片段可以將包含有null字符的shellcode轉(zhuǎn)換為具備同等作用的形式以避免出現(xiàn)問(wèn)題。它們甚至能夠處理更為嚴(yán)格的限制,例如一條已被篡改的函數(shù)可能只接收能夠通過(guò)標(biāo)準(zhǔn)鍵盤進(jìn)行輸入的結(jié)果。

內(nèi)存棧本身的地址中通常也包含有null字節(jié),這同樣會(huì)引發(fā)問(wèn)題:這意味著返回地址無(wú)法直接被設(shè)定為棧緩沖區(qū)的地址。一般來(lái)講這倒不是什么大問(wèn)題,畢竟那些可用于填寫緩沖區(qū)的函數(shù)(當(dāng)然,也會(huì)造成潛在的溢出隱患)會(huì)自行寫入一個(gè)null字節(jié)。但在某些情況下,它們則可被用于將null字節(jié)添加到正確的位置當(dāng)中,從而篡改內(nèi)存棧中的返回地址。

即使無(wú)法進(jìn)行返回地址篡改,這種狀況也可被攻擊者們用于重新定向。程序及其全部相關(guān)庫(kù)的存在意味著,內(nèi)存當(dāng)中可以駐留可執(zhí)行代碼。大部分此類可執(zhí)行代碼都能夠擁有屬于自己的“安全”地址,也就是說(shuō)其中不包含任何null字節(jié)。

攻擊者們要做的就是找到一個(gè)包含一條指令的可用地址,例如x86架構(gòu)中的call esp,其會(huì)將棧指針的值作為函數(shù)地址看待并加以執(zhí)行——這顯然非常適合用來(lái)承載shellcode。攻擊者隨后會(huì)利用callesp指令的地址來(lái)覆蓋返回地址;如此一來(lái),處理器會(huì)在該地址處進(jìn)行一次額外的跳轉(zhuǎn),但最終仍會(huì)運(yùn)行該shellcode。這項(xiàng)利用其它地址強(qiáng)行實(shí)現(xiàn)代碼執(zhí)行的方法被稱為“trampolining”,也就是蹦床。

安全漏洞是如何造成的:緩沖區(qū)溢出

有時(shí)候我們很難利用緩沖區(qū)地址來(lái)覆蓋返回地址。為了解決這個(gè)問(wèn)題,我們可以利用目標(biāo)程序(或者其對(duì)應(yīng)庫(kù))中的特定可執(zhí)行代碼片段地址來(lái)覆蓋返回地址。這部分代碼片段能幫助我們對(duì)緩沖區(qū)位置進(jìn)行轉(zhuǎn)換。

之所以這種方式能夠奏效,是因?yàn)檎缜懊嫣崽岬剑绦蚣捌渑涮讕?kù)在每時(shí)運(yùn)行時(shí)都會(huì)使用同樣的內(nèi)存地址——即使是多次啟動(dòng)甚至在不同設(shè)備之上都不會(huì)改變這一點(diǎn)。而非常有趣的是,用于提供“蹦床”的庫(kù)本身并不需要執(zhí)行call esp指令。該庫(kù)只需要提供兩個(gè)字節(jié)(在本次示例中為0xff與0xd4)并保證彼此相鄰即可。它們可以作為其它指令中的組成部分甚至直接以數(shù)字形式存在;x86對(duì)于這類內(nèi)容并不挑剔。另外,x86的指令長(zhǎng)度可以相當(dāng)之長(zhǎng)(最高為15字節(jié)!)并指向任意地址。如果處理器從中間部分讀取某條指令——例如在一條長(zhǎng)度為4字節(jié)的指令中從第二個(gè)字節(jié)開(kāi)始讀取——那么最終的執(zhí)行結(jié)果可能會(huì)完全不同、但卻仍然切實(shí)生效。考慮到這一點(diǎn),攻擊者確實(shí)可以很輕松地找到可資利用的“蹦床”。

不過(guò)有時(shí)候,攻擊活動(dòng)無(wú)法直接將返回地址篡改為所需位置。不過(guò)由于內(nèi)存布局總是非常相似,不同設(shè)備或者不同運(yùn)行進(jìn)程之間的設(shè)定幾乎完全相同。舉例來(lái)說(shuō),某個(gè)可利用的緩沖區(qū)的具體位置可能會(huì)出現(xiàn)變化,而存在差異的幾個(gè)字節(jié)則取決于系統(tǒng)名稱或者IP地址。另外,軟件的小型更新可能也會(huì)讓內(nèi)存地址出現(xiàn)稍許變動(dòng)。為了解決這一問(wèn)題,攻擊者只需要找到返回地址的大概正確位置即可,而不必保證其完全符合實(shí)際情況。

面對(duì)這類狀況,攻擊者的處理辦法也很簡(jiǎn)單,這就是使用所謂“NOP sled”技術(shù)。相較于直接向緩沖區(qū)內(nèi)寫入shellcode,攻擊者可以在真正的shellcode之前編寫數(shù)量龐大的多條“NOP”指令(所謂NOP也就是’no-op‘,是指那些不會(huì)真正執(zhí)行的指令),有時(shí)候可以多達(dá)數(shù)百條。要運(yùn)行該shellcode,攻擊者只需要將返回地址設(shè)定在這些NOP指令當(dāng)中的某個(gè)位置即可。只要該地址被包含在NOP當(dāng)中,處理器就會(huì)快速將其略過(guò)并直接執(zhí)行真正的shellcode。

你的錯(cuò)、他的錯(cuò)——都是C的錯(cuò)

導(dǎo)致上述攻擊得以實(shí)現(xiàn)的核心bug——具體來(lái)講,就是向緩沖區(qū)內(nèi)寫入超出其容納能力的內(nèi)容——聽(tīng)起來(lái)可以很輕松地加以避免。將這些問(wèn)題完全歸咎于C編程語(yǔ)言及其各類兼容性分支方案——例如C++以及Objective-C——或許有些夸張,但也不能說(shuō)毫無(wú)道理。C語(yǔ)言本身已經(jīng)相當(dāng)陳舊,但卻應(yīng)用廣泛且作為我們操作系統(tǒng)以及各類軟件的基礎(chǔ)性元素存在。正是由于C語(yǔ)言的流行,才讓這些本來(lái)可以輕松避免的bug長(zhǎng)期生效并影響到無(wú)數(shù)開(kāi)發(fā)者與用戶。

作為C語(yǔ)言自身阻礙安全開(kāi)發(fā)實(shí)踐的一項(xiàng)實(shí)例,我們?cè)谶@里要著重談?wù)刧ets()。作為一項(xiàng)函數(shù),gets()會(huì)獲取一條參數(shù)——也就是一個(gè)緩沖區(qū)——并從標(biāo)準(zhǔn)輸入內(nèi)容中(通常意味著’鍵盤輸入內(nèi)容‘)讀取一行數(shù)據(jù),而后將其添加到緩沖區(qū)當(dāng)中。細(xì)心的朋友可能已經(jīng)注意到,gets()當(dāng)中并不會(huì)對(duì)將被添加至緩沖區(qū)內(nèi)的參數(shù)長(zhǎng)度作出限制,而且作為C語(yǔ)言設(shè)計(jì)中的一種有趣現(xiàn)象,我們沒(méi)辦法利用gets()了解緩沖區(qū)的實(shí)際大小。這是因?yàn)間ets()并不會(huì)對(duì)輸入內(nèi)容的大小作出任何要求:它只負(fù)責(zé)從標(biāo)準(zhǔn)輸入內(nèi)容中讀取數(shù)據(jù)——直到電腦前的操作者按下回車——而后嘗試將全部?jī)?nèi)容添加到緩沖區(qū)內(nèi),即使操作者寫入的內(nèi)容遠(yuǎn)遠(yuǎn)超出了緩沖區(qū)容納能力,gets()也完全不予理會(huì)。

很明顯,這項(xiàng)函數(shù)屬于徹頭徹尾的安全隱患。由于我們無(wú)法制約通過(guò)鍵盤輸入的文本內(nèi)容總量,因此也就不可能避免由gets()引發(fā)的緩沖區(qū)溢出結(jié)果。C語(yǔ)言標(biāo)準(zhǔn)的制定者們確實(shí)意識(shí)到了這個(gè)問(wèn)題,并在1999年的再版C語(yǔ)言規(guī)范中對(duì)gets()加以棄用,最終在2011年的更新中將其完全移除。但它的存在——以及不時(shí)出現(xiàn)的實(shí)際使用——證明了C語(yǔ)言確實(shí)給用戶們挖了一個(gè)非常危險(xiǎn)的潛在陷阱。

而作為誕生于1988年的世界首個(gè)可通過(guò)互聯(lián)網(wǎng)傳輸?shù)淖晕覐?fù)制惡意軟件,莫里斯蠕蟲利用的恰恰是這項(xiàng)函數(shù)。BSD 4.3 fingerd程序會(huì)通過(guò)端口79對(duì)網(wǎng)絡(luò)連接進(jìn)行監(jiān)聽(tīng),也就是我們常說(shuō)的finger端口。事實(shí)上,finger也是一個(gè)非常古老的Unix程序,其作為網(wǎng)絡(luò)協(xié)議存在并負(fù)責(zé)識(shí)別是誰(shuí)登錄到了遠(yuǎn)程系統(tǒng)當(dāng)中。它的使用方式分為兩種;其一是遠(yuǎn)程系統(tǒng)可以利用它來(lái)查詢當(dāng)前已經(jīng)登錄的每位用戶,其二則是用于查詢特定用戶名并告知我們與該用戶相關(guān)的部分信息。

每當(dāng)有連接出現(xiàn)在finger的后臺(tái)進(jìn)程當(dāng)中,它都會(huì)利用gets()從網(wǎng)絡(luò)中讀取數(shù)據(jù)并將其添加到內(nèi)存棧中一個(gè)512字節(jié)的緩沖區(qū)內(nèi)。在通常操作中,fingerd會(huì)隨后生成finger程序,并在可能的情況下向其傳遞相關(guān)用戶名。該finger程序才是真正負(fù)責(zé)監(jiān)聽(tīng)用戶接入或者提供與特定用戶相關(guān)信息的主體,而fingerd本身僅僅負(fù)責(zé)監(jiān)聽(tīng)網(wǎng)絡(luò)并在需要時(shí)啟動(dòng)finger。

鑒于惟一的“真實(shí)”參數(shù)基本只會(huì)是用戶名,因此512字節(jié)的緩沖區(qū)設(shè)定已經(jīng)不算小了。應(yīng)該沒(méi)人會(huì)設(shè)定一個(gè)長(zhǎng)達(dá)512位的用戶名——不過(guò)系統(tǒng)本身并不會(huì)對(duì)此作出強(qiáng)制要求,因?yàn)樵谶@里負(fù)責(zé)內(nèi)容獲取工作的正是臭名昭著的gets()函數(shù)。當(dāng)我們通過(guò)網(wǎng)絡(luò)發(fā)出超過(guò)512字節(jié)的用戶名時(shí),fingerd就會(huì)乖乖地造成緩沖區(qū)溢出狀況。而這也正是Robert Morris的具體作法:他向fingerd發(fā)送了537字節(jié)的數(shù)據(jù)內(nèi)容(其中包含537個(gè)字節(jié)外中一個(gè)換行符,這直接導(dǎo)致gets()停止讀取輸入數(shù)據(jù)),順利實(shí)現(xiàn)緩沖區(qū)溢出并覆蓋了返回地址。在此之后,返回地址被輕松設(shè)置為內(nèi)存棧中的緩沖區(qū)地址。

莫里斯蠕蟲的可執(zhí)行負(fù)載非常簡(jiǎn)單。它會(huì)發(fā)起400條NOP指令,從而讓內(nèi)存棧布局出現(xiàn)輕微的變化,而后再接上一小段代碼片段。這些代碼會(huì)生成一條shell,即/bin/sh。這是攻擊負(fù)載當(dāng)中很常見(jiàn)的選擇;fingerd程序會(huì)以root權(quán)限運(yùn)行,因此在遭到攻擊并被迫運(yùn)行shell時(shí),該shell也將擁有root權(quán)限。另外,fingerd會(huì)被引導(dǎo)至網(wǎng)絡(luò)當(dāng)中,這意味著其接收的“鍵盤輸入內(nèi)容”可以實(shí)際來(lái)源于網(wǎng)絡(luò)傳輸,并將輸出結(jié)果通過(guò)網(wǎng)絡(luò)發(fā)送出去。這兩大特性都明顯昭示其為shell所利用的潛在可能性,也就是說(shuō)這一root shell現(xiàn)在已經(jīng)能夠?yàn)楣粽咚h(yuǎn)程操控。

盡管想要繞開(kāi)gets()并不困難——事實(shí)上即使是在莫里斯蠕蟲剛剛誕生的時(shí)候,就出現(xiàn)了能夠徹底禁用gets()的fingerd修復(fù)版本——但C語(yǔ)言的其它一些組成部分仍然難以被忽略,甚至幾乎不可能被徹底修復(fù)。C語(yǔ)言對(duì)于文本字符的處理方式就是一種常見(jiàn)的問(wèn)題根源。正如之前所提到,C語(yǔ)言在處理字符串時(shí)會(huì)在讀取至null字節(jié)后中止。在C語(yǔ)言中,一條字符串就是一段字符序列,其末尾以null字節(jié)作為字符串中止標(biāo)記。C語(yǔ)言當(dāng)中有一系列函數(shù)負(fù)責(zé)操作這些字符串。其中最典型的例子要數(shù)strcpy()——負(fù)責(zé)從來(lái)源處將一條字符串復(fù)制至目標(biāo)位置——以及strcat()——負(fù)責(zé)從來(lái)源處將一條字符串添加至目標(biāo)位置——這對(duì)奇葩了。這兩項(xiàng)函數(shù)都沒(méi)有對(duì)指向目標(biāo)緩沖區(qū)的參數(shù)作出長(zhǎng)度限制,因此添加之后會(huì)不會(huì)造成緩沖區(qū)溢出根本就不在這二者的考量范圍之內(nèi)。

即使C語(yǔ)言的字符串處理函數(shù)能夠?qū)χ赶蚓彌_區(qū)的參數(shù)長(zhǎng)度作出限制,同樣的錯(cuò)誤及溢出狀況仍然得不到徹底解決。C語(yǔ)言分別為strcat()與strcpy()提供一對(duì)姐妹函數(shù),分別名為strncat()與strncpy()。名稱當(dāng)中額外的n代表的正是其所獲取參數(shù)的長(zhǎng)度。但正如很多資深C語(yǔ)言程序員們所知,這個(gè)n并不是將要寫入的緩沖區(qū)的具體大小;相反,它其實(shí)是來(lái)源處將要進(jìn)行復(fù)制的字符數(shù)量。如果來(lái)源提供的數(shù)據(jù)量超出了對(duì)應(yīng)字符限制(因?yàn)檫_(dá)到了null字節(jié)的位置),那么strncpy()與strncat()將會(huì)通過(guò)向目標(biāo)位置復(fù)制更多null字節(jié)的方式來(lái)補(bǔ)足差額。換句話來(lái)說(shuō),這些函數(shù)仍然完全不關(guān)心目標(biāo)緩沖區(qū)的實(shí)際大小。

與gets()不同,我們其實(shí)有能力以安全方式使用以上函數(shù),只不過(guò)有點(diǎn)困難罷了。C++與Objective-C都針對(duì)C語(yǔ)言的函數(shù)庫(kù)提供更理想的替代方案,這使得我們能夠更輕松且更安全地實(shí)現(xiàn)字符串操作——不過(guò)由于向下兼容的考量,某些C語(yǔ)言中的陳舊特性仍然被繼承了下來(lái)。

除此之外,二者還包含了C語(yǔ)言的一大根本性缺陷:緩沖區(qū)自身并不了解自己的確切大小,而且C語(yǔ)言也根本不會(huì)驗(yàn)證緩沖區(qū)之上所執(zhí)行的讀取與寫入操作——這就使得緩沖區(qū)溢出成為了可能。正是同樣的機(jī)制導(dǎo)致OpenSSL當(dāng)中曝出了Hearbleed漏洞,但值得強(qiáng)調(diào)的是,它并不算是溢出、而屬于讀取越界。OpenSSL當(dāng)中的C代碼會(huì)嘗試讀取超出緩沖區(qū)容納能力的內(nèi)容,并最終導(dǎo)致敏感信息泄露至外部環(huán)境。#p#

修復(fù)此類漏洞

無(wú)需贅言,隨著人類智慧的進(jìn)一步發(fā)展,我們?nèi)缃褚呀?jīng)擁有了更多更出色的語(yǔ)言選項(xiàng)——它們會(huì)對(duì)指向緩沖區(qū)的讀取與寫入操作進(jìn)行驗(yàn)證,這就徹底阻斷了溢出問(wèn)題的發(fā)生。由Mozilla打造的Rust等編譯語(yǔ)言、安全運(yùn)行時(shí)環(huán)境的杰出代表Java以及.NET,外加Python、JavaScript、Lua以及Perl等虛擬化腳本語(yǔ)言都徹底解決了緩沖區(qū)溢出的問(wèn)題(當(dāng)然,.NET仍然允許開(kāi)發(fā)人員直接關(guān)閉所有保障措施,在這種選項(xiàng)設(shè)置之下緩沖區(qū)溢出會(huì)再度成為可能)。

緩沖區(qū)溢出目前仍然作為安全領(lǐng)域的一大關(guān)注重點(diǎn)存在,同時(shí)也是C語(yǔ)言持久生命力的有效證明。任何存在這一問(wèn)題的遺留代碼都有可能引發(fā)重大的安全事故。但目前世界上仍在運(yùn)行的C代碼依舊數(shù)不勝數(shù),其中包括眾多主流操作系統(tǒng)的內(nèi)核以及OpenSSL等高人氣代碼庫(kù)。即使開(kāi)發(fā)人員傾向于使用C#這樣安全性更出色的語(yǔ)言,他們也仍然需要使用大量由C語(yǔ)言編寫而成的第三方庫(kù)。

性能水平則是C語(yǔ)言繼續(xù)被廣泛使用的另一大理由,雖然關(guān)于這方面的具體判斷方式仍然比較模糊。確實(shí),經(jīng)過(guò)編譯的C與C++代碼能夠帶來(lái)更理想的執(zhí)行速度表現(xiàn),而且在某些情況下起到了無(wú)可替代的重要作用。然而目前大多數(shù)用戶所使用的處理器在絕大部分情況下都處于資源閑置的狀態(tài);如果我們能夠犧牲百分之十的總體性能來(lái)讓自己的瀏覽器獲得更為堅(jiān)實(shí)的安全保障,包括緩沖區(qū)溢出以及其它眾多潛在安全隱患,那么相信大家絕對(duì)會(huì)選擇這種方式。只要有廠商愿意開(kāi)發(fā)出這樣值得依賴的瀏覽器,我們就能夠根據(jù)自己的實(shí)際需要作出權(quán)衡。

盡管如此,C語(yǔ)言和它的整個(gè)大家族卻仍然廣泛存在——當(dāng)然也包括由其帶來(lái)的緩沖區(qū)溢出風(fēng)險(xiǎn)。

目前已經(jīng)有不少相關(guān)舉措努力阻止溢出錯(cuò)誤影響到開(kāi)發(fā)人員以及使用者。在開(kāi)發(fā)過(guò)程中,我們可以選擇多種工具對(duì)源代碼進(jìn)行分析,并通過(guò)程序運(yùn)行來(lái)檢測(cè)其中是否存在危險(xiǎn)結(jié)構(gòu)或者溢出錯(cuò)誤,這就避免了此類bug被實(shí)際添加到軟件成品當(dāng)中。AddressSantizer等新型工具以及Valgrind等傳統(tǒng)方案都可以實(shí)現(xiàn)上述功能。

然而,這些工具需要開(kāi)發(fā)人員的積極采用方能奏效,否則就是一堆毫無(wú)意義的0和1——也就是說(shuō)仍有相當(dāng)多的程序并沒(méi)有將其納入開(kāi)發(fā)流程。另有一些系統(tǒng)層面的保護(hù)手段,能夠在緩沖區(qū)溢出問(wèn)題真正發(fā)生之后盡可能保證其它軟件免受其侵害。在這方面,操作系統(tǒng)以及編譯器開(kāi)發(fā)者們已經(jīng)采取了一系列方案,旨在提高攻擊者使用這些溢出漏洞的難度。

某些系統(tǒng)的存在目的正是讓一部分特定攻擊活動(dòng)變得更難實(shí)現(xiàn)。當(dāng)前的多套Linux系統(tǒng)補(bǔ)丁就能夠確保系統(tǒng)庫(kù)全部被加載在底端內(nèi)存地址處,從而保證其地址中至少包含一個(gè)null字節(jié)。在這種情況下,攻擊者將很難利用C字符串處理方式在緩沖區(qū)溢出攻擊中使用這些地址。

其它防御機(jī)制也更為普遍。目前很多編譯器都擁有某種類型的內(nèi)存棧保護(hù)機(jī)制,其會(huì)將一個(gè)名為“canary”(意為金絲雀)的運(yùn)行時(shí)檢測(cè)值寫入到返回地址存儲(chǔ)位置附近的內(nèi)存棧末尾。在每項(xiàng)函數(shù)執(zhí)行結(jié)束之前,系統(tǒng)都會(huì)檢查該值以確定返回指令是否遭到了修改。如果該canary值發(fā)生了變化(因?yàn)槠湓诰彌_區(qū)溢出中被覆蓋),那么該程序?qū)⒘⒓幢罎⒍抢^續(xù)執(zhí)行。

而最重要的單項(xiàng)保護(hù)手段之一正是名為W^X(意為’單純寫入或執(zhí)行‘)、DEP(意為’數(shù)據(jù)執(zhí)行保護(hù)‘)、NX(意為‘不執(zhí)行’)、XD(意為‘執(zhí)行禁用’)、EVP(意為‘增強(qiáng)病毒保護(hù)’,AMD公司往往比較喜歡使用這一術(shù)語(yǔ))、XN(即‘從不執(zhí)行’)等一系列措施。它們所采取的概念非常簡(jiǎn)單。這些系統(tǒng)會(huì)盡可能讓內(nèi)存擁有可寫入能力(適用于緩沖區(qū))或者可執(zhí)行能力(適用于庫(kù)及程序代碼),但不會(huì)使其二者兼?zhèn)洹R虼耍词构粽吣軌蚴咕彌_區(qū)出現(xiàn)溢出并控制其中的返回地址,處理器最終仍然會(huì)拒絕執(zhí)行對(duì)應(yīng)的shellcode。

無(wú)論具體使用什么樣的名稱,這都是一項(xiàng)重要的技術(shù),這主要是因?yàn)槠淠軌蛟跓o(wú)需額外成本的前提下起效——這類方案使用的是處理器自身內(nèi)置的、作為虛擬內(nèi)存硬件支持而存在的保護(hù)機(jī)制。

正如之前所提到,在虛擬內(nèi)存當(dāng)中每個(gè)進(jìn)程都擁有屬于自己的內(nèi)存地址。操作系統(tǒng)與處理器會(huì)共同保持一套映射機(jī)制,從而令虛擬地址指向其它位置;有時(shí)候一個(gè)虛擬地址可能會(huì)對(duì)應(yīng)一個(gè)物理內(nèi)存地址,但有時(shí)候其會(huì)對(duì)應(yīng)磁盤上某個(gè)文件的一部分,有時(shí)候甚至?xí)驗(yàn)樯形捶峙涠粚?duì)應(yīng)任何對(duì)象。這是映射機(jī)制是高度細(xì)化的,通常以4096字節(jié)為一個(gè)區(qū)塊——也就是我們所說(shuō)的page單位。

用于存儲(chǔ)這一映射的數(shù)據(jù)結(jié)構(gòu)不僅包含有每個(gè)page的位置(物理內(nèi)存、磁盤以及無(wú)位置),同時(shí)(通常)也包含有另外三個(gè)用定義page保護(hù)的字位:即該page是否可以讀取、其是否可以寫入以及其是否可以執(zhí)行。在這樣的保護(hù)之下,進(jìn)程對(duì)應(yīng)的內(nèi)存區(qū)域能夠被標(biāo)記為可讀取、可寫入但不可執(zhí)行。相反,程序的可執(zhí)行代碼片段以及庫(kù)則會(huì)被標(biāo)記為可讀取、可執(zhí)行但不可寫入。

NX的一大出色之處在于,操作系統(tǒng)通過(guò)更新獲得對(duì)應(yīng)的支持能力之后,它就能夠以追溯方式應(yīng)用于現(xiàn)有程序。某些程序偶爾也會(huì)在運(yùn)行中遇到問(wèn)題。Java以及.NET當(dāng)中所使用的即時(shí)編譯器就會(huì)在運(yùn)行時(shí)環(huán)境下在內(nèi)存中生成可執(zhí)行代碼,這些代碼則要求內(nèi)存同時(shí)具備可寫入性與可執(zhí)行性(不過(guò)嚴(yán)格來(lái)講,這些代碼一般不會(huì)同時(shí)要求這兩種能力)。在NX出現(xiàn)之前,內(nèi)存始終同時(shí)具備可讀取性與可執(zhí)行性,因此這些即時(shí)編譯器完全無(wú)需針對(duì)其可讀取/可寫入緩沖區(qū)作出任何調(diào)整。但在NX出現(xiàn)之后,即時(shí)編譯器必須要確保將內(nèi)存保護(hù)機(jī)制從讀取-寫入變更為讀取-執(zhí)行。

市場(chǎng)對(duì)于NX這類安全方案的需求非常明確,特別是對(duì)于微軟陣營(yíng)來(lái)說(shuō)。早在2000年初,兩大蠕蟲的相繼出現(xiàn)就證明了微軟公司的系統(tǒng)代碼當(dāng)中存在著一些嚴(yán)重的安全問(wèn)題:Code Red于2001年7月感染了35萬(wàn)9千套運(yùn)行有微軟IIS Web Server的Windows 2000系統(tǒng),而隨后的SQL Slammer則于2003年1月侵入了超過(guò)7萬(wàn)5千套運(yùn)行有微軟SQL Server數(shù)據(jù)庫(kù)的系統(tǒng)。這些都讓軟件巨頭陷入嚴(yán)重的被動(dòng)局面當(dāng)中。

這兩種蠕蟲利用的都是內(nèi)存棧中的緩沖區(qū)溢出漏洞,而且令人吃驚的是雖然距離莫里斯蠕蟲誕生已經(jīng)分別過(guò)去了13年和15年,但它們的開(kāi)發(fā)方式幾乎完全相同。三者都將惡意負(fù)載添加到內(nèi)存棧的緩沖區(qū)內(nèi),并通過(guò)覆蓋返回地址的方式加以執(zhí)行。(惟一的區(qū)別在于,這兩位相對(duì)年輕的繼任者使用了‘蹦床’技術(shù)。相較于當(dāng)初直接將返回地址設(shè)置為內(nèi)存棧地址的方式,這二者將返回地址設(shè)置成了一條能夠傳遞至內(nèi)存棧并執(zhí)行的指令。)

當(dāng)然,這些蠕蟲方案在其它多個(gè)方面也算有所發(fā)展。Code Red的負(fù)載不僅能夠?qū)崿F(xiàn)自我復(fù)制,同時(shí)也會(huì)侵入網(wǎng)頁(yè)并試圖執(zhí)行拒絕服務(wù)攻擊。SQL Slammer則囊括了一切感染其它計(jì)算設(shè)備并在網(wǎng)絡(luò)上進(jìn)行傳播的功能組件,同時(shí)將自身體積控制在數(shù)百字節(jié)水平——這意味著受感染的機(jī)器上不會(huì)留下明顯的痕跡,而且重新啟動(dòng)之后這些痕跡就會(huì)徹底消失。這兩種蠕蟲也都開(kāi)始以互聯(lián)網(wǎng)作為著眼重點(diǎn),這也使它們超越了老祖宗莫里斯蠕蟲、成功感染了更多計(jì)算機(jī)設(shè)備。

不過(guò)問(wèn)題的關(guān)鍵在于,這樣一種能夠被直接利用的緩沖區(qū)溢出漏洞已經(jīng)算是古董級(jí)別的隱患了。正是由于兩種蠕蟲病毒的相繼出現(xiàn),才使人們對(duì)使用Windows接入互聯(lián)網(wǎng)并作為服務(wù)器系統(tǒng)產(chǎn)生了質(zhì)疑情緒。面對(duì)重重壓力,微軟公司表示將開(kāi)始認(rèn)真對(duì)待安全問(wèn)題。Windows XP SP2就是第一款真正讓安全意識(shí)融入其中的成品。它對(duì)軟件進(jìn)行了一系列調(diào)整,包括提供軟件防火墻、調(diào)整IE以避免工具欄乃至插件的靜默安裝——當(dāng)然,也實(shí)現(xiàn)了對(duì)NX的支持。

在硬件層面支持NX在2004年之后成為主流,當(dāng)時(shí)英特爾公司剛剛推出了其奔騰4處理器。而操作系統(tǒng)對(duì)于NX的支持也在Windows XP SP2邁出第一步后成為了業(yè)界共識(shí)。Windows 8在這方面表現(xiàn)得更加果斷,干脆不支持未配備NX硬件的陳舊處理器。#p#

后NX時(shí)代

隨著NX支持能力的逐步普及,緩沖區(qū)溢出也在當(dāng)下找到了新的實(shí)現(xiàn)途徑——換言之,攻擊者們發(fā)現(xiàn)了一系列能夠有效繞開(kāi)NX的技術(shù)手段。

其中最早的一種與前面提到的“蹦床”機(jī)制非常相似,它能夠通過(guò)來(lái)自其它庫(kù)或者可執(zhí)行代碼的指令繞開(kāi)系統(tǒng)在內(nèi)存棧緩沖區(qū)內(nèi)對(duì)shellcode的控制。不同于以往尋找可執(zhí)行代碼片段來(lái)直接將shellcode傳遞至內(nèi)存棧當(dāng)中,攻擊者們?nèi)缃褶D(zhuǎn)而開(kāi)始特色確實(shí)擁有實(shí)際作用的代碼片段。

而其中最理想的選項(xiàng)也許要數(shù)Unix的system()函數(shù)了。這項(xiàng)函數(shù)會(huì)獲取一個(gè)參數(shù):一條字符串的地址代表著一條將被執(zhí)行的命令行,從傳統(tǒng)角度講該參數(shù)會(huì)被傳遞至內(nèi)存棧當(dāng)中。攻擊者可以創(chuàng)建一條命令行字符串,并將其添加至內(nèi)存棧中以實(shí)現(xiàn)溢出效果,而且由于在傳統(tǒng)角度上內(nèi)存中所承載的內(nèi)容不會(huì)發(fā)生位置變動(dòng),因此該字符串的地址將以已知形式存在、并作為內(nèi)存棧中配合攻擊活動(dòng)的組成部分。在這種情況下,被覆蓋的返回地址不會(huì)再被設(shè)置為緩沖區(qū)地址,而是被設(shè)置為system()函數(shù)的地址。當(dāng)造成緩沖區(qū)溢出的函數(shù)執(zhí)行完成后,它不會(huì)返回至調(diào)用函數(shù)處,而是運(yùn)行system()以執(zhí)行攻擊者選定的命令。

這就巧妙地繞過(guò)了NX的保護(hù)。作為系統(tǒng)庫(kù)的組成部分,system()函數(shù)始終處于執(zhí)行狀態(tài)。這種漏洞利用方式并不需要在內(nèi)存棧中執(zhí)行代碼,而只需要從內(nèi)存棧中讀取已有命令行。這項(xiàng)技術(shù)被稱為“return-to-libc”(即回庫(kù)),最初是由俄羅斯計(jì)算機(jī)安全專家Solar Designer于1997年發(fā)明的。(libc也就是Unix庫(kù)的名稱,其負(fù)責(zé)實(shí)現(xiàn)多種關(guān)鍵性函數(shù),包括system()。Unix庫(kù)通常會(huì)被載入到每個(gè)單獨(dú)的Unix進(jìn)程當(dāng)中,而這也使其成為攻擊活動(dòng)的首選目標(biāo)。)

雖然確切有效,但這項(xiàng)技術(shù)在某種程度上亦可以被扼制。一般來(lái)講,函數(shù)并不會(huì)從內(nèi)存線中獲取自己的參數(shù),而傾向于將其傳遞到寄存器當(dāng)中。在命令行字符串中傳遞參數(shù)以實(shí)現(xiàn)執(zhí)行雖然想法不錯(cuò),但卻往往會(huì)因?yàn)槠渲谐霈F(xiàn)的惱人null字節(jié)而導(dǎo)致運(yùn)轉(zhuǎn)停止。另外,這會(huì)讓多個(gè)函數(shù)同時(shí)調(diào)用變得非常困難。雖然并非無(wú)法解決——同時(shí)提供多個(gè)返回地址而非一個(gè)——但我們將完全無(wú)法變更參數(shù)順序、使用返回值或者實(shí)現(xiàn)其它操作。

安全漏洞是如何造成的:緩沖區(qū)溢出

相較于利用shellcode填寫緩沖區(qū),我們現(xiàn)在選擇利用返回地址與數(shù)據(jù)序列進(jìn)行填充。這些返回地址會(huì)在目標(biāo)程序及其庫(kù)之內(nèi)傳遞對(duì)現(xiàn)有可執(zhí)行代碼片段的控制權(quán)。每個(gè)代碼片段都會(huì)執(zhí)行一項(xiàng)操作而后返回,將控制權(quán)傳遞給下一個(gè)返回地址。

在過(guò)去幾年當(dāng)中,return-to-libc技術(shù)被廣泛用于突破現(xiàn)有安全保護(hù)措施。2001年末,安全業(yè)界就曾記錄下多種通過(guò)擴(kuò)展return-to-libc執(zhí)行多函數(shù)調(diào)用的方法,并提供了解決null字節(jié)問(wèn)題的辦法。這些技術(shù)并未受到嚴(yán)格限制,因此2007年由此衍生出的另一種復(fù)雜度更高的攻擊手段開(kāi)始出現(xiàn)——這種消除了大部分上述限制的方案正是ROP,即“返回導(dǎo)向編程”技術(shù)。

其基本設(shè)計(jì)思路與“回庫(kù)”以及“蹦床”差不多,但卻從普適性方面更進(jìn)了一步。“蹦床”是利用單一代碼片段將可執(zhí)行shellcode添加到緩沖區(qū)當(dāng)中,而ROP則是利用大量被稱為“gadget”的代碼片段。每個(gè)gadget都遵循一種特定模式:它會(huì)執(zhí)行某些操作(包括向寄存器中添加一個(gè)值、向內(nèi)存中寫入或者添加兩個(gè)寄存器等等),而后加上一條返回指令。x86的固有特性讓“蹦床”的設(shè)計(jì)思路在這邊再度起效;進(jìn)程當(dāng)中所加載的系統(tǒng)庫(kù)中包含著成百上千個(gè)能夠被解釋為“執(zhí)行一項(xiàng)操作,而后返回”的序列,因此它們也成為了實(shí)現(xiàn)ROP攻擊的潛在基礎(chǔ)。

這些gadget彼此之間通過(guò)一條長(zhǎng)返回地址序列(也可以是其它任何有用或者必需的數(shù)據(jù))被串連在一起,并作為緩沖區(qū)溢出的組成部分被寫入至內(nèi)存棧當(dāng)中。返回指令則很少甚至完全無(wú)需借助處理器中的calling函數(shù)——而是單純利用returning函數(shù)——在gadget之間跳轉(zhuǎn)。值得注意的是,人們發(fā)現(xiàn)可資利用的gadget的數(shù)量與種類如此之多(至少在x86平臺(tái)上是如此),攻擊者幾乎能夠利用它們實(shí)現(xiàn)任何目標(biāo)。這一奇特的x86子集在特定使用方式之下往往會(huì)呈現(xiàn)出完備的圖靈特性(雖然其具體功能范圍取決于特定程序所加載的庫(kù)類型,并以此決定哪些gadget能夠切實(shí)起效)。

正如“回庫(kù)”技術(shù)一樣,所有可執(zhí)行代碼實(shí)際上都來(lái)源于系統(tǒng)庫(kù),因此NX保護(hù)也就無(wú)處發(fā)力了。這套方案的出色靈活性意味著,攻擊者能夠甚至能夠?qū)崿F(xiàn)原本依靠“回庫(kù)”技術(shù)所難于完成的任務(wù),包括調(diào)用從寄存器內(nèi)獲取參數(shù)的函數(shù)或者將來(lái)自某一函數(shù)的返回值作為另一函數(shù)的參數(shù)等等。

ROP負(fù)載可謂變化多端。有時(shí)候它們只以簡(jiǎn)單的“創(chuàng)建一個(gè)shell“形式的代碼出現(xiàn),但大多數(shù)情況下攻擊者都會(huì)利用ROP來(lái)調(diào)用某項(xiàng)系統(tǒng)函數(shù),從而變更某一內(nèi)存page的NX狀態(tài)、將其由可寫入轉(zhuǎn)變?yōu)榭蓤?zhí)行。通過(guò)這種方式,攻擊者將能夠利用便捷的非ROP負(fù)載在執(zhí)行過(guò)程中實(shí)現(xiàn)ROP。

隨機(jī)性提升

NX的這一弱點(diǎn)早已為安全專家們所了解,而這同時(shí)也成為最大的薄弱環(huán)節(jié)在各類攻擊活動(dòng)中反復(fù)出現(xiàn):攻擊者們?cè)趧?dòng)手之前就已經(jīng)掌握了內(nèi)存棧與系統(tǒng)庫(kù)的確切內(nèi)存地址。正因?yàn)楦黝惞艋顒?dòng)皆以此類知識(shí)作為基礎(chǔ),因此解決安全隱患的最佳途徑就是使這些知識(shí)失去效用。有鑒于此,地址空間布局隨機(jī)化(簡(jiǎn)稱ASLO)技術(shù)應(yīng)運(yùn)而生:它會(huì)對(duì)內(nèi)存棧的位置、內(nèi)存內(nèi)庫(kù)以及可執(zhí)行代碼的位置進(jìn)行隨機(jī)化處理。一般來(lái)講,這些位置在程序每次運(yùn)行時(shí)、系統(tǒng)啟動(dòng)時(shí)或者二者同時(shí)發(fā)生時(shí)都會(huì)出現(xiàn)變化。

這極大地增加了攻擊活動(dòng)的實(shí)施難度,因?yàn)閹缀踉谝灰怪g,攻擊者根本不知道哪些ROP指令片段會(huì)駐留在內(nèi)存當(dāng)中、甚至弄不清楚要實(shí)現(xiàn)溢出的緩沖區(qū)到底在哪里。

ASLR在多個(gè)方面與NX攜手合作,因?yàn)樗饕?fù)責(zé)封殺“回庫(kù)”以及“返回導(dǎo)向編程”這兩大NX未能堵住的缺口。然而遺憾的是,它的介入深度有些過(guò)度。除了即時(shí)編譯器以及少數(shù)其它非常用程序之外,NX幾乎能夠被成功添加到任何現(xiàn)有軟件當(dāng)中。但ASLR在這方面則問(wèn)題多多,程序及庫(kù)需要確保自身的正常運(yùn)行不會(huì)受到內(nèi)存地址隨機(jī)變化的影響。

舉例來(lái)說(shuō),在Windows當(dāng)中,DLL就基本不會(huì)受到內(nèi)存地址隨機(jī)化的影響。DLL在Windows系統(tǒng)上始終支持利用不同內(nèi)存地址加載數(shù)據(jù),但EXE文件就沒(méi)這么幸運(yùn)了。在ASLR出現(xiàn)之前,EXE文件會(huì)始終以0x0040000作為起始加載位置,并安全地以此為運(yùn)行前提。但ASLR出現(xiàn)之后,情況就完全不同了。為了確保不出現(xiàn)差錯(cuò),Windows在默認(rèn)條件下要求EXE可執(zhí)行文件對(duì)ASLR提供支持,并提供啟用選項(xiàng)。不過(guò)出于安全的考慮,即使對(duì)應(yīng)程序并未明確表達(dá)支持能力,Windows仍然默認(rèn)在所有可執(zhí)行程序及庫(kù)中啟用該選項(xiàng)。而且在大多數(shù)情況下,結(jié)果還是令人滿意的。

比較糟心的情況出現(xiàn)在x86 Linux系統(tǒng)當(dāng)中。在使用ASLR的情況下,Linux平臺(tái)的性能損失可能高達(dá)26%。除此之外,這套方案明確要求可執(zhí)行程序與庫(kù)以ASLR支持模式進(jìn)行編譯。這意味著管理員根本無(wú)法像在Windows環(huán)境中那樣對(duì)ASLR進(jìn)行授權(quán)。(x64也沒(méi)能徹底解決Linux的性能損失問(wèn)題,不過(guò)損失程度得到了顯著降低。)

在ASLR啟用之后,它能夠?yàn)橄到y(tǒng)提供良好的緩沖區(qū)溢出狀況保護(hù)。不過(guò)ASLR本身還遠(yuǎn)遠(yuǎn)稱不上完美——舉例來(lái)說(shuō),其能夠提供的隨機(jī)水平就比較有限,而且這種情況在32位系統(tǒng)中表現(xiàn)得尤為嚴(yán)重。盡管內(nèi)存空間所能提供的地址數(shù)量高達(dá)40億個(gè),但并不是所有地址都能夠被用于加載庫(kù)或者旋轉(zhuǎn)內(nèi)存棧。

相反,其分配方式會(huì)受到各種約束,而且其中一部分還屬于泛用性目標(biāo)。總體而言,操作系統(tǒng)傾向于將各個(gè)庫(kù)以相鄰方式進(jìn)行加載,以保證各個(gè)進(jìn)程的地址空間首尾相連,這樣就能盡可能多地為應(yīng)用程序運(yùn)行提供充裕的內(nèi)存容量。大家當(dāng)然也不希望讓一個(gè)庫(kù)以256 MB為單位遍布在整個(gè)內(nèi)存空間當(dāng)中——256 MB是我們能夠作為整體進(jìn)行分配的最大內(nèi)存單位,這種作法會(huì)限制應(yīng)用程序處理大型數(shù)據(jù)庫(kù)集的能力。

可執(zhí)行文件和庫(kù)通常在啟動(dòng)后必須進(jìn)行加載,且至少被包含在一個(gè)page當(dāng)中。通常來(lái)講,這意味著它們必須以4096整數(shù)倍的形式進(jìn)行加載。平臺(tái)也可以對(duì)內(nèi)存棧采用類似的協(xié)議;舉例來(lái)說(shuō),Linux會(huì)以16字節(jié)的整數(shù)倍形式啟動(dòng)內(nèi)存棧。迫于內(nèi)存的壓力,系統(tǒng)有時(shí)候需要對(duì)隨機(jī)性進(jìn)行進(jìn)一步削減,從而保證一切能夠順利運(yùn)行。

這種變化看起來(lái)影響不大,但卻意味著攻擊者有時(shí)候可以猜測(cè)到某個(gè)地址的可能位置,而且有相當(dāng)高的機(jī)率猜測(cè)成功。即使猜對(duì)的可能性非常低——例如二百五十六分之一——在某種情況下攻擊者依然足以利用其實(shí)施惡意活動(dòng)。當(dāng)攻擊某臺(tái)會(huì)自動(dòng)重啟崩潰進(jìn)程的Web服務(wù)器時(shí),256次攻擊中有255次出現(xiàn)崩潰完全不是什么大問(wèn)題。只需要經(jīng)過(guò)簡(jiǎn)單重啟,攻擊者就能再次嘗試下一個(gè)內(nèi)存地址。

不過(guò)在64位系統(tǒng)當(dāng)中,由于地址空間變得更加龐大,單純的猜測(cè)就不足以解決問(wèn)題了。攻擊者面對(duì)的很可能是上百萬(wàn)個(gè)——甚至數(shù)十億個(gè)——潛在內(nèi)存地址,機(jī)率如此之低也就不值得我們?yōu)橹畱n心了。

另外,對(duì)于攻擊者來(lái)說(shuō),猜測(cè)與崩潰這段手段不適用于瀏覽器之類的場(chǎng)景;沒(méi)有哪個(gè)用戶會(huì)連續(xù)對(duì)瀏覽器進(jìn)行256次重啟來(lái)“幫助”攻擊者完成試探。也就是說(shuō),在這種情況下NX與ASLR的聯(lián)手協(xié)作將讓攻擊者們變得無(wú)機(jī)可乘。

但如果有其它幫助手段存在,情況就不一樣了。在瀏覽器當(dāng)中的一種常見(jiàn)實(shí)現(xiàn)途徑在于利用JavaScript或者Flash——二者都包含著有能力生成可執(zhí)行代碼的即時(shí)編譯器——向內(nèi)存中塞進(jìn)大量經(jīng)過(guò)精心構(gòu)建的可執(zhí)行代碼。由此生成的大型NOP sled也就是我們目前經(jīng)常提到的“heap spraying”(也就是堆噴射)技術(shù)。另一種實(shí)現(xiàn)方式則是找出某個(gè)有可能泄露庫(kù)或者棧內(nèi)存地址的次級(jí)漏洞,從而幫助攻擊者獲得構(gòu)建自定義ROP返回地址組所必需的相關(guān)信息。

第三種方法在瀏覽器當(dāng)中比較常見(jiàn):利用那些不支持ASLR的代碼庫(kù)。舉例來(lái)說(shuō),Adobe PDF插件或者微軟Office瀏覽器插件的某些早期版本就不支持ASLR,而且Windows在默認(rèn)情況下不會(huì)強(qiáng)制在非ASLR代碼中啟用該功能。如果攻擊者能夠強(qiáng)制載入這類庫(kù)(舉例來(lái)說(shuō),通過(guò)在隱藏的瀏覽器幀中加載某個(gè)PDF文件),那么他們就能夠直接繞過(guò)ASLR,即利用這些非ASLR庫(kù)容納自己的ROP負(fù)載。

一場(chǎng)永遠(yuǎn)休止的戰(zhàn)爭(zhēng)

攻擊技術(shù)與保護(hù)技術(shù)之爭(zhēng)就像是貓與老鼠的競(jìng)逐。像ASLR以及NX這樣強(qiáng)大的保護(hù)系統(tǒng)能夠提高安全漏洞的利用門檻,從而在一定時(shí)期內(nèi)徹底阻止緩沖區(qū)溢出這類簡(jiǎn)單漏洞的肆虐。然而聰明的攻擊者們?nèi)匀荒軌蛘业狡渌踩毕荩⑺鼈兘M合起來(lái)以繼續(xù)發(fā)動(dòng)攻勢(shì)。

這場(chǎng)軍備競(jìng)賽仍在不斷升級(jí)。微軟公司的EMET(即‘增強(qiáng)緩解體驗(yàn)工具包’)當(dāng)中包含一系列半實(shí)驗(yàn)性保護(hù)方案,旨在檢測(cè)“堆噴射”乃至其它任何以ROP為基礎(chǔ)嘗試?yán)锰囟ǜ呶:瘮?shù)的行為。不過(guò)在這場(chǎng)永無(wú)休止的數(shù)字化對(duì)抗中,這些安全技術(shù)同樣在持續(xù)遭到淘汰。這并不是說(shuō)它們沒(méi)有作用——各類新型保護(hù)技術(shù)的出現(xiàn)確實(shí)提高了漏洞利用的難度與成本——但大家必須正視一個(gè)現(xiàn)實(shí),即警惕之心須長(zhǎng)久保持。

英文:How security flaws work: The buffer overflow

責(zé)任編輯:藍(lán)雨淚 來(lái)源: 51CTO.com
相關(guān)推薦

2015-09-22 14:49:41

網(wǎng)絡(luò)安全技術(shù)周刊

2020-08-10 08:37:32

漏洞安全數(shù)據(jù)

2018-11-01 08:31:05

2022-08-09 08:31:40

C -gets函數(shù)漏洞

2011-11-15 16:00:42

2019-02-27 13:58:29

漏洞緩沖區(qū)溢出系統(tǒng)安全

2017-01-09 17:03:34

2020-10-27 09:51:18

漏洞

2014-07-30 11:21:46

2018-01-26 14:52:43

2009-09-24 18:16:40

2019-03-06 09:00:38

ASLRLinux命令

2010-09-29 15:59:04

2010-12-27 10:21:21

2017-08-30 20:49:15

2011-02-24 09:21:31

2015-03-06 17:09:10

2010-10-09 14:45:48

2010-09-08 15:43:18

2011-03-23 12:39:44

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 国产一区二区免费在线 | 亚洲欧美日韩中文字幕一区二区三区 | 国产一区二区精华 | 国产精品成人av | 在线观看国产视频 | 久久久久国产精品www | 日韩毛片网 | 天堂在线www | 黄色av网站在线免费观看 | 九九久久在线看 | 国产剧情一区 | av天天看| 中文字幕精品一区二区三区精品 | 欧美a免费| 亚洲精品久久久久久久久久久久久 | 一级a性色生活片久久毛片波多野 | 成人免费久久 | 久久最新| 欧美一区二区三区一在线观看 | 欧美精品久久久久久 | 亚洲欧美日本在线 | 午夜影院在线观看 | 中文字幕亚洲视频 | 午夜一区二区三区在线观看 | 97色综合| 成人免费小视频 | 亚洲免费婷婷 | 一级a爱片久久毛片 | 国产精品国产精品国产专区不卡 | 操操操操操| 97人人澡人人爽91综合色 | 日韩精品视频中文字幕 | 国产精品日本一区二区不卡视频 | 婷婷开心激情综合五月天 | av一区在线观看 | 欧美激情在线一区二区三区 | 免费 视频 1级 | av大片在线| 男女久久久| 国产精品久久国产精品久久 | 毛片入口|