解決Android加殼過(guò)程中mprotect調(diào)用失敗的原因分析
問(wèn)題原由
函數(shù)抽取殼是當(dāng)前最為流行的DEX加殼方式之一,這種加殼方式的主要流程包含兩個(gè)步驟:一、將DEX中需要保護(hù)的函數(shù)指令置空(即抽取函數(shù)體);二、在應(yīng)用啟動(dòng)的過(guò)程中,HOOK 類(lèi)的加載過(guò)程,比如ClassLinker::LoadMethod函數(shù),然后及時(shí)回填指令。
筆者在實(shí)現(xiàn)抽取殼的過(guò)程中遇到了一個(gè)問(wèn)題,即在步驟二回填指令之前,需要先調(diào)用mprotect將目標(biāo)內(nèi)存設(shè)置為“可寫(xiě)”,但在初次嘗試過(guò)程中一直調(diào)用失敗,于是有了今天這篇文章。
本文探討的主要內(nèi)容是mprotect調(diào)用失敗的根本原因,以及在加殼實(shí)現(xiàn)中的解決方案,通過(guò)本文的闡述,一方面能夠幫助遇到同類(lèi)問(wèn)題的小伙伴解決心中的疑惑,另一方面能夠給大家提供可落地的實(shí)現(xiàn)方案。
調(diào)用mprotect修改內(nèi)存失敗的現(xiàn)象
以下代碼塊截取自自定義LoadMethod函數(shù),其目標(biāo)是將目標(biāo)函數(shù)指令所在內(nèi)存頁(yè)的屬性修改為可寫(xiě)——通過(guò)mprotect函數(shù)的參數(shù)“PROT_WRITE”指定,實(shí)際結(jié)果是mprotect調(diào)用失敗了,返回”-1“,errno為”13“
int pagesize = sysconf(_SC_PAGESIZE);
int protectsize = pagesize;
byte *code_item_start = static_cast<byte *>(code_item_addr) + 16;
void *protectaddr = (void*) ((int) code_item_start - ((int) code_item_start % pagesize) - pagesize);
LOGD("process:%d,enter loadmethod:protectaddr:%p,protectsize:%d", getpid(), protectaddr, protectsize);
int result = mprotect(protectaddr, protectsize, PROT_WRITE);
LOGD("mprotect return 0: %d, errno: %d", result, errno);”13“號(hào)errno的符號(hào)為EACCES,查看linux手冊(cè)可知是權(quán)限問(wèn)題。手冊(cè)中給出一個(gè)可能的場(chǎng)景,即如果使用mmap映射一個(gè)以”只讀“模式打開(kāi)的文件,然后使用mprotect嘗試修改內(nèi)存屬性為可寫(xiě),就會(huì)返回EACCES錯(cuò)誤。
EACCES The memory cannot be given the specified access. This can
happen, for example, if you mmap(2) a file to which you
have read-only access, then ask mprotect() to mark it
PROT_WRITE.
接下來(lái)我們將沿著這個(gè)可能的場(chǎng)景,首先驗(yàn)證DEX文件是否以只讀模式打開(kāi),然后再進(jìn)行下一步分析。
mprotect調(diào)用失敗的原因分析
使用strace跟蹤應(yīng)用的系統(tǒng)調(diào)用,驗(yàn)證了DEX文件的打開(kāi)模式為只讀模式——"O_RDONLY",然后通過(guò)mmap2將DEX文件映射進(jìn)內(nèi)存,內(nèi)存屬性為只讀的私有映射。
[pid 13190] openat(AT_FDCWD, "/storage/emulated/0/payload.dex", O_RDONLY|O_LARGEFILE) = 49
mmap2(NULL, 2121728, PROT_READ, MAP_PRIVATE, 49, 0) = 0xcef7a000
為了進(jìn)一步證實(shí)并徹底理清背后的邏輯,我研究了下mprotect的設(shè)計(jì)文檔[1]。mprotect是用戶空間PAX的一部分,它的核心目標(biāo)是緩解可利用內(nèi)存漏洞被利用的情況,所以我理解mprotect實(shí)際上就是“memory protect”,它的主要目的是從安全的角度保護(hù)內(nèi)存:
The goal of MPROTECT is to help prevent the introduction of new executable
code into the task's address space. This is accomplished by restricting the
mmap() and mprotect() interfaces.
mprotect通過(guò)內(nèi)存屬性控制內(nèi)存的訪問(wèn)權(quán)限,其中安全狀態(tài)良好的屬性組合包括如下幾種:
VM_WRITE
VM_MAYWRITE
VM_WRITE | VM_MAYWRITE
VM_EXEC
VM_MAYEXEC
VM_EXEC | VM_MAYEXEC
即內(nèi)存要么是“可寫(xiě)”的,要么是“可執(zhí)行”的,“可寫(xiě)”與“可執(zhí)行”必須互斥,這樣才能阻斷“寫(xiě)入并執(zhí)行”的內(nèi)存攻擊。
理解了mprotect的設(shè)計(jì)理念之后,我們?cè)倩氐奖疚乃龅降膯?wèn)題本身:為什么以只讀方式打開(kāi)的DEX文件映射到內(nèi)存之后,無(wú)法使用mprotect修改為“可寫(xiě)”內(nèi)存?
根據(jù)mprotect設(shè)計(jì)文檔的闡述,mprotect主要通過(guò)VM_MAYWRITE控制內(nèi)存是否可被設(shè)置為“可寫(xiě)”,該屬性的設(shè)置時(shí)機(jī)在mmap調(diào)用之時(shí):
VM_WRITE | VM_MAYWRITE or VM_MAYWRITE if PROT_WRITE was requested at
mmap() time
mmap首先將所有可能的屬性標(biāo)致置位,然后再進(jìn)行合法性檢查:
kernel/msm/+/refs/heads/android-msm-vega-4.4-oreo-daydream/mm/mmap.c
/* Do simple checking here so the lower-level routines won't have * to. we assume access permissions have been handled by the open * of the memory object, so we don't do any here. */ vm_flags |= calc_vm_prot_bits(prot) | calc_vm_flag_bits(flags) | mm->def_flags | VM_MAYREAD | VM_MAYWRITE | VM_MAYEXEC;
如果文件打開(kāi)時(shí)未設(shè)置“可寫(xiě)”屬性,則清除“VM_MAYWRITE”屬性。
kernel/msm/+/refs/heads/android-msm-vega-4.4-oreo-daydream/mm/mmap.c
if (!(file->f_mode & FMODE_WRITE)) vm_flags &= ~(VM_MAYWRITE | VM_SHARED);
最后mprotect會(huì)對(duì)相關(guān)屬性進(jìn)行檢查,如果VM_MAYWRITE沒(méi)有被設(shè)置,則不可通過(guò)mprotect設(shè)置內(nèi)存的寫(xiě)屬性,返回EACCES錯(cuò)誤標(biāo)識(shí):
kernel/msm/+/refs/heads/android-msm-vega-4.4-oreo-daydream/mm/mprotect.c
/* newflags >> 4 shift VM_MAY% in place of VM_% */
if ((newflags & ~(newflags >> 4)) & (VM_READ | VM_WRITE | VM_EXEC)) {
error = -EACCES;
goto out;
}通過(guò)strace日志可以證實(shí)mmap DEX文件到內(nèi)存的過(guò)程中并沒(méi)有設(shè)置VM_MAYWRITE和VM_WRITE,所以直接使用mprotect設(shè)置內(nèi)存為“可寫(xiě)”的行為會(huì)被拒絕。
mmap2(NULL, 2121728, PROT_READ, MAP_PRIVATE, 49, 0) = 0xcef7a000
綜上,mprotect修改內(nèi)存為可寫(xiě)的整個(gè)邏輯如下:
系統(tǒng)以只讀模式打開(kāi)DEX文件,所以mmap在映射文件時(shí)清除了VM_MAYWRITE標(biāo)志,導(dǎo)致接下來(lái)在調(diào)用mprotect修改內(nèi)存為可寫(xiě)的過(guò)程中,mprotect檢測(cè)目標(biāo)內(nèi)存未設(shè)置VM_MAYWRITE標(biāo)志,返回EACCES錯(cuò)誤代碼。
兩種可行的解決方案
在研究清楚原因之后,我們?cè)賮?lái)聊聊可能的解決方案。我這里給出兩種經(jīng)過(guò)驗(yàn)證的思路:
1)hook openat函數(shù),設(shè)置文件打開(kāi)時(shí)的屬性為可讀寫(xiě)——O_RDWR;
if(strstr(pathname,"payload")){
LOGD("[myopenat] path: %s, flags: %d", (char*)pathname, flags);
flags &= (~O_RDONLY);
flags |= O_RDWR;
}2)hook mmap函數(shù),或者在mmap之前修改傳入mmap的標(biāo)簽,直接將內(nèi)存屬性修改為“可寫(xiě)”。這里我們以后面一種思路為例,HOOK MemMap::MapFileAtAddress函數(shù),在調(diào)用mmap映射文件之前修改prot參數(shù):
art/runtime/mem_map.cc
void* myMapFileAtAddr(int expected_ptr, int byte_count, int prot, int flags, int fd, int start, int low_4gb, int reuse, char *filename, int error_msg){
if(strstr(filename, "payload"))
{
LOGD("[myMapFileAtAddr] file name contains 'payload': %s, prot: %d, flags: %d, fd: %d", filename, prot, flags, fd);
prot |= PROT_WRITE;
}
void* res = oriMapFileAtAddr(expected_ptr, byte_count, prot, flags, fd, start, low_4gb, reuse, filename, error_msg);
return res;
}小結(jié)
網(wǎng)絡(luò)上很多關(guān)于抽取殼實(shí)現(xiàn)的教程都沒(méi)有提過(guò)mprotect的問(wèn)題,默認(rèn)mprotect修改內(nèi)存是成功的,這可能是因?yàn)榇蠖鄶?shù)人都是通過(guò)模擬器進(jìn)行實(shí)驗(yàn)。然而,如果我們要做線上的加殼產(chǎn)品,面向生產(chǎn)環(huán)境進(jìn)行開(kāi)發(fā)的話,mprotect調(diào)用失敗的問(wèn)題大概率會(huì)遇到,希望本文能有所幫助。
參考:
[1].mprotect設(shè)計(jì)文檔:https[:][/][/]pax[.]grsecurity[.]net[/]docs[/]mprotect[.]txt
到此這篇關(guān)于Android加殼過(guò)程中mprotect調(diào)用失敗的原因及解決方案的文章就介紹到這了,更多相關(guān)Android加殼mprotect調(diào)用失敗內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Android編程之控件狀態(tài)配置文件實(shí)例
這篇文章主要介紹了Android編程之控件狀態(tài)配置文件,以實(shí)例形式分析了Android控件狀態(tài)配置文件對(duì)于選中、獲得焦點(diǎn)、按下時(shí)的狀態(tài)等相關(guān)設(shè)置技巧,需要的朋友可以參考下2016-01-01
android studio2.3如何編譯動(dòng)態(tài)庫(kù)的過(guò)程詳解
這篇文章主要給大家介紹了關(guān)于android studio 2.3如何編譯動(dòng)態(tài)庫(kù)的過(guò)程,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)各位Android開(kāi)發(fā)者們具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面跟著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2017-08-08
Android開(kāi)發(fā)筆記之:深入理解Cursor相關(guān)的性能問(wèn)題
本篇文章是對(duì)Android中Cursor相關(guān)的性能問(wèn)題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Android開(kāi)發(fā)高級(jí)組件之自動(dòng)完成文本框(AutoCompleteTextView)用法示例【附源碼下載】
這篇文章主要介紹了Android開(kāi)發(fā)高級(jí)組件之自動(dòng)完成文本框(AutoCompleteTextView)用法,簡(jiǎn)單描述了自動(dòng)完成文本框的功能并結(jié)合實(shí)例形式分析了Android實(shí)現(xiàn)自動(dòng)完成文本框功能的具體步驟與相關(guān)操作技巧,并附帶源碼供讀者下載參考,需要的朋友可以參考下2018-01-01
Android實(shí)現(xiàn)上拉加載更多ListView(PulmListView)
這篇文章主要介紹了Android實(shí)現(xiàn)上拉加載更多ListView:PulmListView,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-09-09
Android View 完美實(shí)現(xiàn)EditText 在軟鍵盤(pán)上邊的示例
本篇文章主要介紹了Android View 完美實(shí)現(xiàn)EditText 在軟鍵盤(pán)上邊的示例,具有一定的參考價(jià)值,有興趣的可以了解一下2017-08-08
Android AsyncTask的優(yōu)缺點(diǎn)詳解
本文主要介紹了Android AsyncTask的優(yōu)缺點(diǎn),具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧2017-02-02

