用python一步步解剖dex文件(二)
请勿转载,谢谢!!!
连续半个月重感冒和咳嗽,这个时候才倍感身体的重要。
所以身体才是革命的本钱啊。。。
言归正传。
编码格式uleb128
先介绍下dex中用到的一种编码方式uleb128。
简单来说,它是对int(4字节)数据的一个可变长度编码,使得原先固定长度(4字节)的数据,可以通过更少的字节表示出来;而且这个可变长度不超过5。
假定有数据序列:a b c d e,我们把它们用二进制表示出来:
aaaaaaaa bbbbbbbb cccccccc dddddddd eeeeeeee
如果每字节的高位开头是1,那么继续往后;如果高位开头是0,那么终止在这里;然后把所有的高位都去除,反序拼出来的二进制,就是最后的结果。
比如这样的:
1aaaaaaa 1bbbbbbb 1ccccccc 0ddddddd eeeeeeee
一直到第4个,它是以0为高位开头的,那么这个有效数据就是前四个字节组成,并且反序如下:
ddddddd ccccccc bbbbbbb aaaaaaa
如上,第一是反序,第二是每个字节只有7位。
这个就是简单的uleb128的示范。
解码程序如下:
uleb128解码数据结构
类信息其中classDataOff是指向类数据信息区域的一个偏移数值。
类数据信息从这里可以看到,类数据其实包含了四种信息,静态属性,实例属性,直接方法和虚方法。
字段和方法对于DexMethod来说,它代表一个类的方法,方法中当然是包含代码的,那么代码(字节码)所在的位置,通过codeOff来表示。
另外加一个说明,这里的fieldIdx和methodIdx其实是一个diff,也就是说,在field列表和method列表中,第一个Idx是真实的Idx,后面的Idx都是在这个基础上做的diff。
字节码信息数据追踪
按照上面的数据结构,我们走一遍真实的dex数据。
头部区域中的类区域信息如上一篇所讲,在dex的头部区域中,有两个字段分别表示类信息的偏移值和类的个数;上图中,第一个四字节(07)表示类的总个数,第二个四字节(2ac)就表示类信息的偏移值。
我们找到2ac处: (按照类定义,每个类信息占据4*8=32个字节)
类信息数据区我们把这7个类的信息,按着类的结构解析并且打印出来:
打印类信息结果是这样的:
类信息也就是说,我写的这个Demo程序中,在dex里包含了7个类的信息,如上所示;其中MainActivity就是主Activity。
我们现在只看这个MainActivity的数据:
MainActivity类信息其中类数据的偏移数值,是第7个四字节,也就是5cc (cc05 0000)。
MainActivity类数据信息这里使用到了前面锁说的uleb128格式:00 00 01 01(四个字节)
按上面的解释方法,每个字节都是以0开头的,所以就是最后的结果了,那么定义出来的:
static_field_size = 0
instance_field_size = 0
direct_method_size = 1
virtual_method_size = 1
也就是说,MainActivity中没有属性信息,但是有一个直接方法和一个虚方法。
接下来的10个字节,就是直接方法和虚方法的信息(属性为空忽略),它们也是用uleb128进行编码的。
解码信息这两个方法分别是:
A: method_idx = 5, code_off = 4f0
B: method_idx = 6, code_off = 508
根据上一篇文章中介绍的,我们查找到方法信息如下:
method-idx = 5 method-idx = 6其中第一个是构造函数<init>,第二个就是我们很熟悉的android方法,onCreate。
继续,我们跟踪onCreate方法的信息,它的偏移是508(十六进制):
onCreate方法体数据对照结构如下:
onCreate方法体信息其中从6f20开始,连续的32个字节,就是该方法体的字节码信息。
接下来的任务,就是把这32个字节,翻译成可以识别的指令信息,就是反汇编过程了。
在这之前,我们总结下类信息的打印程序:
读取类信息 读取类数据(一) 读取类数据(二)用到的一些方法:
读取属性信息 读取方法信息 读取注解信息 读取DexTypeList类型 读取指令信息反汇编
前面提到,onCreate方法中的指令字节序列,就是下面的32个字节数据。
指令集数据我们知道指令一般是指令符+指令数组成的,第一个字节一般标识一个特定的指令。
在dex格式中,这个指令既表示具体的操作(字节码格式),又对应一个数据解析格式(说明格式)。
字节码格式文档:
https://source.android.google.cn/devices/tech/dalvik/dalvik-bytecode
说明格式文档:
https://source.android.google.cn/devices/tech/dalvik/instruction-formats
比如第一个字节是6f,对照文档如下:
字节码格式
字节码格式6e..72是数字范围,下面的35c就代表着数据说明格式。
说明格式对于上面的35c来说,3表示本次指令共计3个字(2个字节),5表示寄存器数目,而c是一个助记符标识常量:
助记符这3个字是怎么排列呢?
字节排列把6f开始的3个字,如上面格式依次排开:
206f 0001 0032
对照格式得出:
A = 2, G = 0, op = 6f, BBBB = 1, C = 2, D = 3, E = 0, F = 0
这里的A = 2,那么对应格式:
格式A = 2也就是op {v2, v3}, kind@0001。
再对照字节码格式文档,其实就是:
invoke-super {v2, v3}, meth@0001
根据method的信息,查询出: meth@0001是
meth@0001那么前3个字的反汇编代码就是:
invoke-super {v2, v3}, Landroid/app/Activity; --> V onCreate(Landroid/os/Bundle;)
这个不就是调用父类Activity的onCreate方法吗?
对照源代码:
源代码同样的方式,可以把32个字节的数据,全部反汇编出来。
我们是为了学习Dex的结构,所以会这么大费周折;更加方便的方式,是直接用现有的反编译工具,将dex反编译成smali,可以更加直观的看到信息。
在面对一个未知的应用时,我们没有源码,如果去修改它的信息呢?(假定没有加固)
最简单的方式,还是反编译出smali,通过修改smali再重新打包。
如果是,我们直接在dex上修改呢? 后面继续研究和探讨。