一代壳dex整体加固
第一代壳主要是对dex/apk文件整体加密,然后自定义类加载器动态加载dex/apk文件并执行。在动态加载dex/apk文件的时候有落地加载和不落地加载,落地加载就是通过DexClassLoader/PathClassLoader从磁盘加载dex/apk文件,不落地加载就是通过InMemoryDexClassLoader从内存中加载dex/apk文件。下面以落地加载为例(不落地加载实现原理类似)。
一代壳实现原理
通过将原apk进行加密后保存在加壳apk的dex文件尾部,在加壳apk运行时将dex文件尾部加密的原apk文件解密后进行动态加载。
需要在最早获取到加壳apk的执行时机,所以加壳apk的Application实现了attachContextApplication函数,此函数在ActivityThread的main函数中通过调用makeApplicaton进行调用,是一个APP进程最早的执行入口。加壳apk需要进行如下操作
- attachContextApplication解密原apk保存到磁盘文件中(不落地加载可以不保存磁盘文件),动态加载解密后的apk并替换掉mClassLoader
- Application::onCreate重定位Application,调用原apk的Application的onCreate函数
二代壳dex函数抽取
一代壳无论是落地加载函数不落地加载其dex文件一旦加载到内存中就会以完整的形式保存,可以hook相关函数dump下完整的dex文件。二代壳在一代壳的基础上实现,为了防止dump下来完整的dex文件通过对java类方法进行抽取,并在真正加载此类时才对dex文件中的函数字节码进行填充。
二代壳实现原理
010定位到需要抽取的函数的code_item, 前16个字节固定大小是用来描述此函数信息的,其余字节码为函数体实际的字节码,对他进行抽空。
然后修复dex文件的checksum和signature的值,在查看此dex文件的smali代码发现函数体已经被抽空。
将原apk的dex文件替换并进行签名,然后原apk加密后保存在加壳apk的dex文件尾部并保存加密后的原apk的大小。
加壳apk的实现
二代壳的加壳apk除了要实现一代壳加壳apk的基本操作外,还需要在原apk中被抽取的函数加载时进行字节码的还原。
- 首先就是还原点,通常在art::ClassLoader::LoadMethod函数中进行,所以加壳apk需要hook此函数。(注意不同android版本下此函数的导出符号略有差别)
- 由于art虚拟机下dex2oat会对dex文件进行优化生成oat文件,而apk后续执行就会直接加载oat文件,这样我们还原dex文件中的字节码是无效的,因此需要阻止dex2oat执行,分析dex2oat执行被执行的代码发现可以通过hook execve来阻止dex2oat的执行。(android 10开始默认不在对应用程序加载的dex文件执行dex2oat,所以可以省略此步)
hook LoadMethod为myLoadMethod,在myLoadMethod中判断当前加载的函数是否为被抽取的函数,如果是就进行函数字节码的恢复。恢复后将dex dump下来查看被抽取的函数已经正常。
参考:
https://developer.android.com/about/versions/10/behavior-changes-10?hl=zh-cn#system-only-oat
https://segmentfault.com/a/1190000040778372
https://bbs.pediy.com/thread-252630.htm