深入分析Ubuntu本地提權(quán)漏洞【CVE-2017-16995】

      網(wǎng)友投稿 1108 2022-05-30

      前言:

      2018年3月中旬,Twitter 用戶 @Vitaly Nikolenko 發(fā)布消息,稱 ubuntu 最新版本(Ubuntu 16.04)存在高危的本地提權(quán)漏洞,而且推文中還附上了 EXP -。

      由于該漏洞成功在aws Ubuntu鏡像上復(fù)現(xiàn),被認(rèn)為是0DAY,引起了安全圈同學(xué)們的廣泛關(guān)注。大體瀏覽了 一下exp代碼,發(fā)現(xiàn)利用姿勢(shì)很優(yōu)雅,沒有ROP,沒有堆,沒有棧,比較感興趣,不過等了幾天也沒發(fā)現(xiàn)有詳細(xì)的漏洞分析,正好趕上周末,便自己跟了一下:)

      技術(shù)分析

      eBPF簡介

      眾所周知,linux的用戶層和內(nèi)核層是隔離的,想讓內(nèi)核執(zhí)行用戶的代碼,正常是需要編寫內(nèi)核模塊,當(dāng)然內(nèi)核模塊只能root用戶才能加載。而BPF則相當(dāng)于是內(nèi)核給用戶開的一個(gè)綠色通道:BPF(Berkeley Packet Filter)提供了一個(gè)用戶和內(nèi)核之間代碼和數(shù)據(jù)傳輸?shù)臉蛄骸S脩艨梢杂胑BPF指令字節(jié)碼的形式向內(nèi)核輸送代碼,并通過事件(如往socket寫數(shù)據(jù))來觸發(fā)內(nèi)核執(zhí)行用戶提供的代碼;同時(shí)以map(key,value)的形式來和內(nèi)核共享數(shù)據(jù),用戶層向map中寫數(shù)據(jù),內(nèi)核層從map中取數(shù)據(jù),反之亦然。BPF設(shè)計(jì)初衷是用來在底層對(duì)網(wǎng)絡(luò)進(jìn)行過濾,后續(xù)由于他可以方便的向內(nèi)核注入代碼,并且還提供了一套完整的安全措施來對(duì)內(nèi)核進(jìn)行保護(hù),被廣泛用于抓包、內(nèi)核probe、性能監(jiān)控等領(lǐng)域。BPF發(fā)展經(jīng)歷了2個(gè)階段,cBPF(classic BPF)和eBPF(extend BPF),cBPF已退出歷史舞臺(tái),后文提到的BPF默認(rèn)為eBPF。

      eBPF虛擬指令系統(tǒng)

      eBPF虛擬指令系統(tǒng)屬于RISC,擁有10個(gè)虛擬寄存器,r0-r10,在實(shí)際運(yùn)行時(shí),虛擬機(jī)會(huì)把這10個(gè)寄存器一一對(duì)應(yīng)于硬件CPU的10個(gè)物理寄存器,以x64為例,對(duì)應(yīng)關(guān)系如下:

      如一條簡單的x86賦值指令:mov eax,0xffffffff,對(duì)應(yīng)的BPF指令為:BPF_MOV32_IMM(BPF_REG_2, 0xFFFFFFFF),其對(duì)應(yīng)的數(shù)據(jù)結(jié)構(gòu)為:

      其在內(nèi)存中的值為:\xb4\x09\x00\x00\xff\xff\xff\xff。

      關(guān)于BPF指令系統(tǒng)此處就不再贅述,只要明確以下兩點(diǎn)即可:1.其為RISC指令系統(tǒng),也就是說每條指令大小都是一樣的;2.其虛擬的10個(gè)寄存器一一對(duì)應(yīng)于物理cpu的寄存器,且功能類似,比如BPF的r10寄存器和rbp一樣指向棧,r0用于返回值。

      BPF的加載過程:

      一個(gè)典型的BPF程序流程為:

      1. 用戶程序調(diào)用syscall(__NR_bpf, BPF_MAP_CREATE, &attr, sizeof(attr))申請(qǐng)創(chuàng)建一個(gè)map,在attr結(jié)構(gòu)體中指定map的類型、大小、最大容量等屬性。

      2. 用戶程序調(diào)用syscall(__NR_bpf, BPF_PROG_LOAD, &attr, sizeof(attr))來將我們寫的BPF代碼加載進(jìn)內(nèi)核,attr結(jié)構(gòu)體中包含了指令數(shù)量、指令首地址指針、日志級(jí)別等屬性。在加載之前會(huì)利用虛擬執(zhí)行的方式來做安全性校驗(yàn),這個(gè)校驗(yàn)包括對(duì)指定語法的檢查、指令數(shù)量的檢查、指令中的指針和立即數(shù)的范圍及讀寫權(quán)限檢查,禁止將內(nèi)核中的地址暴露給用戶空間,禁止對(duì)BPF程序stack之外的內(nèi)核地址讀寫。安全校驗(yàn)通過后,程序被成功加載至內(nèi)核,后續(xù)真正執(zhí)行時(shí),不再重復(fù)做檢查。

      3. 用戶程序通過調(diào)用setsockopt(sockets[1], SOL_SOCKET, SO_ATTACH_BPF, &progfd, sizeof(progfd)將我們寫的BPF程序綁定到指定的socket上。Progfd為上一步驟的返回值。

      4. 用戶程序通過操作上一步驟中的socket來觸發(fā)BPF真正執(zhí)行。

      BPF的安全校驗(yàn)

      Bpf指令的校驗(yàn)是在函數(shù)do_check中,代碼路徑為kernel/bpf/verifier.c。do_check通過一個(gè)無限循環(huán)來遍歷我們提供的bpf指令,理論上虛擬執(zhí)行和真實(shí)執(zhí)行的執(zhí)行路徑應(yīng)該是完全一致的。如果步驟2安全校驗(yàn)過程中的虛擬執(zhí)行路徑和步驟4 bpf的真實(shí)執(zhí)行路徑不完全一致的話,會(huì)怎么樣呢?看下面的例子:

      第一條指令是個(gè)簡單的賦值語句,把0xFFFFFFFF這個(gè)值賦值給r9.

      第二條指令是個(gè)條件跳轉(zhuǎn)指令,如果r9等于0xFFFFFFFF,則退出程序,終止執(zhí)行;如果r9不等于0xFFFFFFFF,則跳過后面2條執(zhí)行繼續(xù)執(zhí)行第5條指令。

      虛擬執(zhí)行的時(shí)候,do_check檢測到第2條指令等式恒成立,所以認(rèn)為BPF_JNE的跳轉(zhuǎn)永遠(yuǎn)不會(huì)發(fā)生,第4條指令之后的指令永遠(yuǎn)不會(huì)執(zhí)行,所以檢測結(jié)束,do_check返回成功。

      真實(shí)執(zhí)行的時(shí)候,由于一個(gè)符號(hào)擴(kuò)展的bug,導(dǎo)致第2條指令中的等式不成立,于是cpu就跳轉(zhuǎn)到第5條指令繼續(xù)執(zhí)行,這里是漏洞產(chǎn)生的根因,這4條指令,可以繞過BPF的代碼安全檢查。既然安全檢查被繞過了,用戶就可以隨意往內(nèi)核中注入代碼了,提權(quán)就水到渠成了:先獲取到task_struct的地址,然后定位到cred的地址,然后定位到uid的地址,然后直接將uid的值改為0,然后啟動(dòng)/bin/bash。

      漏洞分析

      下面結(jié)合真實(shí)的exp來動(dòng)態(tài)分析一下漏洞的執(zhí)行過程。

      Vitaly Nikolenko公布的這個(gè)exp,關(guān)鍵代碼就是如下這個(gè)prog數(shù)組:

      這個(gè)數(shù)組就是BPF的指令數(shù)據(jù),想要搞清楚exp的機(jī)理,首先要把這堆16進(jìn)制數(shù)據(jù)翻譯成BPF指令,翻譯結(jié)果如下:

      在do_check上打個(gè)斷點(diǎn),編譯運(yùn)行,成功斷了下來,先看一下調(diào)用棧:

      首先看第一條賦值語句BPF_MOV32_IMM(BPF_REG_9, 0xFFFFFFFF),do_check中最終的賦值語句如下:

      其中dst_reg為虛擬執(zhí)行過程中的寄存器結(jié)構(gòu)體,結(jié)構(gòu)體定義如下:

      可以看到該結(jié)構(gòu)體有2個(gè)字段,第一個(gè)為type,代表寄存器數(shù)據(jù)的類型,此處為CONST_IMM,CONST_IMM的值為8.另外一個(gè)為常量立即數(shù)的具體數(shù)值,可以看到類型為int有符號(hào)整形。

      我們?cè)诖颂幭聰帱c(diǎn),可以看到具體的賦值過程,如下:

      $rsi+$rax處即為reg_state結(jié)構(gòu)體,可以看到第一個(gè)字段為8,第二個(gè)字段為0Xffffffff。

      然后我們跟進(jìn)第二條指令中的比較語句BPF_JMP_IMM(BPF_JNE, BPF_REG_9, 0xFFFFFFFF, 2),do_check檢測到跳轉(zhuǎn)類指令時(shí),根據(jù)跳轉(zhuǎn)類型進(jìn)入不通的檢測分支,此處是JNE跳轉(zhuǎn),進(jìn)入check_cond_jmp_op分支,如下圖:

      do_check在校驗(yàn)條件類跳轉(zhuǎn)指令的時(shí)候,會(huì)判斷條件是否成立,如果是非確定性跳轉(zhuǎn)的話,就說明接下來2個(gè)分支都有可能執(zhí)行(分支A和分支B),這時(shí)do_check會(huì)把下一步需要跳轉(zhuǎn)到的指令編號(hào)(分支B)放到一個(gè)臨時(shí)棧中備用,這樣當(dāng)前指令順序校驗(yàn)(分支A)過程中遇到EXIT指令時(shí),會(huì)從臨時(shí)棧中取出之前保存的下一條指令的序號(hào)(分支B)繼續(xù)校驗(yàn)。如果跳轉(zhuǎn)指令恒成立的話,就不會(huì)再往臨時(shí)棧中放入分支B,因?yàn)榉种永遠(yuǎn)不會(huì)執(zhí)行,如下圖:

      第一個(gè)紅框即為虛擬寄存器中的imm與指令中提供的imm進(jìn)行比較,這兩個(gè)類型如下:

      可以看到等號(hào)兩側(cè)的數(shù)據(jù)類型完全一致,都為有符號(hào)整數(shù),所以此處條件跳轉(zhuǎn)條件恒成立,不會(huì)往臨時(shí)棧中push分支B指令編號(hào)。

      接下來看BPF_EXIT_INSN(),剛才提到在校驗(yàn)EXIT指令時(shí),會(huì)從臨時(shí)棧中嘗試取指令(調(diào)用pop_stack函數(shù)),如果臨時(shí)棧中有指令,那就說明還有其他可能執(zhí)行到的分支,需要繼續(xù)校驗(yàn),如果取不到值,表示當(dāng)前這條EXIT指令確實(shí)是BPF程序最后一條可以執(zhí)行到的指令,此時(shí)pop_stack會(huì)返回-1,然后break跳出do_check校驗(yàn)循環(huán),do_check執(zhí)行結(jié)束,校驗(yàn)通過,如下圖:

      跟進(jìn)pop_stack,如下圖:

      實(shí)際執(zhí)行過程如下:

      到此為止我們了解了BPF的校驗(yàn)過程,這個(gè)exp一共有41條指令,BPF只校驗(yàn)了4條指令,然后返回校驗(yàn)成功。

      接下來我們繼續(xù)跟進(jìn)BPF指令的執(zhí)行過程,對(duì)應(yīng)的代碼如下(路徑為kernel/bpf/core.c):

      其中DST為目標(biāo)寄存器,IMM為立即數(shù),我們跟進(jìn)DST的定義:

      跟進(jìn)IMM的定義:

      很明顯,等號(hào)兩邊的數(shù)據(jù)類型是不一致的,所以導(dǎo)致這里的條件跳轉(zhuǎn)語句的結(jié)果完全相反,以下為實(shí)際執(zhí)行過程:

      等號(hào)兩邊的值完全不一樣,這里的跳轉(zhuǎn)條件成立,會(huì)往后跳2條指令繼續(xù)執(zhí)行,和虛擬執(zhí)行的過程相反。

      接下來就是分析exp里面的BPF指令了,通過自定義BPF指令,我們可以繞過安全校驗(yàn)實(shí)現(xiàn)任意內(nèi)核指針泄露,任意內(nèi)核地址讀寫。

      構(gòu)造一下攻擊路徑:

      1.申請(qǐng)一個(gè)MAP,長度為3;

      2.這個(gè)MAP的第一個(gè)元素為操作指令,第2個(gè)元素為需要讀寫的內(nèi)存地址,第3個(gè)元素用來存放讀取到的內(nèi)容。此時(shí)這個(gè)MAP相當(dāng)于一個(gè)CC,3個(gè)元素組成一個(gè)控制指令。

      3.組裝一個(gè)指令,讀取內(nèi)核的棧地址。根據(jù)內(nèi)核棧地址獲取到current的地址。

      4.讀current結(jié)構(gòu)體的第一個(gè)成員,或得task_struct的地址,繼而加上cred的偏移得到cred地址,最終獲取到uid的地址。

      5.組裝一個(gè)寫指令,向上一步獲取到的uid地址寫入0.

      6.啟動(dòng)新的bash進(jìn)程,該進(jìn)程的uid為0,提權(quán)成功。

      Exp中就是按照如上的攻擊路徑來提權(quán)的,申請(qǐng)完map之后,首先發(fā)送獲取內(nèi)核棧地址的指令,如下:

      之前提到過,BPF的r10寄存器相當(dāng)于x86_64的rbp,是指向內(nèi)核棧的,所以這里第一行指令將map的標(biāo)識(shí)放到r9,第二條指令將r9放到r1,作為后續(xù)調(diào)用BPF_FUNC_map_lookup_elem函數(shù)的第一個(gè)參數(shù),第三條指令將內(nèi)核棧指針賦值給r2,第四條指令在棧上開辟4個(gè)字節(jié)的空間,第五條指令將map元素的序號(hào)放到r2,第六條指令取map中第r2個(gè)元素的值并把返回值存入r0,第七條指令判斷BPF_FUNC_map_lookup_elem有沒有執(zhí)行成功,r0=0則未成功。成功后執(zhí)行第9條指令,將取到的值放到r6中。繼續(xù)依次往下執(zhí)行,直到執(zhí)行到下面的路徑:

      判斷r6是否為0,為0說明是取棧地址的指令,這時(shí)會(huì)往下跳3條指令,繼續(xù)執(zhí)行第7條指令,將r10的內(nèi)容寫入r2,由于在執(zhí)行第30條指令時(shí)r0指向map中的第二個(gè)元素,所以這時(shí)r2也指向這個(gè)元素,然后用戶層通過get_value(2)取到了內(nèi)核棧的地址,我們通過給BPF_STX_MEM(BPF_DW, BPF_REG_2, BPF_REG_10, 0)下斷點(diǎn),可以看到過程如下:

      其中rax的值0xffff8800758c3c88即為泄露的內(nèi)核棧地址(其實(shí)應(yīng)該稱為幀指針更準(zhǔn)確)。

      然后通過經(jīng)典的addr & ~(0x4000 - 1)獲取到current結(jié)構(gòu)體的起始地址0xffff8800758c0000,然后構(gòu)造讀數(shù)據(jù)的map指令去讀current中偏移為0的指針值(即為指向task_struct的指針):

      深入分析Ubuntu本地提權(quán)漏洞【CVE-2017-16995】

      其中addr為當(dāng)前線程current的值0xffff8800758c0000,這樣可以得到task_struct的地址,

      過程如下:

      其中rax的值即為指向task_struct的指針,可以看到和current結(jié)構(gòu)體的第一個(gè)成員的值是一致的,都是0xffff880074343c00。

      得到task_struct地址之后,加上cred的偏移CRED_OFFSET=0x5f8(由于內(nèi)核版本不通或者內(nèi)核的編譯選項(xiàng)不同,都可能導(dǎo)致cred在task_struct中的偏移不同),組裝讀取指令去讀取指向cred結(jié)構(gòu)體的指針地址:

      過程如下:

      其中rax的值0xffff880074cb5e00即為從task_struct中讀取到的指向cred的指針。

      cred的地址得到了,再加上uid在cred中的偏移(固定為4)便得到了uid的地址0xffff880074cb5e04,然后構(gòu)造寫數(shù)據(jù)的map指令:

      過程如下(由于第一次運(yùn)行exp的時(shí)候,這里沒斷下來,所以下面的過程是第二次運(yùn)行的過程,中間一些結(jié)構(gòu)體的地址發(fā)生了稍微的變化):

      提權(quán)成功:

      到此整個(gè)漏洞利用完成,后面的部分寫的有點(diǎn)倉促了,如果有錯(cuò)誤的地方,還請(qǐng)各位朋友不吝賜教。

      安全

      版權(quán)聲明:本文內(nèi)容由網(wǎng)絡(luò)用戶投稿,版權(quán)歸原作者所有,本站不擁有其著作權(quán),亦不承擔(dān)相應(yīng)法律責(zé)任。如果您發(fā)現(xiàn)本站中有涉嫌抄襲或描述失實(shí)的內(nèi)容,請(qǐng)聯(lián)系我們jiasou666@gmail.com 處理,核實(shí)后本網(wǎng)站將在24小時(shí)內(nèi)刪除侵權(quán)內(nèi)容。

      上一篇:【Python3網(wǎng)絡(luò)爬蟲開發(fā)實(shí)戰(zhàn)】4.3-使用pyquery
      下一篇:互聯(lián)互通:云應(yīng)用的主要因素
      相關(guān)文章
      国产亚洲人成网站在线观看不卡| 亚洲国产免费综合| 久久亚洲国产午夜精品理论片| 亚洲精品成人区在线观看| 亚洲AV无码专区亚洲AV桃| 亚洲国产精品美女久久久久| 亚洲欧美日韩综合久久久久| 亚洲国产成人久久精品大牛影视| 亚洲丰满熟女一区二区哦| 久久综合亚洲色hezyo| 久久人午夜亚洲精品无码区| 日韩亚洲翔田千里在线| 婷婷亚洲综合一区二区| 婷婷亚洲天堂影院| 亚洲日韩精品无码专区网站| 亚洲天堂中文字幕在线| 亚洲综合av永久无码精品一区二区| 成人午夜亚洲精品无码网站| 日本红怡院亚洲红怡院最新| 亚洲av鲁丝一区二区三区| 亚洲欧洲第一a在线观看| 亚洲综合综合在线| 亚洲国产成人久久99精品| 亚洲欧洲日本在线观看| 亚洲国产精品美女久久久久| mm1313亚洲国产精品美女| 国产成人精品久久亚洲| 九月丁香婷婷亚洲综合色| 亚洲国产精品热久久| 亚洲字幕在线观看| 亚洲乱色伦图片区小说| 一本色道久久88亚洲综合| 国产日产亚洲系列| 亚洲AV无码国产精品色午友在线| 久久亚洲AV成人无码国产| 亚洲国产成人久久精品大牛影视 | 亚洲AV无码AV男人的天堂不卡| 久久精品熟女亚洲av麻豆| 久久亚洲国产成人精品无码区| 亚洲AV无码成人精品区日韩| 成人亚洲网站www在线观看|