網站首頁 編程語言 正文
問題原由
函數抽取殼是當前最為流行的DEX加殼方式之一,這種加殼方式的主要流程包含兩個步驟:一、將DEX中需要保護的函數指令置空(即抽取函數體);二、在應用啟動的過程中,HOOK 類的加載過程,比如ClassLinker::LoadMethod函數,然后及時回填指令。
筆者在實現抽取殼的過程中遇到了一個問題,即在步驟二回填指令之前,需要先調用mprotect將目標內存設置為“可寫”,但在初次嘗試過程中一直調用失敗,于是有了今天這篇文章。
本文探討的主要內容是mprotect調用失敗的根本原因,以及在加殼實現中的解決方案,通過本文的闡述,一方面能夠幫助遇到同類問題的小伙伴解決心中的疑惑,另一方面能夠給大家提供可落地的實現方案。
調用mprotect修改內存失敗的現象
以下代碼塊截取自自定義LoadMethod函數,其目標是將目標函數指令所在內存頁的屬性修改為可寫——通過mprotect函數的參數“PROT_WRITE”指定,實際結果是mprotect調用失敗了,返回”-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“號errno的符號為EACCES,查看linux手冊可知是權限問題。手冊中給出一個可能的場景,即如果使用mmap映射一個以”只讀“模式打開的文件,然后使用mprotect嘗試修改內存屬性為可寫,就會返回EACCES錯誤。
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.
接下來我們將沿著這個可能的場景,首先驗證DEX文件是否以只讀模式打開,然后再進行下一步分析。
mprotect調用失敗的原因分析
使用strace跟蹤應用的系統調用,驗證了DEX文件的打開模式為只讀模式——"O_RDONLY",然后通過mmap2將DEX文件映射進內存,內存屬性為只讀的私有映射。
[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
為了進一步證實并徹底理清背后的邏輯,我研究了下mprotect的設計文檔[1]。mprotect是用戶空間PAX的一部分,它的核心目標是緩解可利用內存漏洞被利用的情況,所以我理解mprotect實際上就是“memory protect”,它的主要目的是從安全的角度保護內存:
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通過內存屬性控制內存的訪問權限,其中安全狀態良好的屬性組合包括如下幾種:
VM_WRITE
VM_MAYWRITE
VM_WRITE | VM_MAYWRITE
VM_EXEC
VM_MAYEXEC
VM_EXEC | VM_MAYEXEC
即內存要么是“可寫”的,要么是“可執行”的,“可寫”與“可執行”必須互斥,這樣才能阻斷“寫入并執行”的內存攻擊。
理解了mprotect的設計理念之后,我們再回到本文所遇到的問題本身:為什么以只讀方式打開的DEX文件映射到內存之后,無法使用mprotect修改為“可寫”內存?
根據mprotect設計文檔的闡述,mprotect主要通過VM_MAYWRITE控制內存是否可被設置為“可寫”,該屬性的設置時機在mmap調用之時:
VM_WRITE | VM_MAYWRITE or VM_MAYWRITE if PROT_WRITE was requested at
mmap() time
mmap首先將所有可能的屬性標致置位,然后再進行合法性檢查:
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;
如果文件打開時未設置“可寫”屬性,則清除“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會對相關屬性進行檢查,如果VM_MAYWRITE沒有被設置,則不可通過mprotect設置內存的寫屬性,返回EACCES錯誤標識:
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; }
通過strace日志可以證實mmap DEX文件到內存的過程中并沒有設置VM_MAYWRITE和VM_WRITE,所以直接使用mprotect設置內存為“可寫”的行為會被拒絕。
mmap2(NULL, 2121728, PROT_READ, MAP_PRIVATE, 49, 0) = 0xcef7a000
綜上,mprotect修改內存為可寫的整個邏輯如下:
系統以只讀模式打開DEX文件,所以mmap在映射文件時清除了VM_MAYWRITE標志,導致接下來在調用mprotect修改內存為可寫的過程中,mprotect檢測目標內存未設置VM_MAYWRITE標志,返回EACCES錯誤代碼。
兩種可行的解決方案
在研究清楚原因之后,我們再來聊聊可能的解決方案。我這里給出兩種經過驗證的思路:
1)hook openat函數,設置文件打開時的屬性為可讀寫——O_RDWR;
if(strstr(pathname,"payload")){ LOGD("[myopenat] path: %s, flags: %d", (char*)pathname, flags); flags &= (~O_RDONLY); flags |= O_RDWR; }
2)hook mmap函數,或者在mmap之前修改傳入mmap的標簽,直接將內存屬性修改為“可寫”。這里我們以后面一種思路為例,HOOK MemMap::MapFileAtAddress函數,在調用mmap映射文件之前修改prot參數:
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; }
小結
網絡上很多關于抽取殼實現的教程都沒有提過mprotect的問題,默認mprotect修改內存是成功的,這可能是因為大多數人都是通過模擬器進行實驗。然而,如果我們要做線上的加殼產品,面向生產環境進行開發的話,mprotect調用失敗的問題大概率會遇到,希望本文能有所幫助。
參考:
[1].mprotect設計文檔:https[:][/][/]pax[.]grsecurity[.]net[/]docs[/]mprotect[.]txt
原文鏈接:https://bbs.pediy.com/thread-266527.htm
相關推薦
- 2022-11-05 nginx修改默認端口方法圖文詳解_nginx
- 2022-12-15 C++?boost?scoped_ptr智能指針詳解_C 語言
- 2022-12-12 C#中TextBox的橫線樣式及占位提示詳解_C#教程
- 2022-06-10 ASP.NET?Core使用AutoMapper組件_實用技巧
- 2022-07-24 Harbor高可用配置及倉庫使用介紹_云其它
- 2022-10-23 Linux服務器VPS的Windows?DD包詳細的制作教程_Linux
- 2023-07-02 jQuery和HTML對某個標簽設置只讀或者禁用屬性的方式_jquery
- 2023-04-07 C語言中的編碼小技巧_C 語言
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支