美文网首页开卷有益程序员python热爱者
用python一步步解剖dex文件(一)

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

作者: 虎七 | 来源:发表于2018-01-24 01:50 被阅读344次

    请勿转载,谢谢!!! 


    目标

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

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


    知识点

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

    1. mmap

    2. struct

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

    mmap

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

    创建的格式:

    mmap函数原型

    例如:

    mmap调用示例

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

    直接读写

    更详细介绍参考:

    http://blog.csdn.net/zdy0_2004/article/details/53200250

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

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

    struct

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

    使用示例:

    静态方法调用

    其中'<L',指定了要从数据流中读取的格式,第二个参数则是数据流的对应位置。

    格式对应表如下:

    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',把函数名称填写进去。

    打印方法列表(代码)

    画风是这样的:

    打印方法列表(结果)

    打印类属性列表

    打印类属性列表(代码)

    画风是这样的:

    打印类属性列表(结果)

    保存信息

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

    保存到文件

    【未完待续】


    相关文章

      网友评论

        本文标题:用python一步步解剖dex文件(一)

        本文链接:https://www.haomeiwen.com/subject/xyaeaxtx.html