先从DEX文件格式开始分析,因为dex是安卓应用程序编译后的结果,直接与Android虚拟机进行交互的文件。
有关DEX文件的介绍可以参照我之前写的这篇文章。
看这个图片很复杂,其实对于初学者来说,只要了解到这一点就行了:
Android应用的Java代码,所有的数据格式、数据、算法都是经过转化,变成了以上的格式存储,储存的是具体内容的偏移地址。
比如索引区的string_ids,描述了所有程序中的字符串,格式也很简单,只有一个偏移量地址,用文件偏移地址来记录、描述字符串的内容。文件偏移地址和编程经常会用到的内存地址区别,我们了解内存地址,因为是在程序运行的时候,会用到,但是此时的android应用是保存在dex文件中的,所以继续我们成为文件偏移地址,并用来记录描述的。
type_ids,描述了所有的数据类型,包括class类型,数组类型和基本类型。
言而总之,程序被分区分块的进行转换和保存了。
dex文件和加固有什么关系?
如果dex文件未加密,未混淆等安全加固,我们可以通过apktool或者jeb拿到汇编源代码或源代码,之前也介绍过。
混淆和加密主要是为了隐藏dex文件中关键的代码,力度从轻到重:静态变量的隐藏、函数的重复定义、函数的隐藏、以及整个类的隐藏。混淆后的dex文件依旧可以通过dex2jar jeb等工具反编译成Java源码,但是里面的关键代码已经看不见了。
四种混淆加密的实现方式都是通过修改class_def结构体字段实现的。
static_values_off保存了每个类中静态变量值的偏移量,指向data区的一个列表,格式为encode_array_item,如果没有此项内容,此值为0,所以要实现静态变量赋值隐藏只需要将static_cales_off值修改为0即可。
class_def -> class_data -> virtual_methods -> code_ff表示的是某个类中某个函数的代码偏移地址。
这里提一点,Java中所有函数实现都是虚函数,这一点和C++是不一样的,所以所有这里修改的都是virtual_methods中的code_off。
大概的原理和实现方式就是,读取第一个函数的代码偏移地址,将接下来的函数偏移地址都修改为第一的值。
根据文件偏移量确定函数的,这样就实现了函数重复定义。
class_def -> class_data -> virtual_methods_size和class_def -> class_data -> direct_methods_size记录了类定义中函数的个数,如果没有定义函数则该值为0.所以只要将该值改为0,函数定义就会被隐藏。
手段比较重的隐藏是直接隐藏了类的所有东西,包括成员变量,成员函数,静态变量,静态函数。
在class_def -> class_data_off保存了具体类定义的偏移地址,也就是class_def -> class_data的地址,把该值为0即可实现隐藏。
源APK经过加密,然后用壳APK(没错,壳也是APK)的dex和源APK的dex文件进行融合,得到一个全新的dex,然后再打包成新的APK,这个新的APK已经不是传统意义的APK了,他的主要工作是:负责解密源Apk,然后加载Apk,让它正常运行起来。
我们主要看head头,因为head头可以用来校验整个包的完整性和安全性。
checksum
文件校验码,使用alder32算法校验文件除去magic, checksum外余下的所有文件区域,用于检查文件错误。破解后如果提示"文件已损坏",就是这个地方数据出了问题。
signature
使用SHA-1算法hash除去magic, checksum和signature外余下的所有文件区域,用于唯一识别本文件。
File_size
Dex文件的大小。
因为将一个文件(加密之后的源Apk)写入到Dex中,那么肯定需要修改文件校验码(checksum)、signature,因为他是检查文件和签名是否有错误,还有就是需要修改dex文件的大小。
还需要一个操作,就是标注一下加密的APK的大小,因为在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。这个值放到哪里呢,直接放到文件末尾就可以了。
(1)源程序项目(需要加密的Apk)
(2)脱壳项目(解密源Apk和加载Apk,其实就是壳APK)
(3)对源Apk进行加密和脱壳项目的Dex的合并
2、从脱壳Dex中得到源Apk文件
3、解密源程序Apk
4、加载解密之后的源程序Apk
5、找到源程序的Application程序,让其运行。
两种思路:
加载一个Activity肯定不像加载一般的类那样,因为activity作为系统的组件有自己的声明周期,所以不能像其他类一样自定义一个DexClassLoader类加载器来加载插件中的Activity。它有专门的启动流程这里不做过多描述,只需要知道加载Activity的时候,有一个很重要的类:LoadedApk.Java
这个类是负责加载一个Apk程序的,其内部有一个mClassLoader变量,是负责加载一个Apk程序的,只要获取到这个类加载器就可以获取到APK内容了,所以还得获取一个LoadedApk对象。再去看一下另外一个类ActivityThread.java的源码
ActivityThread类中有一个自己的static对象,然后还有一个ArrayMap存放Apk包名和LoadedApk映射关系的数据结构,那么分析清楚了,下面通过反射来获取mClassLoader对象就可以拿到想要的Apk包了。
PathClassLoader和DexClassLoader类的父加载器是BootClassLoader,他们的父类是BaseDexClassLoader。里面有一个DexPathList对象,在来看一下DexPathList.java源码:
首先看一下这个类的描述,还有一个Elements数组,这个变量是专门存放加载的dex文件的路径的,系统默认的类加载器是PathClassLoader,一个程序加载之后会释放一个dex出来,这时候会将dex路径放到里面,
当然DexClassLoader也是一样的,可以将DexClassLoader中的dexElements和PathClassLoader中的dexElements进行合并,然后在设置给PathClassLoader中。
https://www.jianshu.com/p/6a504c7928da