目标:

dex文件:Android中dalvik虚拟机运行的程序格式文件;
art虚拟机:基于dex格式再创作;看看这个dex到底是个什么东西;

知识点:

mmap和struct两个python内置模块

mmap

映射文件

m = mmap.mmap(fileno, length[, flags[, prot[, access[, offset]]]])
ex:
f = open(filedir, 'rb') # 通过二进制只读打开文件
m = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)

m[index_s: index_e]可以直接操作数组

struct

对二进制流进行操作的一个模块
从二进制读取数据(unpack,把数据写入二进制流(pack

struct.unpack('<L', m[0x38: 0x3C])[0]

Dex文件格式

文件结构:

  1. 文件头:header;
  2. 索引区:string_ids, type_ids, proto_ids, field_ids, method_ids;
  3. 数据区:class_defs, data, link_data;
    p1

文件头

固定0x70长度,也就是112个字节

struct DexHeader {    
    u1 magic[8];    # magic是dex文件标识,固定为'dex\n035'    
    u4 checksum;    # checksum计算的范围是除去magic和checksum两部分的其余所有部分    
    u1 signature[kSHA1DigestLen];    # 计算范围是出去以上三部分    
    u4 fileSize;    
    u4 headerSize;                # offset to start of next section    
    u4 endianTag;    
    u4 linkSize;    
    u4 linkOff;    
    u4 mapOff;    
    u4 stringIdsSize;    
    u4 stringIdsOff;    
    u4 typeIdsSize;    
    u4 typeIdsOff;    
    u4 protoIdsSize;    
    u4 protoIdsOff;    
    u4 fieldIdsSize;    
    u4 fieldIdsOff;    
    u4 methodIdsSize;    
    u4 methodIdsOff;    
    u4 classDefsSize;    
    u4 classDefsOff;    
    u4 dataSize;    
    u4 dataOff;
}

索引区结构

五大区域:字符串;类名;函数原型;类属性;方法
每个区域的范围,可通过头部的信息获取到,[off, size]

一个区域的记录往往是另一个区域的索引;

字符串区域

每四个字节是一项,代表的是真实字符串的偏移值

struct DexStringId {
    u4 stringDataOff;
    // file offset to string_data_item
}

类名区域

重点在这个“名”吧,存储的是所有类名的索引,每四字节是一项,表示字符串区域的索引

问题:这个索引指向StringIds?

struct DexTypeId {
    u4 descriptorIds;    // 指向DexStringId列表的索引
}

函数原型区域

存储的是所有函数的原型汇总三部分:

  1. 名称;
  2. 返回类型;
  3. 参数类型;

名称索引 -> 字符串区域索引
返回类型 -> 类名区域索引
参数类型 -> 导向另一个地方,存储的是一个偏移数值

struct DexProtoId {
    u4 shortIdx;                 // 指向DexStringId列表的索引
    u4 returnTypeIdx;            // 指向DexTypeId列表的索引
    u4 parametersOff;            // 指向DexTypeList的偏移
}

参数类型索引最终导向的结构是一个数组列表结构,由[size, itemlist]构成,其中size是参数的总个数,itemlist是size大小的参数列表,列表的每一项又是一个指向类名的索引

struct DexTypeList {
    u4 size;
    DexTypeItem list[1];         // 大小为3的参数列表
}

struct DexTypeItem {
    u2 typeIdx;
}

方法区域

存储的是所有方法的原型汇总三部分:

  1. 类名;
  2. 函数原型;
  3. 方法名;

    struct DexMethodId {

    u2 classIdx;    // 指向DexTypeId列表索引,类名索引
    u2 protoIdx;    // 指向DexProtoId列表索引,原型索引
    u4 nameIdx;    // 指向DexStringId列表索引,字符串索引

    }

类属性区域

与方法类似,三部分组成

  1. 类名;
  2. 字段类型;
  3. 名称;

struct DexFieldId {

u2 classIdx;    // DexTypeId中的索引下标
u2 typeIdx;
// DexTypeId索引下标
u4 nameIdx;    // DexStringId索引下标

}

哦吼,到了最激动人心的打印结构环节了

最后

字符串指向还有一个结构

struct DexString {
    u1 size;    // 字符串长度
    u1 str;    // size大小的字符串
}

这样看所有的索引范围都是 0 - 列表最大长度

Dex解析库

pip install dexparser -i https://pypi.doubanio.com/simple/

这种失败了,因为我更新到最新的py3.7了

只能下载源码,将里面所有print改了就可以了,其实也就只有一处需要改

def virtualmethod_data(self, offset):
    print(hex(offset))

这里理解一下源码里string_list方法

上面的字符串结构可以在string_list中得到应用

def string_list(self):
    string_data = []     // 这是需要返回的string_data
    string_ids_size = self.header['string_ids_size']
    string_ids_off = self.header['string_ids_off']
    for i in range(string_ids_size):
        off = struct.unpack('<L', self.mmap[string_ids_off + (i*4):string_ids_off + (i*4) + 4])[0]
        # c_size = ord(self.mmap[off])   这里是一处源码的错误
        c_size = self.mmap[off]                    
        c_char = self.mmap[off+1: off+1+c_size]
        string_data.append(c_char)
    self.string_data = string_data
    return string_data

可以理解为,size是个数,off是偏移,u4的意思是四位,所以每取一个索引才会+4;

顺着这个索引,在mmap里找到对应下一个数据结构,mmap[off]直接就指向那个size,mmap[off+1: off+1+c_size]就直接是取出的字符串值,真的有点厉害

到源码目录下

ipython
from dexparser import Dexparser
import structdex = Dexparser('../../classes.dex')
dex.string_list()

得到这个东西
p2

打印类名,因为只能返回typeid,而这个值就是个string_ids的索引,所以得到索引之后再去string_list里面找就好了

type_list = dex.typeid_list()
for i in type_list:
    print(string_list[i])

这里就不做异常处理了,直接找就完事了隐隐约约觉得会从dex文件结构解析上升到反汇编,真的是宝藏了!

先到这吧

基本上操作都是,调用方法取索引,在对应的基本索引结构里将string取出来