函数抽取壳是当前最为流行的DEX加壳方式之一,这种加壳方式的主要流程包含两个步骤:一、将DEX中需要保护的函数指令置空(即抽取函数体);二、在应用启动的过程中,HOOK 类的加载过程,比如ClassLinker::LoadMethod函数,然后及时回填指令。
笔者在实现抽取壳的过程中遇到了一个问题,即在步骤二回填指令之前,需要先调用mprotect将目标内存设置为“可写”,但在初次尝试过程中一直调用失败,于是有了今天这篇文章。
本文探讨的主要内容是mprotect调用失败的根本原因,以及在加壳实现中的解决方案,通过本文的阐述,一方面能够帮助遇到同类问题的小伙伴解决心中的疑惑,另一方面能够给大家提供可落地的实现方案。
以下代码块截取自自定义LoadMethod函数,其目标是将目标函数指令所在内存页的属性修改为可写——通过mprotect函数的参数“PROT_WRITE”指定,实际结果是mprotect调用失败了,返回”-1“,errno为”13“。
”13“号errno的符号为EACCES,查看linux手册可知是权限问题。手册中给出一个可能的场景,即如果使用mmap映射一个以”只读“模式打开的文件,然后使用mprotect尝试修改内存属性为可写,就会返回EACCES错误。
接下来我们将沿着这个可能的场景,首先验证DEX文件是否以只读模式打开,然后再进行下一步分析。
使用strace跟踪应用的系统调用,验证了DEX文件的打开模式为只读模式——"O_RDONLY",然后通过mmap2将DEX文件映射进内存,内存属性为只读的私有映射。
为了进一步证实并彻底理清背后的逻辑,我研究了下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
[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)
最后于 2021-3-18 19:12
被kxliping编辑
,原因: