原标题:Android加固原理研究
《【1.13杭州站沙龙】主持小姐姐让我跟大家说她是单身···》
点击本日推送第二条 即刻报名!
一、DEX文件格式分析1、文件布局
文档可以参考官方文档:http://source.android.com/devices/tech/dalvik/dex-format.html
dex 文件可以分为3个模块,头文件(header)、索引区(xxxx_ids)、数据区(data)。头文件概况的描述了整个 dex 文件的分布,包括每一个索引区的大小跟偏移。索引区表示每个数据的标识,主要是指向数据区的偏移。
010Editor 中除了数据区(data)没有显示出来,其他区段都有显示,另外 link_data 在模板中被定为 map_list
2、文件头header
magic: 这个是固定值,用于识别 dex 文件。转化为字符串为 "dex 035", 中间是一个换行,后面035是版本号。
checksum:文件校验码,使用 alder32 算法校验文件除去 maigc、checksum 外余下的所有文件区域,用于检 查文件错误。
signature:使用 SHA-1 算法 hash 除去 magic、checksum 和 signature 外余下的所有文件区域, 用于唯一识别本文件 。
file_size: dex文件大小
header_size: header 区域的大小,目前是固定为 0x70
endian_tag: 大小端标签,dex 文件格式为小端,固定值为 0x12345678 常量
map_off: map_item 的偏移地址,该 item 属于 data 区里的内容,值要大于等于 data_off 的大小,处于 dex 文件的末端。
其他元素都是成对出现的。_off 表示元素的偏移量,_size 表示元素的个数
3、索引区——string_ids
string_ids 区段描述了 dex 文件中所有的字符串。格式很简单只有一个偏移量,偏移量指向了 string_data 区段的一个字符串:
其中 data 保存的就是字符串的值。string_ids 是比较关键的,后续的区段很多都是直接指向 string_ids 的 index。在写工具进行比较的时候也需要提取到 string_ids。
4、索引区—— type_ids
type_ids 区索引了 dex 文件里的所有数据类型,包括 class 类型,数组类型(array types)和基本类型(primitive types)。区段里的元素格式为 type_ids_item
type_ids_item 里面 deor_idx 的值的意思,是 string_ids 里的 index 序号,是用来描述此 type 的字符串。
5、索引区—— proto_ids
proto 的意思是 method prototype 代表 java 语言里的一个 method 的原型 。proto_ids 里的元素为 proto_id_item
shorty_idx: 跟 type_ids 一样,它的值是一个 string_ids 的 index 号 ,最终是一个简短的字符串描述,用来说明该 method 原型。
return_type_idx: 它的值是一个 type_ids 的 index 号 ,表示该 method 原型的返回值类型。
parameters_off: 指向 method 原型的参数列表 type_list,若 method 没有参数,值为0。参数列表的格式是 type_list,下面会有描述。
6、索引区—— field_ids
filed_ids 区里面有 dex 文件引用的所有的 field。区段的元素格式是 field_id_item,结构如下:
class_idx: 表示 field 所属的 class 类型,class_idx 的值是 type_ids 的一个 index,并且必须指向一个 class 类型。
type_idx: 表示本 field 的类型,它的值也是 type_ids 的一个 index 。
name_idx: 表示本 field 的名称,它的值是 string_ids 的一个 index 。
7、索引区—— method_ids
method_ids 是索引区的最后一个条目,描述了 dex 文件里的所有的 method。method_ids 的元素格式是 method_id_item,结构跟 fields_ids 很相似:
class_idx: 表示 method 所属的 class 类型,class_idx 的值是 type_ids 的一个 index,并且必须指向一个 class 类型。ushort类型也是为什么我们说一个 dex 只能有 65535 个方法的原因,多了必须分包。
proto_idx: 表示 method 的类型,它的值也是 type_ids 的一个 index。
name_idx: 表示 method 的名称,它的值是 string_ids 的一个 index。
8、索引区—— class_defs
class_def 区段主要是对 class 的定义,它的结构很复杂,看的我有点晕
class_idx: 类名序号,值是type_ids的一个index
class_def: 类定义结构体
static_values_off: 静态变量值偏移
class_data_off: 类定义偏移
class_data: 类定义结构体
direct_methods_size: 直接函数个数
virtual_methods_size: 虚函数个数
virtual_methods: 虚函数结构体
code_off: 函数代码偏移
9、索引区—— map_list
map_list 中大部分 item 跟 header 中的相应描述相同,都是介绍了各个区的偏移和大小,但是 map_list 中描述的更加全面,包括了 HEADER_ITEM 、TYPE_LIST、STRING_DATA_ITEM、DEBUG_INFO_ITEM 等信息。
map_list 里先用一个 uint 描述后面有 size 个 map_item,后续就是对应的 size 个 map_item 描述。 map_item 结构有 4 个元素: type 表示该 map_item 的类型,Dalvik Executable Format 里 Type Code 的定义; size 表示再细分此 item,该类型的个数;offset 是第一个元素的针对文件初始位置的偏移量; unuse 是用对齐字节的,无用。
二、DEX文件混淆加密
混淆加 密主要是为了隐藏 dex 文件中关键的代码,力度从轻到重包括:静态变量的隐藏、函数的重复定义、函数的隐藏、以及整个类的隐藏。混淆后的 dex 文件依旧可以通过 dex2jar jade 等工具的反编译成 Java 源码,但是里面关键的代码已经看不到了。
四种混淆加密的实现方式都是通过修改 class_def结构体中字段实现的。
1、静态变量隐藏
static_vaules_off保存了每个类中静态变量的值的偏移量,指向 data 区里的一个列表,格式为 encode_array_item,如果没有此项内容,该值为0。所以要实现静态变量赋值隐藏只需要将 static_values_off值修改为0。
2、函数重复定义
class_def -> class_data -> virtual_methods -> code_ff表示的是某个类中某个函数的代码偏移地址。这里需要提到一个概念:Java 中所有函数实现都是虚函数,这一点和 C++ 是不一样的,所有这里修改的都是 virtual_methods中 code_off。
实现方式:读取第一个函数的代码偏移地址,将接下来的函数偏移地址都修改为第一的值。
3、函数隐藏
class_def -> class_data -> virtual_methods_size和 class_def -> class_data -> direct_methods_size记录了类定义中函数的个数,如果没有定义函数则该值为0。所以只要将该值改为0,函数定义就会被隐藏。
4、类定义隐藏
class_def -> class_data_off保存了具体类定义的偏移地址,也就是 class_def -> class_data的地址,如果该值为0则所有实现将被隐藏。隐藏后会把类定义的所有东西都隐藏包括成员变量,成员函数,静态变量,静态函数。
三、APK加壳(加固)1、原理解析
在加固的过程中需要三个对象:
需要加密的Apk(源Apk)
壳程序Apk(负责解密Apk工作)
加密工具(将源Apk进行加密和壳Dex合并成新的Dex)
2、主要步骤:
拿 到需要加密的Apk和自己的壳程序Apk,然后用加密算法对源Apk进行加密;再与壳Apk的Dex进行合并得到新的Dex文件;然后替换原壳程序中的 dex文件即可,得到新的Apk。那么这个新的Apk我们也叫作脱壳程序Apk,他已经不是一个完整意义上的Apk程序了,他的主要工作是:负责解密源 Apk,然后加载Apk,让其正常运行起来。
3、 源Apk和壳Dex合并生成新的Dex
只要关注上面红色标记的三个部分:
1) checksum
文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。
2) signature
使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。
3) file_size
Dex 文件的大小 。
因为将一个文件(加密之后的源Apk)写入到Dex中,那么肯定需要修改文件校验码(checksum).因为他是检查文件是否有错误。那么signature也是一样,唯一识别文件的算法。还有就是需要修改dex文件的大小。
这里还需要一个操作,就是标注一下加密的Apk的大小,因为在脱壳的时候,需要知道Apk的大小,才能正确的得到Apk。那么这个值放到哪呢?这个值直接放到文件的末尾就可以了。
总结一下:修改Dex的三个文件头,将源Apk的大小追加到壳dex的末尾就可以了。
修改之后得到新的Dex文件样式如下:
4、加壳代码
四、APK脱壳
步骤:
1、从脱壳Apk中获取到Dex文件
2、从脱壳Dex中得到源Apk文件
3、解密源程序Apk
4、加载解密之后的源程序Apk
5、找到源程序的Application程序,让其运行
五、动态加载预备知识:
android中的类加载器:http://blog.csdn.net/jiangwei0910410003/article/details/41384667
android中的动态加载机制:http://blog.csdn.net/jiangwei0910410003/article/details/17679823
第一个思路:替换LoadedApk中的mClassLoader
加载Activity的时候,有一个很重要的类:LoadedApk.Java,这个类是负责加载一个Apk程序的
内部有一个mClassLoader变量,是负责加载一个Apk程序的,只要获取到这个类加载器就可以了。他不是static的,所以还得获取一个LoadedApk对象。再去看一下另外一个类:ActivityThread.java的源码
ActivityThread类中有一个自己的static对象,然后还有一个ArrayMap存放Apk包名和LoadedApk映射关系的数据结构,那么分析清楚了,下面就来通过反射来获取mClassLoader对象吧。
第二思路:合并PathClassLoader和DexClassLoader中的dexElements数组
PathClassLoader和DexClassLoader类的父加载器是BootClassLoader,他们的父类是BaseDexClassLoader。里面有一个DexPathList对象,在来看一下DexPathList.java源码:
首先看一下这个类的描述,还有一个Elements数组,这个变量是专门存放加载的dex文件的路径的,系统默认的类加载器是PathClassLoader,一个程序加载之后会释放一个dex出来,这时候会将dex路径放到里面,
当然DexClassLoader也是一样的,可以将DexClassLoader中的dexElements和PathClassLoader中的dexElements进行合并,然后在设置给PathClassLoader中。
文/Mob开发者平台 Android开发专家 张冰峰
大家都在看
805941275 435399051返回搜狐,查看更多
责任编辑: