精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

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

譯文
安全 漏洞
自1988年莫里斯蠕蟲誕生以來,緩沖區溢出漏洞就威脅著從Linux到Windows的各類系統環境。

自1988年莫里斯蠕蟲誕生以來,緩沖區溢出漏洞就威脅著從Linux到Windows的各類系統環境。

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

[[147704]]

但頗為諷刺的是,作為一種肆虐多年的安全隱患,緩沖區溢出漏洞的核心卻只是由一種實踐性結果衍生出的簡單bug。計算機程序會頻繁使用多組讀取自某個文件、網絡甚至是源自鍵盤輸入的數據。程序為這些數據分配一定量的內存塊——也就是緩沖區——作為存儲資源。而所謂緩沖區漏洞的產生原理就是,寫入或者讀取自特定緩沖區的數據總量超出了該緩沖區所能容納量的上限。

事實上,這聽起來像是一種相當愚蠢、毫無技術含量的錯誤。畢竟程序本身很清楚緩沖區的具體大小,因此我們似乎能夠很輕松地確保程序只向緩沖區發送不超出上限的數據量。這么想確實沒錯,但緩沖區溢出仍在不斷出現,并始終成為眾多安全攻擊活動的導火線。

為了了解緩沖區溢出問題的發生原因——以及為何其影響如此嚴重——我們需要首先談談程序是如何使用內存資源以及程序員是如何編寫代碼的。(需要注意的是,我們將以堆棧緩沖區溢出作為主要著眼對象。雖然這并不是惟一一種溢出問題,但卻擁有著典型性地位以及極高的知名度。)

堆疊起來

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

在這方面,最重要的核心概念就是內存地址。內存當中每個獨立的字節都擁有一個與之對應的數值地址。當處理器從主內存(也就是RAM)中加載或者向其中寫稿數據時,它會利用內存地址來確定讀取或寫入所指向的位置。系統內存并不單純用于承載數據,它同時也被用于執行那些構建軟件的可執行代碼。這意味著處于運行中的程序,其每項功能都會擁有對應的地址。

在計算機制發展的早期階段,處理器與操作系統使用的是物理內存地址:每個內存地址都會直接與RAM中的特定位置相對應。盡管目前某些現代操作系統仍然會有某些組成部分繼續使用這類物理內存地址,但現在所有操作系統都會在廣義層面采用另一種機制——也就是虛擬內存。

在虛擬內存機制的幫助下,內存地址與RAM中物理位置直接對應的方式被徹底打破。相反,軟件與處理器會利用虛擬內存地址保證自身運轉。操作系統與處理器配合起來共同維護著一套虛擬機內存地址與物理內存地址之間的映射機制。

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

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

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

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

我們首先需要關注的就是構建程序的各類可執行代碼與庫。主可執行代碼及其全部配套庫都會被載入到對應進程的地址空間當中,而且所有組成部分都擁有自己的對應內存地址。

其次就是程序用于存儲自身數據的內存,這部分內存資源通常被稱為heap、也就是內存堆。舉例來說,內存堆可以用于存儲當前正在編輯的文檔、瀏覽的網頁(包括其中的全部JavaScript對象、CSS等等)或者當前游戲的地圖資源等等。

第三也是最重要的一項概念即call stack,即調用堆——也簡稱為棧。內存棧可以說是最復雜的相關概念了。進程中的每個分線程都擁有自己的內存棧。棧其實就是一個內存塊,用于追蹤某個線程當前正在運行的函數以及所有前趨函數——所謂前趨函數,是指那些當前函數需要調用的其它函數。舉例來說,如果函數a調用函數b,而函數b又調用函數c,那么棧內所包含的信息則依次為a、b和c。

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

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

調用堆棧屬于通用型“棧”數據結構的一個特殊版本。棧是一種用于存儲對象且大小可變的結構。新對象能夠被加入到(即’push‘)該棧的一端(一般為對應內存棧的’top‘端,即頂端),也可從棧中進行移除(即’pop’)。只有內存棧頂端的部分能夠通過push或者pop進行修改,因此棧會強制執行一種排序機制:最近添加進入的項目也會被首先移除。而首個添加進入的項目則會被最后移除。

調用堆棧最為重要的任務就是存儲返回地址。在大多數情況下,當一款程序調用某項函數時,該函數會按照既定設計發生作用(包括調用其它函數),并隨后返回至調用它的函數處。為了能夠切實返回至正確的調用函數,必須存在一套記錄系統來注明進行調用的源函數:即應當在函數調用指令執行之后從指令中恢復回來。這條指令所對應的地址就被稱為返回地址。棧用于維護這些返回地址,就是說每當有函數被調用時,返回地址都會被push到其內存棧當中。而在函數返回之后,對應返回地址則從內存棧中被移除,處理器隨后開始在該地址上執行指令。

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

esp始終容納有棧頂端的對應地址。每一次有數據被添加到該棧中時,esp中的值都會降低。而每當有數據從棧中被移除時,esp的值則相應增加。這意味著該棧的值出現“下降”時,則代表有更多數據被添加到了該棧當中,而esp中的存儲地址則會不斷向下方移動。不過盡管如此,esp所使用的參考內存位置仍然被稱為該內存棧的“頂端”。

eip 為現有執行指令提供內存地址,而處理器則負責維護eip本身的正常運作。處理器會從內存當中根據eip增量讀取指令流,從而保證始終能夠獲得正確的指令地址。x86擁有一項用于函數調用的指令,名為call,另一項用于從函數處返回的指令則名為ret。

call 會獲取一個操作數,也就是欲調用函數的地址(當然,我們也可以利用其它方式來獲取欲調用函數的地址)。當執行call指令時,棧指針esp會通過4個字節(32位)來表現,而緊隨call之后的指令地址——也就是返回地址——則會被寫入至當前esp的參考內存位置。換句話說,返回地址會被添加至內存棧中。接下來,eip會將該地址指定為call的操作數,并以該地址為起始位置進行后續操作。

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

【視頻】

在實際操作中了解call與ret。

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

這種便捷性讓內存棧成為一套邏輯空間,能夠存儲歸屬于函數的各類變量。每項函數擁有256字節的緩沖空間來讀取用戶的輸入內容。簡單來講,我們只需要在棧指針中減去256這一數值就能創建出該緩沖區。而在函數執行結束時,向棧指針內添加添加256就能丟棄這個緩沖區。

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

當我們正確使用程序時,鍵盤輸入內容會被存儲至name緩沖區中,隨后為null(即0)字節。幀指針與返回地址則保持不變。

但這種處理方式也存在局限。內存棧并不適合保存規模龐大的對象;內存的整體可用容量通常在線程創建之時就被確定下來了,而且通常大小為1 MB。因此,那些大型對象必須被保存在內存堆中。棧也不適合保存那些需要長久存在,甚至生命周期比單一函數調用更長的對象。由于每個分配的內存棧都會在函數執行完成后被撤銷,因此任何存在于該棧中的對象將無法在函數結束后繼續駐留。不過存在于內存堆中的對象則不受此類限制,它們能夠獨立于函數之外實現長期駐留。

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

最后,內存棧通常被用于向函數傳遞參數。調用函數會將每個參數添加到內存棧中,而受調用函數之后則能夠將這些參數移除出去。這并不是惟一一種參數傳遞方式——舉例來說,也可以在寄存器內部進行參數傳遞——但卻是最為靈活的方式。

函數在內存棧上的所有具體內容——包括其本地變量、spilling寄存器操作以及任何準備傳遞給其它函數的參數——被整體稱為一個“棧幀”。由于棧幀中的數據會被廣泛使用,因此需要一種能夠實現快速引用的辦法與之配合。

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

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

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

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

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

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

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

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

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

很明顯,這會導致程序數據遭到破壞,但緩存區溢出還會造成其它更加嚴重的后果:通常會影響到代碼執行。之所以會出現這種情況,是因為緩沖區溢出不僅會覆蓋數據,同時也可能覆蓋內存棧中的返回地址乃至其它更為重要的內容。返回地址負責控制處理器在完成當前函數之后,接下來執行哪些指令。返回地址正常來說應該處于調用函數之內的某個位置,但如果由于緩沖區溢出而被覆蓋,那么返回地址的指向位置將變得隨機而不可控制。如果攻擊者能夠利用這種緩沖區溢出手段,則能夠選定處理器接下來要執行的代碼位置。

在這一過程中,攻擊者可能并沒有什么理想的、便捷的“設備入侵”方法可供選擇,但這并不會影響惡意活動的發生。用于覆蓋返回地址的緩沖區同時也可以被用于保存一小段可執行代碼,也就是所謂shellcode,其隨后將能夠下載一段惡意可執行代碼、開啟某個網絡連接或者是實現其它一些攻擊手段。

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

攻擊者的工具箱

在理想狀態下——當然,這是從攻擊者的角度出發的——被覆蓋的返回地址可以就是緩沖區的所在位置。當程序從文件或者網絡處讀取輸入數據時,往往就會符合這一條件。

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

【視頻】

查看整個緩沖區溢出過程。在這段視頻中,我們將shellcode添加到了緩沖區內,而后通過執行以棋牌室返回地址。我們的shellcode運行了Windows計算器程序。

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

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

為了實際這一目標,攻擊者可以選擇多種技術手段。代碼片段可以將包含有null字符的shellcode轉換為具備同等作用的形式以避免出現問題。它們甚至能夠處理更為嚴格的限制,例如一條已被篡改的函數可能只接收能夠通過標準鍵盤進行輸入的結果。

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

即使無法進行返回地址篡改,這種狀況也可被攻擊者們用于重新定向。程序及其全部相關庫的存在意味著,內存當中可以駐留可執行代碼。大部分此類可執行代碼都能夠擁有屬于自己的“安全”地址,也就是說其中不包含任何null字節。

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

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

有時候我們很難利用緩沖區地址來覆蓋返回地址。為了解決這個問題,我們可以利用目標程序(或者其對應庫)中的特定可執行代碼片段地址來覆蓋返回地址。這部分代碼片段能幫助我們對緩沖區位置進行轉換。

之所以這種方式能夠奏效,是因為正如前面提提到,程序及其配套庫在每時運行時都會使用同樣的內存地址——即使是多次啟動甚至在不同設備之上都不會改變這一點。而非常有趣的是,用于提供“蹦床”的庫本身并不需要執行call esp指令。該庫只需要提供兩個字節(在本次示例中為0xff與0xd4)并保證彼此相鄰即可。它們可以作為其它指令中的組成部分甚至直接以數字形式存在;x86對于這類內容并不挑剔。另外,x86的指令長度可以相當之長(最高為15字節!)并指向任意地址。如果處理器從中間部分讀取某條指令——例如在一條長度為4字節的指令中從第二個字節開始讀取——那么最終的執行結果可能會完全不同、但卻仍然切實生效。考慮到這一點,攻擊者確實可以很輕松地找到可資利用的“蹦床”。

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

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

你的錯、他的錯——都是C的錯

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

作為C語言自身阻礙安全開發實踐的一項實例,我們在這里要著重談談gets()。作為一項函數,gets()會獲取一條參數——也就是一個緩沖區——并從標準輸入內容中(通常意味著’鍵盤輸入內容‘)讀取一行數據,而后將其添加到緩沖區當中。細心的朋友可能已經注意到,gets()當中并不會對將被添加至緩沖區內的參數長度作出限制,而且作為C語言設計中的一種有趣現象,我們沒辦法利用gets()了解緩沖區的實際大小。這是因為gets()并不會對輸入內容的大小作出任何要求:它只負責從標準輸入內容中讀取數據——直到電腦前的操作者按下回車——而后嘗試將全部內容添加到緩沖區內,即使操作者寫入的內容遠遠超出了緩沖區容納能力,gets()也完全不予理會。

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

而作為誕生于1988年的世界首個可通過互聯網傳輸的自我復制惡意軟件,莫里斯蠕蟲利用的恰恰是這項函數。BSD 4.3 fingerd程序會通過端口79對網絡連接進行監聽,也就是我們常說的finger端口。事實上,finger也是一個非常古老的Unix程序,其作為網絡協議存在并負責識別是誰登錄到了遠程系統當中。它的使用方式分為兩種;其一是遠程系統可以利用它來查詢當前已經登錄的每位用戶,其二則是用于查詢特定用戶名并告知我們與該用戶相關的部分信息。

每當有連接出現在finger的后臺進程當中,它都會利用gets()從網絡中讀取數據并將其添加到內存棧中一個512字節的緩沖區內。在通常操作中,fingerd會隨后生成finger程序,并在可能的情況下向其傳遞相關用戶名。該finger程序才是真正負責監聽用戶接入或者提供與特定用戶相關信息的主體,而fingerd本身僅僅負責監聽網絡并在需要時啟動finger。

鑒于惟一的“真實”參數基本只會是用戶名,因此512字節的緩沖區設定已經不算小了。應該沒人會設定一個長達512位的用戶名——不過系統本身并不會對此作出強制要求,因為在這里負責內容獲取工作的正是臭名昭著的gets()函數。當我們通過網絡發出超過512字節的用戶名時,fingerd就會乖乖地造成緩沖區溢出狀況。而這也正是Robert Morris的具體作法:他向fingerd發送了537字節的數據內容(其中包含537個字節外中一個換行符,這直接導致gets()停止讀取輸入數據),順利實現緩沖區溢出并覆蓋了返回地址。在此之后,返回地址被輕松設置為內存棧中的緩沖區地址。

莫里斯蠕蟲的可執行負載非常簡單。它會發起400條NOP指令,從而讓內存棧布局出現輕微的變化,而后再接上一小段代碼片段。這些代碼會生成一條shell,即/bin/sh。這是攻擊負載當中很常見的選擇;fingerd程序會以root權限運行,因此在遭到攻擊并被迫運行shell時,該shell也將擁有root權限。另外,fingerd會被引導至網絡當中,這意味著其接收的“鍵盤輸入內容”可以實際來源于網絡傳輸,并將輸出結果通過網絡發送出去。這兩大特性都明顯昭示其為shell所利用的潛在可能性,也就是說這一root shell現在已經能夠為攻擊者所遠程操控。

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

即使C語言的字符串處理函數能夠對指向緩沖區的參數長度作出限制,同樣的錯誤及溢出狀況仍然得不到徹底解決。C語言分別為strcat()與strcpy()提供一對姐妹函數,分別名為strncat()與strncpy()。名稱當中額外的n代表的正是其所獲取參數的長度。但正如很多資深C語言程序員們所知,這個n并不是將要寫入的緩沖區的具體大小;相反,它其實是來源處將要進行復制的字符數量。如果來源提供的數據量超出了對應字符限制(因為達到了null字節的位置),那么strncpy()與strncat()將會通過向目標位置復制更多null字節的方式來補足差額。換句話來說,這些函數仍然完全不關心目標緩沖區的實際大小。

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

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

修復此類漏洞

無需贅言,隨著人類智慧的進一步發展,我們如今已經擁有了更多更出色的語言選項——它們會對指向緩沖區的讀取與寫入操作進行驗證,這就徹底阻斷了溢出問題的發生。由Mozilla打造的Rust等編譯語言、安全運行時環境的杰出代表Java以及.NET,外加Python、JavaScript、Lua以及Perl等虛擬化腳本語言都徹底解決了緩沖區溢出的問題(當然,.NET仍然允許開發人員直接關閉所有保障措施,在這種選項設置之下緩沖區溢出會再度成為可能)。

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

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

盡管如此,C語言和它的整個大家族卻仍然廣泛存在——當然也包括由其帶來的緩沖區溢出風險。

目前已經有不少相關舉措努力阻止溢出錯誤影響到開發人員以及使用者。在開發過程中,我們可以選擇多種工具對源代碼進行分析,并通過程序運行來檢測其中是否存在危險結構或者溢出錯誤,這就避免了此類bug被實際添加到軟件成品當中。AddressSantizer等新型工具以及Valgrind等傳統方案都可以實現上述功能。

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

某些系統的存在目的正是讓一部分特定攻擊活動變得更難實現。當前的多套Linux系統補丁就能夠確保系統庫全部被加載在底端內存地址處,從而保證其地址中至少包含一個null字節。在這種情況下,攻擊者將很難利用C字符串處理方式在緩沖區溢出攻擊中使用這些地址。

其它防御機制也更為普遍。目前很多編譯器都擁有某種類型的內存棧保護機制,其會將一個名為“canary”(意為金絲雀)的運行時檢測值寫入到返回地址存儲位置附近的內存棧末尾。在每項函數執行結束之前,系統都會檢查該值以確定返回指令是否遭到了修改。如果該canary值發生了變化(因為其在緩沖區溢出中被覆蓋),那么該程序將立即崩潰而非繼續執行。

而最重要的單項保護手段之一正是名為W^X(意為’單純寫入或執行‘)、DEP(意為’數據執行保護‘)、NX(意為‘不執行’)、XD(意為‘執行禁用’)、EVP(意為‘增強病毒保護’,AMD公司往往比較喜歡使用這一術語)、XN(即‘從不執行’)等一系列措施。它們所采取的概念非常簡單。這些系統會盡可能讓內存擁有可寫入能力(適用于緩沖區)或者可執行能力(適用于庫及程序代碼),但不會使其二者兼備。因此,即使攻擊者能夠使緩沖區出現溢出并控制其中的返回地址,處理器最終仍然會拒絕執行對應的shellcode。

無論具體使用什么樣的名稱,這都是一項重要的技術,這主要是因為其能夠在無需額外成本的前提下起效——這類方案使用的是處理器自身內置的、作為虛擬內存硬件支持而存在的保護機制。

正如之前所提到,在虛擬內存當中每個進程都擁有屬于自己的內存地址。操作系統與處理器會共同保持一套映射機制,從而令虛擬地址指向其它位置;有時候一個虛擬地址可能會對應一個物理內存地址,但有時候其會對應磁盤上某個文件的一部分,有時候甚至會因為尚未分配而不對應任何對象。這是映射機制是高度細化的,通常以4096字節為一個區塊——也就是我們所說的page單位。

用于存儲這一映射的數據結構不僅包含有每個page的位置(物理內存、磁盤以及無位置),同時(通常)也包含有另外三個用定義page保護的字位:即該page是否可以讀取、其是否可以寫入以及其是否可以執行。在這樣的保護之下,進程對應的內存區域能夠被標記為可讀取、可寫入但不可執行。相反,程序的可執行代碼片段以及庫則會被標記為可讀取、可執行但不可寫入。

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

市場對于NX這類安全方案的需求非常明確,特別是對于微軟陣營來說。早在2000年初,兩大蠕蟲的相繼出現就證明了微軟公司的系統代碼當中存在著一些嚴重的安全問題:Code Red于2001年7月感染了35萬9千套運行有微軟IIS Web Server的Windows 2000系統,而隨后的SQL Slammer則于2003年1月侵入了超過7萬5千套運行有微軟SQL Server數據庫的系統。這些都讓軟件巨頭陷入嚴重的被動局面當中。

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

當然,這些蠕蟲方案在其它多個方面也算有所發展。Code Red的負載不僅能夠實現自我復制,同時也會侵入網頁并試圖執行拒絕服務攻擊。SQL Slammer則囊括了一切感染其它計算設備并在網絡上進行傳播的功能組件,同時將自身體積控制在數百字節水平——這意味著受感染的機器上不會留下明顯的痕跡,而且重新啟動之后這些痕跡就會徹底消失。這兩種蠕蟲也都開始以互聯網作為著眼重點,這也使它們超越了老祖宗莫里斯蠕蟲、成功感染了更多計算機設備。

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

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

后NX時代

隨著NX支持能力的逐步普及,緩沖區溢出也在當下找到了新的實現途徑——換言之,攻擊者們發現了一系列能夠有效繞開NX的技術手段。

其中最早的一種與前面提到的“蹦床”機制非常相似,它能夠通過來自其它庫或者可執行代碼的指令繞開系統在內存棧緩沖區內對shellcode的控制。不同于以往尋找可執行代碼片段來直接將shellcode傳遞至內存棧當中,攻擊者們如今轉而開始特色確實擁有實際作用的代碼片段。

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

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

雖然確切有效,但這項技術在某種程度上亦可以被扼制。一般來講,函數并不會從內存線中獲取自己的參數,而傾向于將其傳遞到寄存器當中。在命令行字符串中傳遞參數以實現執行雖然想法不錯,但卻往往會因為其中出現的惱人null字節而導致運轉停止。另外,這會讓多個函數同時調用變得非常困難。雖然并非無法解決——同時提供多個返回地址而非一個——但我們將完全無法變更參數順序、使用返回值或者實現其它操作。

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

相較于利用shellcode填寫緩沖區,我們現在選擇利用返回地址與數據序列進行填充。這些返回地址會在目標程序及其庫之內傳遞對現有可執行代碼片段的控制權。每個代碼片段都會執行一項操作而后返回,將控制權傳遞給下一個返回地址。

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

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

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

正如“回庫”技術一樣,所有可執行代碼實際上都來源于系統庫,因此NX保護也就無處發力了。這套方案的出色靈活性意味著,攻擊者能夠甚至能夠實現原本依靠“回庫”技術所難于完成的任務,包括調用從寄存器內獲取參數的函數或者將來自某一函數的返回值作為另一函數的參數等等。

ROP負載可謂變化多端。有時候它們只以簡單的“創建一個shell“形式的代碼出現,但大多數情況下攻擊者都會利用ROP來調用某項系統函數,從而變更某一內存page的NX狀態、將其由可寫入轉變為可執行。通過這種方式,攻擊者將能夠利用便捷的非ROP負載在執行過程中實現ROP。

隨機性提升

NX的這一弱點早已為安全專家們所了解,而這同時也成為最大的薄弱環節在各類攻擊活動中反復出現:攻擊者們在動手之前就已經掌握了內存棧與系統庫的確切內存地址。正因為各類攻擊活動皆以此類知識作為基礎,因此解決安全隱患的最佳途徑就是使這些知識失去效用。有鑒于此,地址空間布局隨機化(簡稱ASLO)技術應運而生:它會對內存棧的位置、內存內庫以及可執行代碼的位置進行隨機化處理。一般來講,這些位置在程序每次運行時、系統啟動時或者二者同時發生時都會出現變化。

這極大地增加了攻擊活動的實施難度,因為幾乎在一夜之間,攻擊者根本不知道哪些ROP指令片段會駐留在內存當中、甚至弄不清楚要實現溢出的緩沖區到底在哪里。

ASLR在多個方面與NX攜手合作,因為它主要負責封殺“回庫”以及“返回導向編程”這兩大NX未能堵住的缺口。然而遺憾的是,它的介入深度有些過度。除了即時編譯器以及少數其它非常用程序之外,NX幾乎能夠被成功添加到任何現有軟件當中。但ASLR在這方面則問題多多,程序及庫需要確保自身的正常運行不會受到內存地址隨機變化的影響。

舉例來說,在Windows當中,DLL就基本不會受到內存地址隨機化的影響。DLL在Windows系統上始終支持利用不同內存地址加載數據,但EXE文件就沒這么幸運了。在ASLR出現之前,EXE文件會始終以0x0040000作為起始加載位置,并安全地以此為運行前提。但ASLR出現之后,情況就完全不同了。為了確保不出現差錯,Windows在默認條件下要求EXE可執行文件對ASLR提供支持,并提供啟用選項。不過出于安全的考慮,即使對應程序并未明確表達支持能力,Windows仍然默認在所有可執行程序及庫中啟用該選項。而且在大多數情況下,結果還是令人滿意的。

比較糟心的情況出現在x86 Linux系統當中。在使用ASLR的情況下,Linux平臺的性能損失可能高達26%。除此之外,這套方案明確要求可執行程序與庫以ASLR支持模式進行編譯。這意味著管理員根本無法像在Windows環境中那樣對ASLR進行授權。(x64也沒能徹底解決Linux的性能損失問題,不過損失程度得到了顯著降低。)

在ASLR啟用之后,它能夠為系統提供良好的緩沖區溢出狀況保護。不過ASLR本身還遠遠稱不上完美——舉例來說,其能夠提供的隨機水平就比較有限,而且這種情況在32位系統中表現得尤為嚴重。盡管內存空間所能提供的地址數量高達40億個,但并不是所有地址都能夠被用于加載庫或者旋轉內存棧。

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

可執行文件和庫通常在啟動后必須進行加載,且至少被包含在一個page當中。通常來講,這意味著它們必須以4096整數倍的形式進行加載。平臺也可以對內存棧采用類似的協議;舉例來說,Linux會以16字節的整數倍形式啟動內存棧。迫于內存的壓力,系統有時候需要對隨機性進行進一步削減,從而保證一切能夠順利運行。

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

不過在64位系統當中,由于地址空間變得更加龐大,單純的猜測就不足以解決問題了。攻擊者面對的很可能是上百萬個——甚至數十億個——潛在內存地址,機率如此之低也就不值得我們為之憂心了。

另外,對于攻擊者來說,猜測與崩潰這段手段不適用于瀏覽器之類的場景;沒有哪個用戶會連續對瀏覽器進行256次重啟來“幫助”攻擊者完成試探。也就是說,在這種情況下NX與ASLR的聯手協作將讓攻擊者們變得無機可乘。

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

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

一場永遠休止的戰爭

攻擊技術與保護技術之爭就像是貓與老鼠的競逐。像ASLR以及NX這樣強大的保護系統能夠提高安全漏洞的利用門檻,從而在一定時期內徹底阻止緩沖區溢出這類簡單漏洞的肆虐。然而聰明的攻擊者們仍然能夠找到其它安全缺陷,并將它們組合起來以繼續發動攻勢。

這場軍備競賽仍在不斷升級。微軟公司的EMET(即‘增強緩解體驗工具包’)當中包含一系列半實驗性保護方案,旨在檢測“堆噴射”乃至其它任何以ROP為基礎嘗試利用特定高危函數的行為。不過在這場永無休止的數字化對抗中,這些安全技術同樣在持續遭到淘汰。這并不是說它們沒有作用——各類新型保護技術的出現確實提高了漏洞利用的難度與成本——但大家必須正視一個現實,即警惕之心須長久保持。

英文:How security flaws work: The buffer overflow

責任編輯:藍雨淚 來源: 51CTO.com
相關推薦

2015-09-22 14:49:41

網絡安全技術周刊

2020-08-10 08:37:32

漏洞安全數據

2018-11-01 08:31:05

2011-11-15 16:00:42

2022-08-09 08:31:40

C -gets函數漏洞

2017-01-09 17:03:34

2019-02-27 13:58:29

漏洞緩沖區溢出系統安全

2020-10-27 09:51:18

漏洞

2014-07-30 11:21:46

2009-09-24 18:16:40

2018-01-26 14:52:43

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

2019-01-11 09:00:00

2010-10-09 14:45:48

2015-03-06 17:09:10

2011-03-23 12:39:44

點贊
收藏

51CTO技術棧公眾號

一区二区三区av电影| 久久99九九99精品| 亚洲精品在线不卡| 浓精h攵女乱爱av| 天天干在线视频论坛| 成人自拍视频在线| 国产成人精品一区二区三区| 久草福利资源在线| 国产一区二区三区亚洲| 在线观看日韩av先锋影音电影院| 亚洲精品偷拍视频| 无码国产色欲xxxx视频| 久久一区激情| 欧美黄色片视频| www.色天使| 欧美一区在线观看视频| 色婷婷综合久久| 青青青在线观看视频| 九色视频在线观看免费播放| 国产在线看一区| 日本高清+成人网在线观看| 麻豆一区在线观看| 性欧美lx╳lx╳| 欧美一级午夜免费电影| 日本激情视频在线| 国产777精品精品热热热一区二区| 国产日产精品一区| 国产99视频精品免费视频36| av网站中文字幕| 欧美午夜a级限制福利片| 亚洲精品资源美女情侣酒店 | 久久机这里只有精品| 97国产在线视频| 杨钰莹一级淫片aaaaaa播放| 国产欧美日韩视频在线| 亚洲护士老师的毛茸茸最新章节| 伊人国产精品视频| 亚洲日本在线观看视频| 狠狠色狠狠色综合日日五| www.国产亚洲| 国产激情在线视频| 国产精品久久久99| 四虎影院一区二区三区| 青青草娱乐在线| 国产成人免费av在线| 92国产精品视频| 亚洲一区二区视频在线播放| 日韩专区欧美专区| 欧美在线观看网址综合| 91香蕉在线视频| 国模一区二区三区| 欧美人与性动交| 久热这里有精品| 小处雏高清一区二区三区| 中文国产成人精品久久一| 西西444www无码大胆| 天天躁日日躁成人字幕aⅴ| 亚洲精品在线免费观看视频| 亚洲精品成人无码毛片| 18国产精品| 精品国产人成亚洲区| 被黑人猛躁10次高潮视频| 日本免费精品| 欧美大黄免费观看| 日韩少妇一区二区| 欧美交a欧美精品喷水| 国产视频精品久久久| 亚洲av无码国产精品久久| 偷拍自拍亚洲色图| 亚洲午夜色婷婷在线| 东方伊人免费在线观看| 久久在线免费| 欧美成人中文字幕| 国产一级片免费看| 夜久久久久久| 国产精品露脸自拍| 一级片在线观看视频| 国产精品一区免费在线观看| av一区观看| 日本天堂在线| 欧美激情一区三区| 福利在线小视频| av伦理在线| 色综合中文综合网| av亚洲天堂网| 97青娱国产盛宴精品视频| 亚洲精品国产suv| 精品欧美一区二区久久久| 99久久久久国产精品| 欧美日本高清视频| 五月天激情四射| 精品一区二区三区在线播放视频 | 国产日韩欧美中文字幕| 国产91精品一区二区| 久久久久欧美| 免费a级毛片在线播放| 亚洲成a人v欧美综合天堂下载 | 国产无套粉嫩白浆内谢| 欧美一级专区| 91亚洲精品在线观看| 婷婷五月综合久久中文字幕| 中文字幕乱码亚洲精品一区 | www久久久久久久| 中国成人一区| 日韩av片免费在线观看| 99久久国产免费| 91亚洲精华国产精华精华液| 亚洲午夜精品国产| 国产h片在线观看| 在线综合视频播放| 免费看黄色的视频| 激情综合亚洲| 91探花福利精品国产自产在线| 头脑特工队2免费完整版在线观看| 国产精品免费视频网站| 毛片在线视频播放| 免费精品一区二区三区在线观看| 国产视频久久久久久久| 国产一二三四区| 老牛嫩草一区二区三区日本| http;//www.99re视频| 91在线视频免费看| 欧美日韩视频免费播放| 少妇丰满尤物大尺度写真| 国产中文字幕一区二区三区| 97国产精品视频人人做人人爱| av官网在线观看| 国产精品乱码人人做人人爱| 人妻熟女一二三区夜夜爱| 91麻豆精品激情在线观看最新 | 久久精品国产精品青草| 久久精品日产第一区二区三区 | 国产成人在线一区| 手机在线观看毛片| 一区二区三区四区在线免费观看| 岛国av在线免费| 国产探花在线精品一区二区| 91成人福利在线| 蜜桃av鲁一鲁一鲁一鲁俄罗斯的 | 亚洲欧美中文日韩v在线观看| 国产在线综合网| 国产成人亚洲综合a∨婷婷图片| 亚洲精品一区二区毛豆| 色豆豆成人网| 亚洲性生活视频| 日韩综合在线观看| 久久久亚洲高清| 少妇高潮喷水久久久久久久久久| 美女视频亚洲色图| 久久久影视精品| 高h震动喷水双性1v1| 一区二区三区免费在线观看| www.51色.com| 亚洲激情久久| av日韩中文字幕| 欧美aaa免费| 精品裸体舞一区二区三区| 久久高清无码视频| 成人免费高清在线| 欧美日韩性生活片| 精品在线99| 国产精品国语对白| 在线播放毛片| 91精品免费在线观看| 2021亚洲天堂| av在线不卡观看免费观看| 久久久久久免费看| 亚洲人亚洲人色久| 国产成人精品免费视频| 日本在线免费播放| 欧美一区二区免费| 日本三级理论片| 久久久99精品免费观看| 国产精品自拍视频在线| 亚洲色图国产| 国产一区免费| av有声小说一区二区三区| 中文字幕亚洲综合| 午夜精品小视频| 精品国产乱码久久久久久天美| 中文字幕av久久爽一区| 国产一区免费电影| 久草热视频在线观看| 欧美理论视频| 99se婷婷在线视频观看| 最新欧美色图| 久久久精品视频成人| 人妻少妇一区二区三区| 一本色道**综合亚洲精品蜜桃冫| 久久精品日韩无码| 成人激情动漫在线观看| 日韩在线第三页| 欧美激情91| 欧美精品在线一区| 国产专区精品| 日韩美女视频免费在线观看| 日本中文在线观看| 日韩精品亚洲元码| 国产伦理吴梦梦伦理| 精品日韩视频在线观看| 99久久99久久精品国产| 97精品电影院| 日本中文字幕在线不卡| 六月丁香综合| 国产一区 在线播放| 三区四区不卡| 久久99热只有频精品91密拍| 91国产精品| 日本久久久久久久久久久| 性xxxfreexxxx性欧美| 亚洲欧洲av一区二区| 亚洲av色香蕉一区二区三区| 欧美亚洲一区二区在线| 日韩精品一区二区在线播放 | 亚洲视频导航| 亚洲免费专区| 国产精品一区视频| 高清在线一区二区| 国产精品激情av在线播放| yellow在线观看网址| 久久天天躁狠狠躁夜夜av| 国产视频第一区| 日韩黄色高清视频| 亚洲精品字幕在线观看| 777xxx欧美| 怡红院男人天堂| 91久久精品网| 日韩视频在线观看一区| 亚洲国产精品视频| 欧美成人免费看| 日韩美女啊v在线免费观看| av网在线播放| 26uuu另类欧美| 日韩www视频| 成人涩涩免费视频| 激情小说欧美色图| 国产福利91精品一区二区三区| www午夜视频| 奇米综合一区二区三区精品视频| 亚洲午夜无码av毛片久久| 99成人在线| 激情深爱综合网| 一区二区久久| 91猫先生在线| 首页国产欧美日韩丝袜| 日本成人中文字幕在线| 日本午夜一区二区| 在线视频日韩一区 | 国产精品日韩在线一区| 日韩一区二区三区在线免费观看| 国产成一区二区| 亚洲第一会所| 国产日韩欧美视频在线| 精品176极品一区| 国产精品久久久久免费a∨| av亚洲一区二区三区| 国产精品美女免费视频| 99久久亚洲国产日韩美女| 国产在线视频91| 91精品网站在线观看| 99国产精品久久久久老师| 在线播放一区二区精品视频| 好吊妞www.84com只有这里才有精品| 91九色鹿精品国产综合久久香蕉| 高清不卡一区二区三区| 欧美成人专区| 日本电影一区二区三区| 99久久亚洲精品蜜臀| 日本精品免费视频| 狠狠干成人综合网| 国产午夜福利100集发布| 免费视频久久| 五月天激情播播| 国产99久久久国产精品免费看 | 欧美毛片免费观看| 欧美日韩亚洲一区二区三区四区| 精品日韩免费| 糖心vlog在线免费观看| 91久久综合| 亚欧在线免费观看| 国产在线麻豆精品观看| 国产黄色三级网站| 国产精品卡一卡二| 久草视频在线免费看| 欧美日韩中文字幕| 在线免费一级片| 欧美大片顶级少妇| 懂色av中文在线| 久久99久久亚洲国产| 免费成人动漫| 99超碰麻豆| 全球成人免费直播| 久草免费福利在线| 日本免费新一区视频| 风韵丰满熟妇啪啪区老熟熟女| 91免费观看在线| h色网站在线观看| 色综合久久久久综合| 国产熟女精品视频| 亚洲精品一区中文| 久久亚洲资源| 国产欧美日韩免费| 欧美色图婷婷| 偷拍盗摄高潮叫床对白清晰| 国产精品一二| 国产sm在线观看| 国产欧美日韩视频在线观看| 久久在线视频精品| 欧美精品色综合| 狠狠色伊人亚洲综合网站l| 欧美激情一区二区三区高清视频 | 国产欧美精品| 午夜激情视频网| 国产精品久久久久一区二区三区| 欧美日韩综合在线观看| 91麻豆精品91久久久久久清纯| 欧美成熟毛茸茸| 欧美激情高清视频| 日本午夜精品久久久久| 欧美国产一二三区| 激情久久久久| 久久久久亚洲av片无码v| 国产精品私人影院| 中文字幕精品无码一区二区| 日韩欧美国产三级电影视频| 麻豆网站视频在线观看| 国产精品aaa| 狠狠综合久久av一区二区蜜桃| 国产亚洲综合视频| av不卡免费在线观看| 国产在线观看免费av| 日韩精品一区二区三区中文精品| 日本中文字幕在线2020| 国产精品一二三视频| 国产欧美高清视频在线| 黑鬼大战白妞高潮喷白浆| 91美女片黄在线| 亚洲欧美偷拍视频| 国产婷婷成人久久av免费高清| 美女高潮视频在线看| 激情视频在线观看一区二区三区| 国产精品观看| 精品伦一区二区三区| 亚洲综合免费观看高清完整版| 国产普通话bbwbbwbbw| 久久成人av网站| 久久伊人久久| 97av中文字幕| 成人在线视频一区| 国产在线视频二区| 日韩国产欧美精品一区二区三区| 黄色aa久久| 欧美亚州在线观看| 日韩福利电影在线| 一本在线免费视频| 欧美人牲a欧美精品| 男人天堂久久久| 91福利视频导航| 欧美人成在线| 国产黑丝一区二区| 欧美色播在线播放| 理论在线观看| 国产男人精品视频| 亚洲深深色噜噜狠狠爱网站| 免费观看黄网站| 狠狠色狠色综合曰曰| 福利视频在线看| 成人午夜高潮视频| 国产综合欧美| 亚洲自拍偷拍一区二区| 欧美日韩一区二区三区高清| 二区在线播放| 国产精品播放| 日本中文字幕一区二区有限公司| 国产探花视频在线| 欧美α欧美αv大片| 免费成人在线电影| 亚洲欧美日韩另类精品一区二区三区| 精品亚洲aⅴ乱码一区二区三区| 免费无码毛片一区二区app| 日韩电影第一页| 日韩成人精品一区二区三区| 99er在线视频| 欧美高清一级片在线观看| av一级黄色片| 国产成人精品视频在线| 亚洲天天综合| 青青草视频成人| 日韩欧美在线123| 日韩欧美另类一区二区| 黄色www在线观看| 91丨porny丨国产入口| 国产伦精品一区二区三区免.费 | 91久久青草| 国产精品50p| 综合久久给合久久狠狠狠97色| 内射后入在线观看一区| 国产精品高潮呻吟久久av野狼| 中文无码久久精品| 国产熟女一区二区|