开卷有益python热爱者码农的世界

用python一步步解剖dex文件(二)

2018-02-05  本文已影响206人  虎七

请勿转载,谢谢!!! 


连续半个月重感冒和咳嗽,这个时候才倍感身体的重要。

所以身体才是革命的本钱啊。。。

言归正传。


编码格式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上修改呢? 后面继续研究和探讨。

【待续】

上一篇下一篇

猜你喜欢

热点阅读