999精品在线视频,手机成人午夜在线视频,久久不卡国产精品无码,中日无码在线观看,成人av手机在线观看,日韩精品亚洲一区中文字幕,亚洲av无码人妻,四虎国产在线观看 ?

運行時代碼隨機化防御代碼復用攻擊*

2019-10-24 05:50:02張貴民李清寶曾光裕趙宇韜
軟件學報 2019年9期
關鍵詞:指令程序信息

張貴民,李清寶,曾光裕,趙宇韜

1(解放軍信息工程大學,河南 鄭州 450001)

2(數學工程與先進計算國家重點實驗室,河南 鄭州 450001)

通訊作者:張貴民,E-mail:zh.guimin@163.com

代碼復用攻擊(code reuse attack,簡稱CRA)已成為當前一種廣泛應用的主流攻擊.CRA 首先在內存中的已有代碼中尋找可用的指令序列(即gadgets),然后劫持控制流,使這些gadgets 得到執行,從而實現攻擊.由于并不植入任何代碼,因此能夠輕易繞過數據執行阻止技術(data executive prevention,簡稱DEP)[1].

地址空間布局隨機化技術(address space layout randomization,簡稱ASLR)[2]是防御代碼復用攻擊的一類重要技術,已在當前的大部分操作系統中得到應用.ASLR 通過對代碼段和數據段的基地址的隨機化,使攻擊者無法定位相關指令序列.但之后提出的暴力破解攻擊[3]和基于內存泄漏漏洞[4]實現的攻擊,成功突破了ASLR 防護技術.為進一步提高ASLR 的防御能力,更細粒度的隨機化技術[5-8]被不斷提出,通過進一步增加代碼或指令的隨機性,阻止攻擊者獲得gadgets 信息.但隨后,Snow 等人提出了即使在細粒度ASLR 下也能實現的JIT-ROP 攻擊[9].JIT-ROP 攻擊的成功在于現有ASLR 普遍采用程序運行前的一次代碼隨機化,而運行過程中代碼在內存中的位置則相對固定,攻擊者可借助內存泄漏漏洞動態地分析內存中的代碼以獲取gadgets 的信息.

為更好地防御復雜先進的代碼復用攻擊,如JIT-ROP 攻擊等,本文提出一種基于運行時代碼隨機化的代碼復用攻擊防御方法LCR.其基本原理是:允許攻擊者獲取內存信息,但在攻擊者利用這些信息實施攻擊之前使這些信息失效,從而導致攻擊失敗.該方法監控對目標程序代碼段的讀數據操作和該程序執行的輸入類系統調用,當發生上述操作時,則對程序代碼中各函數塊在內存中的位置進行隨機變換,使攻擊者通過直接內存泄漏或間接內存泄漏[10]獲得的內存信息無效,從而阻止攻擊者根據泄漏的信息實現代碼復用攻擊.最后,本文借助硬件虛擬化技術Intel VT[11]設計實現了LCR 的原型系統,并對該方法的有效性和性能開銷進行了測試,結果表明:LCR能夠有效防御包含JIT-ROP 在內的大多數代碼復用攻擊,且在SPEC CPU2006 上的平均開銷低于5%.

本文第1 節描述LCR 的威脅模型和假設,明確防御對象和實施條件.第2 節簡要介紹LCR 的總體架構.第3節論述LCR 的具體設計和實現方法.第4 節實現LCR 原型系統并對其進行測試.第5 節探討LCR 的局限性.第6 節對比分析LCR 和其他相關的研究工作.第7 節對全文工作進行總結.

1 威脅模型和假設

LCR 旨在保護目標程序免遭代碼復用攻擊,即使是如JIT-ROP 這樣功能強大的攻擊也無法在有LCR 保護的情況下得以實現.LCR 的威脅模型和假設為:

(1)目標程序中至少含有一個內存損壞漏洞,攻擊者能夠借助該漏洞實現對目標程序內存的任意讀寫以及對目標程序控制流的劫持;

(2)攻擊者可以獲取和分析目標程序的二進制文件;

(3)操作系統已開啟ASLR 和DEP 機制,攻擊者不能通過植入代碼實現攻擊;

(4)系統的硬件設備是可信的;

(5)只考慮針對應用程序的代碼復用攻擊,針對內核層和虛擬層的攻擊不在考慮范圍內.

本文與其他相關代碼復用攻擊的防御方法[12,13]所采用的攻擊模型和假設基本一致.

2 總體架構

LCR 的目的是使攻擊者通過內存泄漏等方式得到的關于程序代碼的內存信息失效,而利用這些失效的信息,攻擊者無法成功發動代碼復用攻擊.在目標程序運行過程中,LCR 動態地對目標程序代碼的內存布局進行隨機化變換,其總體架構如圖1 所示.

LCR 總體分為3 個階段.

· 一是預處理階段:重新編譯目標程序,獲取與隨機化處理相關的程序代碼信息;

· 二是運行時監控階段:在發現對程序代碼所在內存頁的讀數據操作和執行輸入類操作時,觸發對目標程序代碼的隨機化處理,實現對代碼復用攻擊的有效防御;

· 三是運行時代碼隨機化處理階段:采用對目標程序代碼段中各函數塊所處內存位置重排序的方式,打亂原有代碼中所有指令的內存地址,使攻擊者在變化前獲取的內存分布信息失效,當攻擊者利用這些失效的信息發動代碼復用攻擊時,導致攻擊失敗;同時,該階段還要完成對所有相關指針、代碼等內容的更新操作,以保證變換后程序仍能正常運行.

本文借助Intel VT 硬件虛擬化技術[11]對LCR 進行了設計和實現.LCR 的核心功能模塊,即觸發事件監控模塊和隨機化處理模塊,均在一個輕量級Hypervisor 中實現.

Fig.1 Overall architecture of LCR圖1 LCR 的總體架構

3 LCR 的設計與實現

3.1 預處理

在對目標進程代碼段中的函數塊進行隨機化處理后,函數塊的絕對地址以及各函數塊之間的相對位置都將改變,因此,某些指令中使用的相關地址參數和偏移量以及指向各函數的指針類型變量都需要進行更新.預處理階段獲取需要更新的指令和變量的信息,為隨機化后的更新提供依據.

為盡量降低隨機化后內容更新的復雜度,采用了地址無關代碼(position-independent code,簡稱PIC)和地址無關可執行(position-independent executable,簡稱PIE)技術,在編譯時增加-fpic 或-pie 選項即可.PIC/PIE 對常量和函數入口地址的操作都基于基寄存器+偏移量的相對地址的尋址方式,有利于代碼隨機化的實現.采用PIC/PIE 技術在一定程度上簡化了隨機化變換后的處理,但仍要對目標進程的某些代碼和數據進行更新操作.

3.1.1 代碼段中相關內容

為便于分析,這里引入模塊的概念,將一個采用動態鏈接程序的每個可執行文件和共享對象都看做是該程序的一個模塊,而將采用靜態鏈接的程序看做一個獨立模塊.

(1)函數塊內的代碼引用

由于隨機化處理時將函數塊作為一個整體進行移動,函數塊內順序執行部分不受影響,因此主要考慮非順序執行的內容,即分支指令,包括CALL,JCC(如JA,JAE,JC,JRCXZ 等),JMP 和LOOP[11]這4 類.分支指令只能跳轉到同一個函數內部的某條指令或者另一個函數的入口,正常情況下不能跳轉到其他函數內部.在這里僅考慮內部跳轉,跳轉到其他函數入口的情況在情形(2)中討論.而上述指令在函數塊中使用時,都通過由相對于當前指令指針值(存放在EIP 寄存器中)的偏移值確定目的指令.由于隨機化前后函數塊內的指令順序不變,因此指令間的相對偏移也不變,所以該類指令在隨機化后不需要更新.

(2)模塊內的函數調用

同一模塊內的函數調用通過CALL 指令或跳轉指令實現.對于CALL 指令和JMP 指令,當以寄存器直接或間接尋址時,由于LCR 并不改變目標進程的執行流程,寄存器中的內容與隨機化變換前一致,因此該類指令不需要修正;而當采用相對于指令指針或程序代碼段基地址的偏移值定位目的指令時,則需要根據該指令的下一條指令與被調用函數在隨機化變換之后的位置關系計算新的偏移值.對于JCC 和LOOP 指令,均采用相對于當前指令指針值的偏移,同樣需要重新計算偏移值.

為了便于在隨機化后執行上述操作,需提取相關指令的地址信息,同時記錄目標程序代碼中各個函數塊在隨機化變換前后的布局信息.為此,在預處理階段,通過分析目標程序的匯編指令獲取代碼段中的各個函數的名稱、起始地址和長度,并構造一個原始的函數布局信息表.而在每次隨機化變換時,都將生成一個新的布局信息表,并按照該表對代碼進行隨機化變換.通過新舊布局信息表即可計算出所需的各類數據.例如,假設某次變換時原始表和新表見表1,那么原來跳轉到func1(0x55c)的跳轉指令使用的偏移值只需更新為指令指針值與地址0x582 的偏移值即可.

Table 1 Function blocks layout information table表1 函數布局信息表

(3)模塊間的函數調用

模塊間的函數調用依賴程序鏈接表(procedure linkage table,簡稱PLT)和全局偏移表(global offset table,簡稱GOT)實現.ELF 文件將GOT 拆分成“.got”和“.got.plt”兩個表,分別保存全局變量和函數的地址.下面以程序exam.c 為例(exam.c 的源代碼以及部分匯編代碼、符號表及調試信息如圖2 所示),分析在x86 架構下采用PIC/PIE 進行編譯生成共享庫(共享庫默認即為PIC)和可執行代碼時的函數調用機制.

Fig.2 Relevant files for exam圖2 程序exam 的相關文件

由匯編代碼可知:exam 調用共享庫函數時,首先執行圖2③中的兩條指令.由圖2①中函數__i686.get_pc_thunk.bx 的定義可知,該函數獲取0x627 處的指令在內存中的實際內存地址并存放于ebx 中.通過符號表(如圖2④所示)可知,.got.plt 與0x627 處指令的偏移正好為0x19cd.因此,0x627 處指令獲取.got.plt 的實際內存地址并存放在ebx 中.程序之后對共享庫中內容的調用都基于相對于.got.plt 的偏移實現.由于LCR 并不改變數據段(含.got.plt),在隨機化后該偏移值不變,因此只需重新計算調用__i686.get_pc_thunk.bx 函數的那條指令的下一條指令(本例中為0x627)與.got.plt 地址(本例中為0x1ff4)的相對偏移(本例中為0x19cd),并用該值替換原參數,即可保證程序仍能夠定位到.got.plt.

(4)訪問數據的指令

對數據的訪問同樣分為模塊內和模塊間兩種情況:模塊內的數據訪問采用相對尋址方式實現,由于一個模塊中的代碼段和數據段的相對位置固定,因此每一條指令和模塊內部數據之間的相對位置也固定,所以只需在當前指令地址的基礎上加一個固定的偏移量即可訪問相應數據,如圖2 中②所示對變量a的訪問方式;模塊間的數據訪問由于數據地址要等到其所在模塊裝載時才能確定,故該類數據訪問基于GOT 實現.雖然兩類數據訪問方式在理論上有所區別,但實際實現中都是首先獲取.got.plt 的地址,然后以該地址與要訪問數據所在內存地址之間的偏移作為指令訪問數據的偏移量.因此,在隨機化后只需重新計算當前指令地址的下一條指令與.got.plt 地址的相對偏移(本例中為0x1a1c),并修正相關指令(本例中為0x5d8 處指令)中的偏移值即可.

(5)指令指針寄存器(EIP)

EIP 確定下一步要執行指令的地址,在隨機化變換后,需要對該值進行更新以保證后續指令正常執行.首先讀取EIP 的值,然后基于該值和函數布局信息表判斷其所屬的函數塊以及該值和函數塊入口地址的偏移值,然后根據該函數塊隨機化變換后的新地址加上該偏移值計算新的EIP 值,并更新指令指針寄存器.

(6)回調函數相關指令

通過把一個函數的指針作為參數傳遞給另一個函數(如atexit,signal,sigaction 以及其他以函數指針作為參數的函數等),當該指針被用來調用其所指向的函數時,該函數就稱為回調函數.當目標程序中包含回調函數時,必定存在相應指令將該函數的指針(即內存地址)作為參數傳遞給其他函數(通常為movl 指令),即注冊指令.在執行代碼隨機化后,回調函數的地址將改變,此時,注冊指令的參數若不進行相應更新,當回調函數被調用時,將導致程序Crash.因此,必須對這類指令進行更新.而根據隨機化操作發生時注冊指令是否已經執行,可將該類指令分為未執行和已執行兩類.如圖3 所示,指令Func1(fp3,argu1,argu2)和Func2(fp6,argu3,argu4)分別將fp3 和fp6指向的函數F3 和F6 注冊為回調函數,且前者屬于隨機化操作時已執行注冊指令,后者屬于未執行注冊指令.

Fig.3 An example program圖3 例子程序

對于尚未執行的注冊指令,只需在隨機化發生時定位到該指令,然后通過原函數布局信息表定位該指令傳遞的函數指針所對應的函數,再在新函數布局信息表中找到該函數變換后的新地址,并以該值替換原指令中的指針參數.假設圖3 中程序的原內存布局和隨機化變換后的新內存布局分別如圖4 左上圖和圖4①所示,Func2在隨機化操作后執行時,將直接按更新后F6 的新地址fp6′進行回調函數的注冊,從而保證程序正常運行.

而對于已執行的注冊指令,如圖3 中的Func1,由于該指令已執行完畢,此時即使將其參數fp3 更新為F3 的新地址fp3′也不起作用,當程序按照fp3 回調函數F3 時,仍將造成程序Crash.為此,提出兩種解決方法.

1)方法1:在隨機化操作前,首先分析已經執行的注冊指令及其注冊的回調函數;然后,在隨機化操作時將已注冊函數進行定位和鎖定,保證這些函數的內存地址在隨機化操作后不發生改變,只對剩余函數進行隨機化.其實現機制如圖4②所示:在隨機化前后保證函數F3 的內存位置不發生變化,即fp3=fp3′;而對于尚未注冊的函數F6 以及其他函數,則進行正常的隨機化.該方法可保證隨機化操作不影響已注冊回調函數的執行過程,避免Crash;

2)方法2:首先,仍要在隨機化操作前對已經注冊的回調函數進行定位;然后,對所有函數(包括已注冊函數)進行隨機化.但在隨機化過程中,向已注冊回調函數的原指針指向的內存地址處寫入一條jump 指令,將已注冊的回調函數的新內存地址作為jump 的目的地址.如圖4③所示:在原注冊回調函數的內存地址fp3 處寫入jump fp3′,從而保證已注冊回調函數仍能跳轉到正確的內存位置處執行.

方法1 簡單高效,但部分代碼的內存位置在隨機化操作后并未發生改變,這在一定程度上為攻擊者留下了可乘之機;方法2 既能解決已注冊回調函數在隨機化操作后導致的Crash 問題,也保證了隨機化方法原有的安全性,避免了方法1 導致的安全性降低的問題.但方法2 的實現還涉及多個方面的問題,包括植入jump 指令后內存布局被打亂;隨機化后相關數據的更新方式均需改變;另外,在下一次隨機化時,如何對待之前植入的jump 指令也是需要解決的問題.本文目前采用方法1,并將在未來工作中對方法2 進行研究和實現.

Fig.4 Method to handle callback functions圖4 回調函數處理方法

3.1.2 數據相關內容

(1)函數指針類型變量

LCR 改變函數的入口地址,因此所有指向這些函數的函數指針類型變量均需要進行修正.由于在C 標準中,函數指針類型無法由其他類型轉換得到,因此可以直接分析目標程序中含有的函數指針類型的全局變量和局部變量.這里,通過在編譯時添加-gdwarf-version 選項(version 為DWARF 的版本號,本文使用-gdwrf-4)生成DWARF 格式的程序調試信息來實現該分析.程序exam 的部分調試信息如圖2⑤所示.

DWARF 中,每條記錄是一個調試信息入口(debugging information entry,簡稱 DIE),當某個 DW_TAG_variable 類型的DIE,又同時屬于指針類型(即DW_TAG_pointer_type)和子過程類型(即DW_TAG_subroutine_type)時,該DIE 對應的變量即為函數指針類型.然后,由該變量的DW_AT_location 屬性即可獲得該變量內存地址的計算方法.例如圖2⑤所示,ptr 的實際地址即程序加載地址加上0x2020.不同的DW_AT_location 屬性對應各自的變量地址的計算方式[14],因此,通過遍歷DIE 即可找到所有函數指針類型變量及其地址的計算方法.

另外,需要特別注意的是:當存在結構體struct 和聯合體union 類型變量時,若存在同時屬于DW_TAG_pointer_type 和DW_TAG_subroutine_type 的成員變量(即DW_TAG_member),該成員變量即為一個函數指針類型變量.更新結構體變量時,只需對該變量的函數指針類型的成員變量進行更新.當聯合體中存在函數指針類型變量時,需要結合程序源代碼和匯編代碼分析定位所有使用其作為函數指針的指令及其所處的函數塊,只需在這些指令執行時對聯合體變量的值進行更新.

隨機化變換后,首先根據函數指針類型變量的地址計算方式獲得其地址,然后由原值和函數布局信息表判斷出其要指向的目的函數,最后由隨機化變換后該函數的新地址替換原值.

(2)函數返回地址

函數返回地址是被調用函數執行完成后的后續指令的地址.隨機化變換后,返回地址也應當更新為變化后的新地址,以保證后續指令的正常執行.返回地址保存在棧中,而且總是在前棧幀ebp 之前入棧.因此,EBP 寄存器中的值加上返回地址的長度,即為當前函數的返回地址在棧中的位置.通過返回地址確定其對應的指令所屬的函數塊以及該指令到函數入口的偏移值,然后根據該函數變換后的新地址加上偏移值,即得到了新的返回地址,最后將該值寫入到原返回地址所在的內存單元中.之后,讀取出前棧幀ebp,并以該值為基礎更新下一個返回地址.循環上述過程,直到發現返回地址位于main 函數時停止.此時,目標進程的函數調用棧已遍歷完畢,所有的返回地址也已更新完成.

(3)跳轉表

若目標程序中含有switch/case 語句,且當case 語句較多時,C 編譯器會自動生成一個跳轉表(jump table)并存放在數據區中,通過該表進行跳轉,使switch/case 語句的執行效率得到提高.跳轉表表項中存放的是case 語句的入口地址相對于.got.plt 的偏移offset.由于.got.plt 的地址在隨機化前后不變,而case 語句的入口地址將發生改變,因此當目標程序含有跳轉表時,表項(即offset 的值)必須進行更新.首先定位到各個case 語句的入口地址;然后,基于該值和函數布局信息表判斷其所屬的函數塊以及該值和函數塊入口地址的偏移值;再根據該函數塊隨機化變換后的新地址加上該偏移值計算新的case 語句入口地址,并基于該值與.got.plt 的偏移得出offset 的值;最后,使用該值更新跳轉表的相應表項.

3.2 運行時監控

代碼復用攻擊通常需經過gadgets 獲取和利用兩個階段.由于ASLR 的廣泛應用,通過直接或間接內存泄漏獲取gadgets 信息已成為最有效的gadgets 獲取方式:直接內存泄漏就是直接讀取(或掃描)程序代碼的內存頁獲取gadgets;而間接內存泄漏則通過代碼指針等數據(例如棧中的函數指針和返回地址等)推斷出可執行代碼的內存地址,不需要讀取代碼頁.LCR 分別采用讀后即變(change after reading,簡稱CAR)和用前即變(change before using,簡稱CBU)兩種機制,使攻擊者利用直接或間接內存泄漏無法獲取到正確有效的gadgets 信息,從而無法實施代碼復用攻擊.

3.2.1 CAR

CAR 機制監控攻擊者對目標程序代碼所在內存頁的讀數據操作,并觸發函數代碼塊內存布局的隨機變換,使攻擊者通過直接內存泄漏的方式無法獲得有效的代碼內存分布信息.

該種監控通常有兩種實現方式.一是監控系統中所有的讀操作,判斷其目的地址是否為目標程序的代碼頁.該方式實現簡單,但開銷較大;二是將目標程序的代碼頁設置為不可讀,當讀這些內存頁時導致訪問異常,通過異常處理實現監控.該方式只監控目標內存頁的讀操作,大大降低了監控開銷復雜度.

CAR 借助Intel VT 硬件虛擬化技術中提出的擴展頁表技術(extended page-table,簡稱EPT)[11]實現了僅對目標內存頁讀操作的監控.

為保證目標程序一開始執行就得到保護,CAR 通過監控do_execve(·)內核函數(kernel function,簡稱KF)(在Linux 操作系統中,該函數完成具體的程序加載工作)的執行過程獲知目標進程是否已加載完成.這里給出監控do_execve(·)等內核函數的方法.通過分析發現,編譯器會在每個函數塊入口插入如下兩條指令:“push %ebp;movl %esp,%ebp”,用于保存原ebp 并修改ebp 指針指向棧頂,且這兩條指令的長度與VMCALL 指令的長度相同(均為3 個字節).因此在系統運行過程中,利用Hypervisor 將這些函數入口處的頭兩條指令替換成VMCALL即可使這些函數執行時陷入Hypervisor,從而實現監控.其中,內核函數的入口地址可通過System.map 文件找到,最后,需在Hypervisor 中仿真執行被覆蓋的兩條指令(“push %ebp;movl %esp,%ebp”)保證程序正常運行.

當監控到do_execve(·)函數陷入到Hypervisor 且由參數獲知加載對象為目標程序時,保存棧中的返回地址,然后用一個非法的內存地址(假設為Addr)替換該值.因此,當do_execve(·)執行完成返回時,將由于非法內存訪問導致EPT violation 異常再次陷入Hypervisor.此時,標志著目標程序已加載完畢.

目標程序加載完畢后,立即將其代碼頁屬性設置為僅可執行.首先,通過在Hypervisor 中重構出目標進程的task_struct 結構獲取start_code 和end_code 的值,從而得到其對應的內存頁;然后,清空并重新構建EPT,此時將目標進程代碼所在內存頁的屬性設置為僅可執行;最后,將do_execve(·)的返回地址恢復為事先保存的正確值,保證程序仍能正常運行.

基于上述操作,目標程序一旦加載運行,其代碼頁即已被設置為僅可執行.因此,當執行對這些代碼頁的讀操作時,將導致EPT Violation 異常而陷入Hypervisor.LCR 監控到異常后,分析導致該異常的原因,若是由讀操作引起的,則修改要讀取的代碼頁屬性為可讀,并將虛擬機控制結構(virtual machine control struct,簡稱VMCS)中的primary processor-based VM-execution control 字段的monitor trap flag 位設置為1(單步執行),之后,執行VMRESUME 返回客戶機.當客戶機執行完讀操作指令后,將再次陷入到Hypervisor,此時將代碼頁的屬性恢復為僅可執行,并同時觸發一次代碼隨機化操作(具體實現方法將在第3.3 節中闡述).

CAR 不阻止對目標進程代碼的讀操作,但通過在每次讀操作后執行一次隨機化,使攻擊者之前讀取到的內存信息失效,能夠有效防御通過直接內存泄漏實現的代碼復用攻擊.另外,由于假設攻擊者可獲得目標程序的二進制文件,因此攻擊者可在加載前獲取相關代碼信息,并在加載后直接進行篡改和利用,而不需再讀取代碼.針對該情況,CAR 除了由程序加載之后的讀操作觸發隨機化外,還設定在程序加載完成后無條件觸發一次隨機化,使攻擊者通過二進制文件獲得的信息失效.但由于CAR 機制只監控讀代碼操作,無法防御間接內存泄漏攻擊.

CAR 是實時性的且在讀后立即實施,但該機制開銷并不高:首先,EPT 機制本身是基于硬件輔助實現的,因此基于該機制陷入陷出的效率遠高于傳統的影子頁表技術;另外,正常情況下,對代碼頁的訪問主要是取指操作,而將代碼頁設置為僅可執行屬性時,取指操作并不會導致陷入,只有當攻擊者試圖構造攻擊時,才有可能大量產生由讀操作導致的陷入.上述分析與本文最后的性能測試結果也是一致的.

3.2.2 CBU

LCR 采用CBU 機制的目的是實現對間接內存泄漏攻擊的防御.CBU 機制通過在攻擊者使用獲取的內存信息發動攻擊前變換原有代碼內存布局的方式,使攻擊者所利用的信息失效,導致攻擊失敗.

TASR[12]首次提出攻擊實現的最小間隔理論,認為執行一個攻擊操作的最短間隔即最近的輸出操作和緊跟其后的輸入操作之間的時間長度.如果隨機化發生在每個最小間隔中間,攻擊者就失去了利用已知的內存狀態信息實現攻擊的機會.但TASR 并沒有考慮那些不需要目標進程執行輸出操作也能獲得內存信息的攻擊方式,如內存掃描方式,攻擊者只需通過執行輸入操作即可實現攻擊.為了防御此類攻擊,CBU 機制采用更寬松的隨機化觸發機制,即每次目標進程執行輸入類操作都將觸發一次隨機化處理.另外,TASR 未考慮那些本身就含有惡意邏輯,不需要輸入操作也可實現的來自惡意軟件自身的攻擊.而本文研究如何保護一個(非惡意的)軟件不受到外部攻擊,因此上述情況也不在本文考慮的范圍內.

首先,通過對32 位linux 3.2.0-29 內核的分析得到輸入類系統調用,并進一步分析獲得每個系統調用實際執行的關鍵內核函數,其結果見表2.通過對這些內核函數的監控,達到監控輸入類操作的目的.實現方法已在第3.2.1 節中論述.

當檢測到上述內核函數陷入后,還需要判斷該操作是否來自目標進程.為此,CBU 在監控到目標程序加載完成時(已在第3.2.1 節中論述)獲取該程序的基地址,即寄存器CR3 的值,并將該值作為判斷當前進程是否為目標進程的依據.若當前CR3 的值與目標進程的基地址值相同,則表明操作的發起者為目標進程,此時觸發對目標進程代碼的隨機化變換,否則不采取任何操作.

CBU 機制在攻擊者使用獲取的內存信息發動攻擊前變換原有代碼的內存布局,使攻擊者所利用的信息失效,導致攻擊失敗.CBU 機制彌補了CAR 機制無法防御間接內存泄漏的不足,而CAR 機制又對那些并不一定需要執行輸入操作的代碼復用攻擊(例如JIT-ROP)提供了有效防御,兩種機制共同保證了LCR 防御代碼復用攻擊的能力.CBU 機制也是實時性的,它引入的性能開銷與目標程序執行的輸入操作的次數和頻率有關,總體上比CAR 機制引入的開銷要大,尤其對于輸入密集型程序,如本文第4.2 節中測試的服務器程序nginx,當對類似nginx 這類輸入密集型程序進行保護時,可考慮在滿足一定安全性的基礎上關閉CBU 機制來換取較高的效率.

Table 2 System calls that perform input operation表2 輸入類系統調用信息

3.3 運行時代碼隨機化

3.3.1 代碼隨機化處理機制

當監控到觸發事件時,Hypervisor 中的隨機化處理模塊負責完成對目標進程代碼段中函數塊布局的隨機化處理.首先,基于原始函數布局信息表對代碼中的函數進行重排序,構造新的函數布局信息表,并保證相鄰變換中任何函數塊都處于不同位置,見表1.LCR 采用的隨機化重排序算法如圖5 所示,其中,隨機數的產生依賴于Linux 內核的隨機數發生器,該算法完成新函數布局表的生成、代碼內存的變換以及相關內容的更新.

Fig.5 Code randomization reorder algorithm圖5 代碼隨機化重排序算法

算法中的Max 是為了降低計算重排列的時間開銷而引入的.由于在進行隨機化操作時需要對n個函數進行全排列,并從中選擇某個排列來重新布局內存,當n較大時,全排列的開銷較大,而若先將n個函數劃分成Max個部分,再對Max 個部分進行全排列則可將時間復雜度從O(n!)降低至O(Xax!).因此,Max 的引入對于提高算法性能、降低開銷具有重要意義.

Max 在[2,n](n≥2)上取值,隨機化算法的開銷與Max 的值為正相關關系.另外,當函數個數n一定時,Max 的值越小,就意味著平均每個分區中含有的函數數量越多,代碼長度越長.此時,雖然每次隨機化后代碼的絕對地址仍然都會發生變化,但相對地址未發生變化的代碼(即同一個分區內的代碼)在所有代碼中所占的比重將越大,即一次隨機化變換后對原有內存布局的打亂程度會越小,那么就可能降低攻擊者暴力破解的難度.所以在開銷可以容忍的情況下,Max 值越大越好(Max 最大等于n).但需要說明的是:Max 的大小并不影響LCR 方法對由直接或間接內存泄露導致的代碼復用攻擊的防御能力,因為即使Max 取最小值2 時,也可實現對所有代碼內存布局的隨機化變換,達到防御內存泄露和代碼復用攻擊的效果.

構造新布局信息表后,按照該表對原代碼內容進行變換.首先,按照當前函數布局信息表將目標進程代碼中的各個函數依次拷貝到另一塊臨時內存區域.然后,再按照新函數布局信息表指定的各個函數的起始地址將臨時內存區域中的函數寫回到目標進程內存.由于進程代碼頁正常情況下不可寫,為實現該操作,需將所在內存頁的EPT 屬性修改為可寫.當完成對所有函數塊的寫回后,根據新舊函數布局信息構造一個映射f,實現從原內存地址到新內存地址的轉化.如圖6 所示,利用該映射對第3.1 節中提到的代碼段中的指令參數和相關數據的內容進行更新,然后恢復代碼頁的不可寫屬性,并修改RIP 寄存器,保證后續指令的正常執行.此時,清空并刪除原函數布局信息表,而新函數布局信息表則作為下次變換的原始表.最后,由Hypervisor 陷出到客戶機繼續運行目標進程.

Fig.6 Mapping relationship between original and new memory layout圖6 新舊內存布局之間的映射關系

為了更有效地防御代碼復用攻擊,LCR 在隨機化操作時,除了.text 節外,還將.plt,.init 和.fini 節中的函數塊納入到隨機化的范疇,實現了對ELF 文件中所有可執行部分的隨機化.其中,對.plt 段隨機化能有效防御returninto-libc 攻擊,而.init 和.fini 節是所有可執行文件都具有的內容,其中所含的gadgets 更容易被攻擊者利用.另外,為了不讓攻擊者發現函數布局信息表,該表始終存放在Hypervisor 中,且每次隨機化操作完成就徹底刪除原始函數布局信息表,只保留新函數布局表用于下次隨機化操作.

3.3.2 安全性分析

LCR 通過對原函數塊位置的變換,使攻擊者之前獲得的信息失效,無法以基于內存泄漏的方式實現代碼復用攻擊.但攻擊者通過分析二進制文件,仍有可能得到目標程序的函數信息,若在此基礎上實施暴力破解攻擊,即枚舉函數塊變換后所有可能的布局方式,并分別發起攻擊,其成功的可能性還是存在的.假設目標程序代碼中共含n個函數塊,攻擊者嘗試K次獲得成功.另外,假設當嘗試失敗導致目標進程崩潰時,該程序將自動重啟.

當攻擊者不了解LCR 的隨機化機制(相鄰兩次排列各個元素的位置均不相同)時,需要嘗試每一種排序,則每一次成功的概率都是,那么此時K的期望值為

即,攻擊者平均需要嘗試n!次才能成功.當攻擊者了解LCR 的隨機化機制時,攻擊者只需要對那些與當前排列相比元素的位置全部發生變化的排列進行嘗試,最大化地縮小事件空間.此時,設滿足條件的新的排列總數為N,至少有一個元素在原來位置上的排列數為N′,則N=n!-N′.攻擊者只需在N個可能排列中進行嘗試即可,其中,

由上述分析可知:目標程序的函數塊數目n越大,攻擊者實現暴力破解攻擊的難度越大.例如,當n=10 時,E(K)1=3628800,E(K)2=1334961,而大部分應用程序的函數塊數量遠大于10.可見,LCR 方法的安全性是有理論保證的.另外,以上評估均在假設攻擊者嘗試失敗導致目標進程崩潰后進程將自動重啟的條件下進行,但在實際應用中,可通過相關設置阻止進程自動重啟,還可通過對程序異常崩潰情況的分析檢測攻擊,從而進一步提高目標程序防御代碼復用攻擊的能力.在這種情況下,攻擊者攻擊成功的可能性將更低.

4 方法評估

在32 位Ubuntu12.04 系統上對LCR 進行了實現.系統內核版本為3.2.0-29-generic-pae,其中,物理地址擴展機制(physical address extension,簡稱PAE)默認開啟,gcc 版本為4.6.3,而Hypervisor 是基于Intel VT 技術設計的一個輕量級的虛擬機監控器,該監控器除對無條件陷入事件[11]進行處理外,只對LCR 中涉及到的觸發事件進行監控,目的是最大化地降低監控開銷.計算系統采用4 核Intel 處理器(CoreTMi7-3770,主頻3.40GHz)和16GB 內存(RAM).

4.1 有效性測試

4.1.1 隨機化有效性測試

本次測試中通過對比隨機化前后內存布局的變化來驗證LCR 隨機化處理的有效性.

為了更具體地體現LCR 隨機化處理過程,以程序exam 為例進行了測試:首先,利用ROPgedget[15]分析exam的二進制文件獲得gadgets 的信息列表,如圖7 所示,其中,每個gadget 對應的16 進制編碼也已在圖中列出;然后,在未實施LCR 保護的情況下運行exam,并假設攻擊者可通過一定方式得到exam 本次加載的虛擬地址0xb770e000(攻擊者可通過hook 系統的程序加載過程、提取目標程序的task_struct 結構等方式獲得該信息),并通過地址轉換獲得了其對應的物理地址0x3dbfdf000.測試中,由Hypervisor 提取出exam 可執行代碼對應的內存內容(0x3dbfdf438~0x3dbfdf751,對應_init,.plt,.text 和.fini 這4 個節),如圖8(a)所示.可見,此時攻擊者可以利用圖7 中獲得的地址信息從exam 的內存空間中成功定位到gadgets.

Fig.7 Running results of ROPgadgets圖7 ROPgadgets 的運行結果

Fig.8 Memory of executable code in exam圖8 exam 中可執行代碼對應的內存內容

之后,利用LCR 對exam 實施保護,由Hypervisor 在每次隨機化處理完成時讀取一次exam 進程的可執行代碼對應的內存內容,某一次讀取的結果如圖8(b)所示.為了更好地對比隨機化效果,在測試中同時保留了原始函數布局信息表和新函數布局信息表,通過對比發現,本次隨機化操作實現了 0x3dbfdf438~0x3dbfdf67f 和0x3dbfdf680~0x3dbfdf751 兩部分內容的位置互換.此時,當攻擊者再次按照之前獲取的gadgets 地址信息去讀取內存內容時,已無法得到有效的gadgets.說明隨機化處理后原有信息失效.

為使結果更具有代表性,按照上述方法對SPEC CPU2006 中用C 語言實現的基準程序進行了測試(400.perlbench 除外,因存在運行時翻譯代碼,編譯時無法直接獲得完整的代碼信息),在編譯時使用-pie 和-gdwarf-4選項生成可執行代碼.測試中,首先利用ROPgadget 提取各基準程序中含有的gadgets 信息,然后分別在采用和未采用LCR 保護的情況下運行基準程序,并在運行過程中獲取可執行代碼的內存快照,據此分析隨機化變換前后gadgets 的變化情況.當采用LCR 保護時,為測試LCR 的動態隨機化效果,測試中隨機選擇3 個時間點獲取內存快照進行分析,3 個時間點獲得的內存快照中均沒有定位到任何可用的gadgets.測試結果見表3.

Table 3 Validity tests of the randomization表3 隨機化有效性測試

通過上述測試表明:LCR 能夠有效實現對目標進程內存空間中的可執行代碼區域的隨機化變換,攻擊者通過靜態分析和內存泄漏等方式無法獲取有效的gadgets 信息.

4.1.2 代碼復用攻擊防御能力測試

為測試LCR 對代碼復用攻擊的防御能力,首先編寫了含有棧緩沖區溢出漏洞的目標程序targetProc,然后借助ROPgadget 構造針對targetProc 的ROP 攻擊payload,用于生成新的shell.在targetProc 程序運行過程中,通過輸入操作將該ROP 攻擊的payload 注入到targetProc 的棧中,并篡改控制流使payload 得到執行.分別在采用和未采用LCR 保護的情況下進行上述測試,結果見表4.當采用LCR 保護時,ROP 攻擊失敗,因為當LCR 對該程序的可執行代碼進行隨機化后,注入的payload 中所使用的gadgets 地址信息已失效,因而此時仍然利用這些失效的地址串聯執行最終導致程序崩潰,無法實現攻擊目標.

Table 4 Tests results of the ROP attack表4 ROP 攻擊測試結果

另外,分析和總結了當前已知的各類代碼復用攻擊技術.由于無法獲得它們的實現代碼,因此通過對它們實現機制的分析來判斷LCR 能否防御這些攻擊,分析結果見表5.上述分析都在滿足本文的攻擊模型和假設條件下進行.

Table 5 Analysis LCR ability to defend CRAs表5 LCR 代碼復用攻擊防御能力分析

4.2 性能測試

LCR 基于虛擬機監控器實現,不僅會對被保護的目標程序產生影響,也會對整個系統的性能產生影響.因此,為了充分說明LCR 帶來的各類性能開銷,分別在裸機、Hypervisor、Hypervisor 下開啟內核函數監控和Hypervisor 下開啟LCR(其中,隨機化重排序算法中的Max 設置為10)這4 種環境下進行了測試,所有測試均使用表4 中列出的SPEC CPU2006 中的C 語言基準測試程序,并且使用-pie 和-gdwarf-4 編譯選項.

根據圖9 所示的測試結果可知,本文設計的輕量級虛擬機監控器Hypervisor 在不監控內核函數時開銷很小.這是因為借助Intel VT,只需處理無條件陷入事件(該類事件執行頻率很低),對不需要陷入的其他操作都采用直接穿透的方式由客戶機操作系統負責完成.開啟內核函數監控后,當執行表2 中的7 類關鍵內核函數時會發生陷入,開銷稍有增加,但由于只在陷入后模擬執行“push %ebp;movl %esp,%ebp”兩條指令,引入的開銷仍然很小.啟動LCR 后,由于要在每次觸發事件發生時對目標程序代碼的內存進行隨機化變換,在一次變換中,每一頁都要經過兩次讀寫操作,從而引入了較多運行開銷,與未開啟Hypervisor 時相比,最小和最大開銷分別為0.22%(433.milc)和18.23%(456.hmmer),而平均開銷為4.89%.可見,LCR 雖然引入了部分開銷,但仍滿足實用性要求[22].

為了更深入地分析LCR 的性能開銷,測試中統計了每個基準程序運行過程中監控到的內核函數的執行次數和觸發CBU 的次數,以及各基準程序可執行代碼對應的內存區的總頁數(start_code~end_code)和有效頁的總頁數(非零頁).表中每個數據的分母表示監控到的執行總數,而分子表示來自于基準程序的執行次數,即觸發CBU 的次數.由于測試中未監控到vfs_readv,do_msgrcv,__sys_recvmsg 和__audit_mq_sendrecv 這4 個內核函數的執行,因此未予未列出.統計結果見表6.

Fig.9 Running overhead for SPEC CPU2006 under four different conditions圖9 SPEC CPU2006 在4 種不同條件下的運行開銷

Table 6 Statistics and analysis of the test data表6 測試數據的統計分析

由表6 可知,433.milc 開銷小是由于其觸發的CBU 次數少,且每次隨機化處理的內存頁也很少.目標程序的開銷與隨機化過程中處理的總內存頁數(即CBU 觸發次數和有效頁的乘積,忽略加載時的一次隨機化)的關系如圖10 所示,兩者基本呈正比關系,即:當目標程序執行過程中含有越多的輸入操作且可執行代碼量越大時,該程序采用LCR 保護后的開銷也會越大.實際測試結果中存在個別不符合正比關系的情況,是因為每個程序運行過程中系統執行關鍵內核函數的總數具有一定偶然性,當該數量較大時,也會對程序的運行開銷產生一定影響.

Fig.10 Relationship between running overhead and total pages which need to be handled圖10 運行開銷和處理的總頁數之間的關系

由于LCR 的CBU 機制在輸入類操作時觸發代碼隨機化,因此,為評估LCR 對高I/O 程序的性能影響,采用webbench-1.5[23]對nginx 服務器程序在不同環境下的處理能力進行了測試.測試命令為webbench-c 100-t 30 http://127.0.0.1/,即并發數為100,時間為30s,測試結果如圖11 所示.在開啟對所有輸入類操作的監控后(即KF monitor 環境下),nginx 處理能力明顯下降;而開啟CAR 和CBU 之后(即LCR(CAR&CBU)環境下),nginx 處理能力下降64.3%.測試發現:nginx 在測試的30s 中,約產生了12 次vfs_read 操作和高達9 363 次sock_recvmsg 操作,共觸發約9 375 次隨機化操作.正是短時間內集中觸發的大量隨機化操作,使nginx 處理能力下降.

Fig.11 Processing capacity of nginx under five different conditions圖11 nginx 在5 種環境下的處理能力

nginx 不僅是一種高I/O 程序,而且在工作時通常會出現I/O 密集型操作,即,在短時間內執行大量輸入/輸出操作,因此,使用LCR 對該類程序進行保護時開銷會較大.在對性能要求較高的場合,存在兩種可行的方案來降低LCR 的開銷.一種是通過僅對這類程序采取CAR 保護機制,如圖11 中的LCR(CAR only)所示,此時性能開銷很小(約為1.07%).這種通過犧牲部分安全性來換取高性能的做法也是安全領域經常采用的平衡策略.另外,由于目前大部分的代碼復用攻擊仍借助直接內存泄漏實現,因此,僅開啟CAR 機制仍能顯著提高對代碼復用攻擊的防御能力;另一種是采取CAR 和周期性隨機化相結合的方式,這樣既能降低部分開銷,又能保證對間接內存泄漏攻擊具有一定的防御能力.

5 LCR 的局限性

LCR 的局限性主要體現在以下幾個方面.

(1)LCR 目前僅支持C 語言程序.首先,C 程序是一種廣泛使用的非類型安全語言,且存在較多內存破壞漏洞,對這類程序的安全防護非常具有現實意義;其次,C 語言中的函數指針無法通過其他數據類型轉換得到,使對函數指針類型變量的跟蹤和更新成為可能.本文提出的動態隨機化代碼復用攻擊防御思想也可用于其他語言程序,只需解決對需要更新的函數指針等相關數據的定位和跟蹤即可,涉及程序分析技術和程序動態跟蹤技術的相關研究.由于本文重點在于提出和驗證這樣一種動態隨機化的防御思想,并未研究對所有語言類型的支持;

(2)LCR 是以函數塊而非gadgets 為對象進行隨機化,因此可能存在以下情況,即:函數塊位置變了,但gadgets 仍在原位置.例如,變換后,原函數func1 的ret 指令處變成了func2 的ret 指令.由于LCR 隨機化變換前后代碼所在的內存空間不變,出現該類情況需滿足以下條件:首先,不同函數中必須含有相同的gadgets;另外,還要保證被該gadgets 所在的新函數塊分成兩部分的原內存空間,每一部分都正好可以容下剩下的若干函數塊.當含相同gadgets 的兩個函數塊大小一致時,該情況出現的可能性較大;而當兩者大小不一致時,則可能性很小.為應對這類情況,可通過在隨機化變換時增加對原gadgets 地址處的內容審查,若發現內容一致,則再觸發一次隨機化.研究發現,大多數代碼復用攻擊往往由多個gadgets 配合實現.因此,除非使用的所有gadgets 在隨機化后都仍在原位置(概率很小),否則攻擊仍會失敗.為兼顧安全和效率,LCR 暫未對該類情況進行處理;

(3)對于多線程程序,攻擊者可能通過對子進程的分析獲得相關的內存信息實現代碼復用攻擊.文獻[24]已經對該類情況進行了詳細分析,并通過在fork 時再次隨機化每個子進程的地址空間,實現了對該類攻擊的防御.LCR 借助該方法實現對多線程目標程序的代碼復用攻擊防御;

(4)LCR 僅對目標程序自身代碼而未對動態鏈接庫的代碼進行隨機化,因此,LCR 無法防御那些不依賴讀程序代碼或者.plt 來獲取動態鏈接庫內存地址,且僅利用動態鏈接庫代碼實現的代碼復用攻擊.此類攻擊可通過代碼掃描(掃描動態鏈接庫代碼而非目標程序代碼)或旁信道攻擊[25]等方式來獲取所需要的動態鏈接庫代碼地址.基于程序控制流的監控對該類攻擊具有較好的防御效果,也是我們下一步要研究的重要內容.

6 相關工作

(1)代碼隨機化防御方法

該類方法的核心思想是:增加獲取gadgets 的難度,使攻擊者無法獲得足夠多且有效的gadgets 來構造攻擊.ASLR[2]最早提出了一種對動態鏈接庫和可執行文件的加載地址隨機化的方法,使攻擊者無法定位gadgets,但攻擊者只需發現一個指令的地址即可獲得其他所有指令的地址.為提高ASLR 的安全性,更細粒度的隨機化方法不斷被提出,包括指令級隨機化[5]、基本塊隨機化[6]、函數隨機化[7]以及頁面級的隨機化[8].但上述方法都只在加載時執行一次隨機化,攻擊者仍可通過內存泄漏定位到gadgets.與該類方法相比,LCR 采用動態運行時多次隨機化,能夠使攻擊者通過直接或間接內存泄漏獲得的信息失效,在能防御的內存泄露攻擊類型和代碼復用攻擊的防御能力方面都得到顯著提高.

JIT-ROP 攻擊在程序運行過程中尋找gadgets 加以利用,這使所有的加載時隨機化方法失效.為了應對這類新型攻擊,動態隨機化方法被提出,LCR 就屬于該類方法.Isomeron[13]同時運行程序的兩個不同副本,使攻擊者無法確定哪一個gadget 會得到執行,即Isomeron 僅提供兩種可能的變換,而LCR 是在第3.3.2 節中分析的N=n!-N′種可能性中選擇一種進行代碼的隨機化.因此,相比于Isomeron,LCR 的隨機化熵值得到顯著提高.文獻[26]首次提出操作系統的動態隨機化方法,但該方法采用周期性隨機化,存在攻擊窗口且實用性較差.Remix[27]動態隨機變換程序每個函數體內基本塊的順序,但函數塊本身位置不變,不能防御函數級復用攻擊.文獻[24]提出了一種通過在fork 之后再次隨機化子進程內存空間防御clone-probing 攻擊的方法,但該方法不能防御直接內存泄漏攻擊.TASR[12]采用最短攻擊間隔觸發隨機化,實現了對程序整個代碼段內存位置的動態變化,但整個代碼段內容不變,因此,一旦攻擊者通過某種機制定位到該程序的內存,即可掌握其所有代碼特征,無法防御只需一次或不需要輸入操作的攻擊(如JIT-ROP 攻擊);另外,TASR 的隨機化模塊位于用戶層,易被破壞,安全性較低.相比上述動態隨機化方法,LCR 首次提出了以CAR 和CBU 相結合的函數級動態隨機化方法防御代碼復用攻擊,所能防御的代碼復用攻擊類型更多(TASR 無法防御只需一次或不需要輸入操作的攻擊;Remix 不能防御函數級復用攻擊,文獻[24]無法防御直接內存泄露攻擊,文獻[26]存在攻擊窗口),能夠提供更高的安全性.

(2)控制流完整性保護方法

該類研究的目的是阻止攻擊者篡改控制流,使gadgets 序列得不到執行.CFI 是由Abadi 等人.最早提出的[28],旨在基于控制流圖(control-flow graph,簡稱CFG)防御所有控制流劫持攻擊.但該方法開銷較大,實用性差.因此,目前最新的研究仍集中在如何提高該方法的實用性上.CCFIR[29],CCFI[30],PICFI[31],Context-sensitive CFI[32]和文獻[33]等結合對程序語義的分析,降低對CFG 精確度的要求,以達到降低監控開銷的目的.但研究者已證明:即使細粒度的CFI 類保護方法,也無法真正保證控制流的安全[34],控制流被劫持的風險依然存在.

(3)其他防御方法

除了上述兩類方法外,還有在編譯時減少可構建gadgets 的指令的方法,例如G-free[35],但這類防御方法可防御的代碼復用攻擊類型有限;還有通過拒絕對代碼的讀操作[36]防御直接內存泄漏的方法,但無法防御間接內存泄漏;以及通過保護代碼指針的安全性防御代碼復用攻擊的方法CPI[37],但該方法已被證明是可以被繞過的[38].另外,還有通過代碼和數據隔離的方法[10,39],可防御內存泄漏攻擊,但實際的函數代碼不變,可能遭到return-intolibc 攻擊.而LCR 則可以有效阻止直接和間接內存泄漏攻擊以及return-into-libc 攻擊.

7 總結

本文提出了一種運行時代碼隨機化防御代碼復用攻擊的方法LCR,并分別對預處理階段、動態監控階段和隨機化處理階段進行了詳細論述.LCR 采用CAR 和CBU 兩種機制,分別實現在攻擊者獲取gadgets 信息后和利用gadgets 前對目標進程代碼中函數塊的隨機化變換,使攻擊者通過直接或間接內存泄漏獲得的gadgets 信息失效,從而阻止代碼復用攻擊的實現.對LCR 原型系統的測試表明:該方法能夠有效實現在目標程序運行過程中對其代碼內容的隨機化變換,可防御大部分已知代碼復用攻擊,并且平均開銷低于5%,具有良好的實用性.

猜你喜歡
指令程序信息
聽我指令:大催眠術
試論我國未決羈押程序的立法完善
人大建設(2019年12期)2019-05-21 02:55:44
ARINC661顯控指令快速驗證方法
測控技術(2018年5期)2018-12-09 09:04:26
LED照明產品歐盟ErP指令要求解讀
電子測試(2018年18期)2018-11-14 02:30:34
“程序猿”的生活什么樣
訂閱信息
中華手工(2017年2期)2017-06-06 23:00:31
英國與歐盟正式啟動“離婚”程序程序
環球時報(2017-03-30)2017-03-30 06:44:45
創衛暗訪程序有待改進
中國衛生(2015年3期)2015-11-19 02:53:32
展會信息
中外會展(2014年4期)2014-11-27 07:46:46
坐標系旋轉指令數控編程應用
機電信息(2014年27期)2014-02-27 15:53:56
主站蜘蛛池模板: 日韩天堂网| 国产成人高清在线精品| 久久精品电影| 国产视频自拍一区| 日韩精品毛片| 久久青草视频| 亚洲无码久久久久| 69综合网| 丰满的熟女一区二区三区l| 亚洲国产天堂在线观看| 九九九久久国产精品| 久久一级电影| 天天操天天噜| 亚洲日本韩在线观看| 高清精品美女在线播放| 色哟哟国产精品| 国产成人综合久久精品尤物| 99精品这里只有精品高清视频| 亚洲 欧美 日韩综合一区| 国产成人一区二区| 99视频只有精品| 又爽又黄又无遮挡网站| 亚洲视频欧美不卡| 国产精品白浆在线播放| www中文字幕在线观看| 99伊人精品| 黄色福利在线| 天天综合网色| 成人综合在线观看| 欧美日韩第二页| 欧美成人看片一区二区三区| 国产性猛交XXXX免费看| 狠狠干欧美| 女人天堂av免费| 在线观看精品自拍视频| 午夜色综合| 三上悠亚在线精品二区| 国产欧美日韩va另类在线播放| 亚洲一区二区在线无码| 99在线观看精品视频| 波多野结衣一级毛片| 亚洲欧美另类中文字幕| 欧美日韩专区| 久久人人97超碰人人澡爱香蕉| 日本少妇又色又爽又高潮| 麻豆精品在线| 日韩视频免费| 日韩一级毛一欧美一国产 | 尤物视频一区| 91九色国产在线| 欧美日本在线| 啪啪免费视频一区二区| 亚洲综合亚洲国产尤物| 国产一区二区精品福利| 一本久道热中字伊人| 国产成a人片在线播放| 国产福利在线免费观看| 国产成人高清精品免费软件 | 免费国产高清精品一区在线| 日本国产精品| 午夜天堂视频| 久久国产精品麻豆系列| 一级毛片无毒不卡直接观看| www亚洲精品| 一级黄色片网| 久久婷婷五月综合色一区二区| 日韩在线视频网站| 露脸国产精品自产在线播| 欧美啪啪一区| 欧美一级在线看| 亚洲伦理一区二区| 欧美激情网址| a天堂视频| 伊人福利视频| 国产精品三级av及在线观看| 国产乱码精品一区二区三区中文| 萌白酱国产一区二区| 久久精品人人做人人| 在线看片免费人成视久网下载| 欧美19综合中文字幕| 国产Av无码精品色午夜| 91精品国产丝袜|