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

目标

这里的dex文件,就是android中dalvik虚拟机运行的程序格式文件;art虚拟机,也是基于dex格式再创作的。

我们的目标,就是直观的了解下,这个dex到底是什么东东。

知识点

在开始之前,先熟悉python的两个知识点:

1. mmap

2. struct

这两个是python中的内置模块,分别是映射文件和结构化操作。

mmap

了解linux的是不是感觉很亲切?是的,功能是类似的,把一个文件映射到一段内存,然后操作内存就像直接读写文件一样。

创建的格式:

mmap函数原型

例如:

mmap调用示例

使用更简单,可以直接操作数组:

直接读写

更详细介绍参考:

对linux的mmap感兴趣的可以参考:

http://ju.outofmemory.cn/entry/224106

struct

struct是对二进制流进行操作的一个模块,直译就是数据结构,很像c语言中的结构体struct声明。它提供了从二进制流读取数据(unpack),和把数据写入二进制流的方法(pack)。

使用示例:

静态方法调用

其中'

格式对应表如下:

struct格式对应表

还可以指定字节顺序:

struct字节序

除此外,还可以指定缓冲区,这里不介绍,参考:

https://www.cnblogs.com/coser/archive/2011/12/17/2291160.html

DEX文件格式

言归正传,我们的目标是dex文件。

文件结构:

dex文件结构

文件头结构

文件头固定0x70长度,也就是112个字节。

其中signature计算的范围是整个文件除去magic, checksum和signature三部分的其余所有部分。

checksum计算的范围是除去magic和checksum两部分的其余所有部分。

magic是dex文件标识,固定为'dex\n035'

其余大部分字段,都是在划分区域,通过(size, off)的方式,就是圈定文件的[off, off+size]部分为相关段的数据;后续的索引,也是建立在这个基础上的。

dex文件头

索引区结构

索引区有五大区域,分别是字符串区域,类名区域,函数原型区域,类属性区域,方法区域。其中每个区域的范围,可以通过头部的信息获取到,[off, size]。

这五个区域,是有相互关系的,一个区域记录的往往是另一个区域的索引。

字符串区域

从下图看,就是每四个字节是一项,这四个字节代表的是真实字符串的偏移值。

字符串区域结构

如果把这个数值作为文件的偏移值,相关的内存是这样的:

其中size是字符串的长度,str是size大小的一串字符。

字符串真实值

类名区域

这个区域存储的是所有的类名索引,结构如下,每一项用4个字节存储,表示字符串区域的索引;这里就已经有两重索引了,一重似乎类索引包含的字符串索引,一重是字符串本身的索引。

类名索引结构

函数原型区域

这个区域存储的是所有函数的原型汇总,每个原型由三部分组成,(名称,返回类型,参数类型),当然它们存储的都是索引数值。

其中名称的索引是字符串区域的索引,返回类型是类名区域的索引,而参数类型则导向另一个地方,它存储的是一个偏移数值。

函数原型结构

这个偏移数值最终导向的结构,是一个数组列表结构,由[size, itemlist]格式组成,其中size是参数的总个数,itemlist就是size大小的参数列表,列表的每一项又是一个索引数,指向类名区域的索引。

也就是说,这个结构其实就是size大小的一个类名数组,也就是我们要找寻的参数类型。

函数原型导向结构

方法区域

方法结构相比原型要简单很多,也是三部分组成,(类名,函数原型,方法名),当然也都是存的索引数值。

其中类名是指向类名区域的索引,函数原型是指向函数原型区域的索引,方法名是指向字符串区域的索引。

方法结构

类属性区域

类的属性和类的方法是相似的,由三部分组成(类名,字段类型,名称),也都是存储的索引数值;

其中类名是指向类名区域的索引,字段类型是指向类名区域的索引,名称则是指向字符串区域的索引。

类属性结构

结构先分析到这里为止,接下来我要把这些结构对应的数据都打印出来。

DEX解析库

首先分享一个现有的python库:

https://github.com/bunseokbot/dexparser

这个库是两年前写的,非常简洁明了。

其实关于dex解析的库还是很多的,我比较喜欢这个库的直观性,说到底是解析Dex的文件格式,这种c风格的写法,非常浅显易懂,直接触摸到文件结构。也因为它的简单,在它的基础之上,我们可以继续做扩充。

这个dexparser库利用了mmap和struct模块,对dex文件做解析。

画风是这样的:

初始化和解析文件头

这样的:

文件头数据赋值

还有这样的:

读取字符串列表

这里用struct,直接从dex文件的映射区域self.mmap中,通过索引读出数据,然后根据结构说明继续转索引或者直接使用。

上面的string_list代码,首先锁定了string索引的区域,[string_ids_size, string_ids_off],也就是索引的总数是string_ids_size大小;

然后根据这个大小遍历,分别从区域相关位置中取出某一个字符串的索引off;

接着,通过这个偏移数值定位到字符串真实数据的区域self.mmap[off],并根据格式[size, char]进行读取;

最后就形成了一个字符串的队列。

dexparser模块中的其它函数也是类似的。

解析和打印

现在用这个库,我要把前面介绍的文件头和索引区域的五大区域都打印出来。

构建一个Dexparser对象

(自己解压一个Apk,拿出其中的dex作为测试用)

初始化

打印文件头

打印文件头(代码)

画风是这样的:

打印文件头(结果)

打印字符串列表

打印字符串(代码)

画风是这样的:

打印字符串(结果)

打印类名列表

打印类名列表(代码)

画风是这样的:

打印类名列表(结果)

打印函数原型列表

如下图所示,Dexparser中提供的列表其实只有索引,我在这里进一步做了补充,根据函数原型的结构,将参数原型的描述也解析了出来。

打印函数原型(代码)

画风是这样的:

其中,函数名的部分我用了'%s'来替代,是为了后面解析方法区域时用的。

打印函数原型(结果)

打印方法列表

下图中就用到了上面函数原型列表中的'%s',把函数名称填写进去。

打印方法列表(代码)

画风是这样的:

打印方法列表(结果)

打印类属性列表

打印类属性列表(代码)

画风是这样的:

打印类属性列表(结果)

保存信息

最后,把打印出来的五大索引区域的列表信息,都保存到文件中。

保存到文件

【未完待续】

微信公众号: 一叶谷

  • 发表于:
  • 原文链接http://kuaibao.qq.com/s/20180218G0CM2Y00?refer=cp_1026
  • 腾讯「云+社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。

扫码关注云+社区

领取腾讯云代金券