Android软件加固概述
从2012年开始,移动互联网进入快速发展阶段,Android App开发热潮的兴起,也推动了Android平台软件保护技术的发展。
• 为何做加固
- 保护核心代码
- 防止营销作弊的手段
- 防止代码被篡改 ...
加固代际
根据不同的理解,现在加固代际基本上可以按照五代或者三代去区分。
第一代:动态加载类
Apk中没有完整原始的Dex,需要运行时动态的加载到内存中
原理
• 落地加载
我们拿到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密再将壳Apk进行合并得到新的Dex文件,最后替换壳程序中的dex文件即可,得到新的Apk,那么这个新的Apk我们也叫作脱壳程序Apk.他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源Apk.然后加载Apk,让其正常运行起来。运行时首先将我们的Dex文件或者Apk文件解密,然后利用DexClassLoader加载器将其加载进内存中,然后利用反射加载待加固的Apk的Appkication,然后运行待加固程序即可。
• 不落地加载
落地加载将Dex文件解密出来会保存到文件中,再通过DexClassLoader加载进内存中,而不落地加载直接重写DexClassLoader使其可以直接加载字节数组,避免写入文件中。我们要做的是重写DexClassLoader,而这涉及到三个函数defineClass、findClass、loadClass,在一个类被加载的时候,会先后调用这三个函数加载一个类,所以我们需要重写这三个函数。系统的DexClassLoader加载Dex进入内存的也必然是通过字节加载的,而在系统so中的libdvm.so中的openDexFile可以直接加载Dex文件,那么现在清楚了,我们可以通过编写so文件调用openDexFile函数加载Dex字节数组,值得注意的是,openDexFile函数返回值为一个int类型的cookie,可以简单理解成一个dex文件的'身份码',通过该'身份码'即可操控这个dex文件,至于怎么调用该函数,可以通过dlopen和dlsym函数调用。
优劣
• 优点
比较容易实现,无明显的兼容性问题 能有效对抗静态分析和二次打包
• 缺点
启动时需要进行大量的解密运算,容易造成卡死的情况 在内存中的数据为完整的Dex,通过动态调试Dump内存即可获取完整的Dex
特点
Dex字符串加密 资源加密 对抗反编译 对抗调试 Dex动态加载 so加密
第二代:函数抽取类
原理
主要分为两个步骤指令抽取和指令还原
• 指令抽取
解析原始Dex文件格式,保存所有方法的代码结构体信息,通过传入需要置空指令的方法和类名,检索到其代码结构体信息。通过方法的代码结构体信息获取指令个数和偏移地址,构造空指令集,然后覆盖原始指令,重新计算dex文件的checksum和signature信息,回写到头部信息中。
• 指令还原
native层hook系统函数dexFindClass,获取类结构体信息(dexFindClass函数用于查找类的DexClassDef结构),获取类中所有的方法信息,通过指定方法名进行过滤,获取该方法的代码结构体信息,获取该方法被抽取的指令集,修改方法对应的内存地址为可读属性,直接进行指令还原。
优劣
• 优点
加密粒度变小,加密技术从Dex文件级变为方法级 按需解密,解密操作延迟到某类方法被执行前,如果方法不被执行,则不被解密 解密后的代码在内存不连续,克服了内存被Dump的缺点,有效保护了移动客户端的Java代码
• 缺点
使用大量的虚拟机内部结构,会出现兼容性问题,无法保护所有方法 无法对抗自定义虚拟机 它跟虚拟机的JIT优化出现冲突,达不到最佳的性能表现
特点
除一代有的特点外 内存中无完整、连续的Dex so代码混淆、膨胀
第三代:VMP、Dex2C类
原理
• VMP
执行到关键代码时进入壳so执行,关于这一点,不同的厂商有着不同的做法,比如把关键函数变成native函数,在壳so中动态或者静态注册
再比如更改关键方法的方法体
无论哪种方法其实都是为了能让壳函数代替原函数去执行 当执行该函数的指令时,解析出指令的OpCode,通过一个巨大的switch case找到处理对应OpCode的函数,然后执行
简单讲就是壳将原本的指令进行一次封装,将原本的指令转换为另一种表现形式
• Dex2C
首先也是将关键代码注册为native函数,主要借助于JNI反射技术,将Java层的方法全部反射为native层,增大分析难度。之后再通过混淆、字符串加密等操作生成so,最后将so进行加固保护。
优劣
• 优点
加固强度高,目前没有公开的脱壳工具 经过混淆加密后很难还原原函数
• 缺点
效率较低,启动、运行时都比较耗时稳定性、可控性差,容易产生崩溃
特点
除一、二代全部特点外 so代码虚拟化 对抗之前所有的脱壳方法
so加密
section加密
• 原理
将关键方法,存放在自定义的section中,通过解析每个section,将我们自定义的section进行加密。因为so在加载时会优先加载.init_array,所以将解密方法放在.init_array中,获取内存中各个section的起始地址和大小,将需要解密的section还原。
函数加密
• 原理
解析so,根据方法名找到指定的方法,将方法进行加密。加载so时,获取指定方法的地址,通过解密方法将指定方法解密。
特点
由于原本的指令已经被加密成其他的字节,IDA等静态分析工具中会出现大段无法识别的代码
各厂商特征
除此外还有很多大佬们可以自行总结
某梆
lib/libDexHelper.so、lib/libDexHelper-x86.so、
某加密
assets/ijiami.ajm、assets/ijiami.dat、assets/ijm_lib/libexec.so、assets/ijm_lib/libexecmain.so
某企鹅
lib/libshell-super.2019.so、lib/libshella-4.1.0.29.so
某数字
assets/libjiagu.so、assets/libjiagu_x86.so
某迦
lib/libxloader.so
assets/libvdog、assets/libvdog64、assets/libvdog-x86
某付盾
lib/libegis.so、lib/libegis-x86.so
脱壳工具
FRIDA-DEXDump
原理
通过Frida在内存中搜索dex\n035,因为Dex的头部都会存在一个dex\n035的模数,所以通过在内存中搜索dex\n035可以搜索到Dex文件。对于一些Dex它们被抹去了头部信息,对于这样的情况,FRIDA-DEXDump也提供了对应的方法,通过遍历当前进程中所有可以读的内存段,通过判断这个段的大小和Dex文件中一些关键区域的关系可以判断是否为一个Dex。
使用
在FRIDA-DEXDump GitHub中下载代码到本地或者通过pip3 install frida-dexdump
安装
用法就是首先打开,我们需要脱壳的软件,然后执行python main.py -d
程序执行开始检索内存中的Dex
检索到后会保存在SavePath对应的目录下
也可以通过python main.py -h
查看它的其他使用方法
最终得到它脱出来的Dex,可以看下它脱壳效果
Youpk
原理
从ClassLinker中遍历所有DexFile对象,在虚拟机中Dex文件都用DexFile对象来表示,并Dump出所有Dex文件。此时只是整体Dump,Dex中的方法还未还原。遍历DexFile中的ClassDef结构,获取到所有Class,主动调用Class中的所有的方法,让程序强制走switch解释器执行,在解释器中添加Hook代码,当方法执行时自动保存CodeItem。根据保存的CodeItem和Dump下来的Dex进行合并,还原Dex中被抽取的指令。
使用
首先下载刷机包和还原工具,目前仅支持刷pixel 1手机
解压相关的镜像,fastboot flash xxx /Download/xxx.img
依次刷入
配置待脱壳的App包名adb shell "echo com.xxx >> /data/local/tmp/unpacker.config"
启动Apk等待脱壳,每隔10秒将自动重新脱壳(已完全dump的dex将被忽略), 当日志打印unpack end时脱壳完成
dump文件路径为/data/data/包名/unpacker,使用adb命令将文件pull出
使用dexfixer.jar修复Dex,java -jar dexfixer.jar /path/unpacker /path/output
最后我们对比一下还原前后的代码
还原前
还原后
总结
本文简单地总结了一下Android加固的背景和发展历史,也介绍了一些目前常见的脱壳工具。对于so加密的情况,目前也有许多方法应对,比如ida动态调试dump内存中的so,GG模拟器dump内存,frida dump so,unidbg等等。对于混淆的so,也有jnitrace,unidbg,还有hluwa大佬的大作obpo等工具辅助我们分析。除此以外还有还有一些优秀的脱壳工具如Fart等,我也没有再做介绍了。总之感谢这些大佬们的努力和开源,没有你们就没有白嫖的我们,哈哈...