一、说明
1.1 背景说明
看《加密与解密》的时候反复听说“PE文件格式”,到Android安全兴起就不断听说“dex文件格式”。意思是看得懂的,但自己不能手解析一番总觉得不踏实,所以决定写个程序来解析一番。
本文其实算是姜维的Android逆向之旅---解析编译之后的Dex文件格式的Python实现版。
1.2 dex文件格式说明
类似exe文件是windows上的可执行文件,dex文件就是android中的可执行文件;pe格式是exe文件的格式,dex文件格式就是dex文件的格式。下边直接偷两张图过来说明dex文件格式
dex文件格式概况如下:
dex文件格式详细版如下:
二、程序代码
我们这里程序所做的是,从dex文件中读取出其header、string_ids、type_ids、proto_ids、filed_ids、method_ids和class_defs等信息。
前边header、string_ids、type_ids、proto_ids、filed_ids、method_ids应该都是没问题的,最后的class_defs也应该没问题只是层级太深头脑有些混乱没想好怎么组织打印。
import binascii class parse_dex: def __init__(self,dex_file): # 由于后续各区都需要从header中获取自己的数量和偏移,所以在构造函数中调用它 self.parse_dex_header() # 此函数用于解析dex文件头部 def parse_dex_header(self): # 定义header结构,key是头成员名称,value是key的字节长度 # xxx_ids_off表示xxx_ids_item列表的偏移量,xxx_ids_size表示xxx_ids_item个数 # xxx_off表示xxx的偏移量,xxx_size表示xxx的字节大小 self.dex_header_struct = { # 魔数 'magic': 8, # 文件校验码 ,使用alder32 算法校验文件除去 maigc ,checksum 外余下的所有文件区域 ,用于检查文件错误 。 'checksum': 4, # 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外余下的所有文件区域 ,用于唯一识别本文件 。 'signature': 20, # Dex 文件的大小 。 'file_size': 4, # header 区域的大小 ,单位 Byte ,一般固定为 0x70 常量 。 'header_size': 4, # 大小端标签 ,标准 .dex 文件格式为 小端 ,此项一般固定为 0x1234 5678 常量 。 'endian_tag': 4, # 链接数据的大小 'link_size': 4, # 链接数据的偏移值 'link_off': 4, # map item 的偏移地址 ,该 item 属于 data 区里的内容 ,值要大于等于 data_off 的大小 。 'map_off': 4, # dex中用到的所有的字符串内容的大小 'string_ids_size': 4, # dex中用到的所有的字符串内容的偏移值 'string_ids_off': 4, # dex中的类型数据结构的大小 'type_ids_size': 4, # dex中的类型数据结构的偏移值 'type_ids_off': 4, # dex中的元数据信息数据结构的大小 'proto_ids_size': 4, # dex中的元数据信息数据结构的偏移值 'proto_ids_off': 4, # dex中的字段信息数据结构的大小 'field_ids_size': 4, # dex中的字段信息数据结构的偏移值 'field_ids_off': 4, # dex中的方法信息数据结构的大小 'method_ids_size': 4, # dex中的方法信息数据结构的偏移值 'method_ids_off': 4, # dex中的类信息数据结构的大小 'class_defs_size': 4, # dex中的类信息数据结构的偏移值 'class_defs_off': 4, # dex中数据区域的结构信息的大小 'data_size': 4, # dex中数据区域的结构信息的偏移值 'data_off': 4 } # 此变量用于存放读取到的dex头部 self.dex_header = {} # 以二进制形式读取文件 self.fo = open(dex_file, "rb") for k, v in self.dex_header_struct.items(): # size,表示个数的字段,取其十进制 if "_size" in k: tmp = self.fo.read(v) tmp = int.from_bytes(tmp, byteorder='little', signed=False) self.dex_header[k] = tmp # off,表示文件偏移量的字段,为方便与以十六进制打开文件时相对比,取其十六进制 # 文件中是小端模式,为方便看我们顺序取反 elif '_off' in k: tmp = self.fo.read(v) tmp = tmp[::-1] self.dex_header[k] = binascii.b2a_hex(tmp).upper() # 其余字段保持原本顺序,直接十六进制转字符串 else: self.dex_header[k] = binascii.hexlify(self.fo.read(v)).upper() # int.from_bytes(binascii.a2b_hex(dex_header['string_ids_off']),byteorder='big',signed=False) # 此函数用于读取leb128格式数值 def read_uleb128(self): values = [] value = int.from_bytes(self.fo.read(1), byteorder='little', signed=False) values.append(value) while value >= 0x7f: value = int.from_bytes(self.fo.read(1), byteorder='little', signed=False) values.append(value) i = len(values) result = 0 values = values[::-1] for value in values: i = i-1 result |= (value&0x7f) << (i*7) return result # 此函数用于解析dex文件中的所有字符串; # 由于后边type等都要通过序号来获取字符串,所以独立出parse_string_by_index def parse_strings(self): # 由于已经_off顺序已取反,所以要指定为大端模式转成整数 string_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['string_ids_off']), byteorder='big', signed=False) string_ids_items = [] # 读取string_ids_size个字符串item for index in range(self.dex_header["string_ids_size"]): # string, string_ids_off, string_start_off = self.parse_string_by_index(i) # 以”字符串序号-起始地址-结束地址-字符串“格式打印 # print(f"{i}-{string_ids_off}-{string_start_off}-{string}") string_ids_item = self.parse_string_by_index(index) string_ids_items.append(string_ids_item) for index in range(len(string_ids_items)): print(f"{string_ids_items[index]}") # 此函数实现读取指定序号字符串 def parse_string_by_index(self, descriptor_idx): # string_ids_off指向string_ids_item结构 string_ids_item_struct = { # string_ids_item结构中只有string_data_off,其长度为4字节 'string_data_off': 4, } # string_data_off指向string_data_item结构 string_data_item = { # 字符串长度,ulb128格式 'size': '', # 字符串值 'data': '', } string_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['string_ids_off']),byteorder='big',signed=False) # 计算指定序号字符串string_ids_item的偏移量 current_string_ids_off = string_ids_off+descriptor_idx*string_ids_item_struct['string_data_off'] self.fo.seek(current_string_ids_off) # 读取指定序号字符串string_data_item的偏移量 string_start_off_tmp = self.fo.read(string_ids_item_struct['string_data_off']) string_start_off = int.from_bytes(string_start_off_tmp, byteorder='little', signed=False) self.fo.seek(string_start_off) string_data_item['size'] = self.read_uleb128() string_data_item['data'] = self.fo.read(string_data_item['size']).decode() return {'index':descriptor_idx,'string_start_off':string_start_off,'string_data_item':string_data_item} # 此函数实现解析dex文件中的所有类型 # 由于后边proto等都要通过序号来获取类型,所以独立出parse_type_by_index def parse_types(self): type_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['type_ids_off']), byteorder='big', signed=False) # 从header中读off,转十进制要这样转 # string_ids_off = int.from_bytes(binascii.a2b_hex(dex_header['string_ids_off']), byteorder='big', signed=False) # fo.seek(type_ids_off) type_ids_items = [] for index in range(self.dex_header["type_ids_size"]): type_ids_item = self.parse_type_by_index(index) type_ids_items.append(type_ids_item) for value in type_ids_items: print(f'{value}') # 此函数实现解析指定序号的类形 def parse_type_by_index(self, type_index): type_ids_item_struct = { 'descriptor_idx': 4 } type_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['type_ids_off']), byteorder='big', signed=False) current_type_ids_off = type_ids_off + type_index * type_ids_item_struct['descriptor_idx'] self.fo.seek(current_type_ids_off) # 从文件读转十进制直接这样转 current_type_descriptor_idx = int.from_bytes(self.fo.read(type_ids_item_struct['descriptor_idx']), byteorder='little', signed=False) type_ids_item = self.parse_string_by_index(current_type_descriptor_idx) return {'type_index': type_index,'type_ids_item':type_ids_item} # 此函数实现解析dex文件所有proto # 由于后边field等都要通过序号来获取类型,所以独立出get_proto_by_index def parse_protos(self): proto_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['proto_ids_off']), byteorder='big', signed=False) proto_id_items = [] for index in range(self.dex_header["proto_ids_size"]): proto_id_item = self.get_proto_by_index(index) proto_id_items.append(proto_id_item) for value in proto_id_items: print(f'{value}') # 些函数用于读取参数 def get_parameter(self,parameters_off,para_size): for j in range(para_size): self.fo.seek(parameters_off + j * 2) type_index = int.from_bytes(self.fo.read(2), byteorder='little', signed=False) string, string_off = self.parse_type_by_index(type_index) yield string # 此函数实现读取指定序号proto def get_proto_by_index(self,proto_idx): proto_id_item_struct = { 'shorty_idx':4, 'return_type_idx':4, 'parameters_off':4, } proto_id_item = {} proto_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['proto_ids_off']), byteorder='big', signed=False) current_type_ids_off = proto_ids_off + proto_idx * (proto_id_item_struct['shorty_idx']+proto_id_item_struct['return_type_idx']+proto_id_item_struct['parameters_off']) self.fo.seek(current_type_ids_off) shorty_idx = int.from_bytes(self.fo.read(proto_id_item_struct['shorty_idx']), byteorder='little', signed=False) return_type_idx = int.from_bytes(self.fo.read(proto_id_item_struct['return_type_idx']), byteorder='little', signed=False) parameters_off = int.from_bytes(self.fo.read(proto_id_item_struct['parameters_off']), byteorder='little', signed=False) proto_id_item['shorty_idx'] = self.parse_string_by_index(shorty_idx) proto_id_item['return_type_idx'] = self.parse_type_by_index(return_type_idx) self.fo.seek(parameters_off) para_size = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) proto_id_item['parameters_off'] = self.get_parameter(parameters_off,para_size) return {'proto_idx':proto_idx, 'proto_id_item':proto_id_item} # 此函数实现解析dex文件所有filed def parse_fields(self): field_id_item_struct = { 'class_idx':2, 'type_idx':2, 'name_idx':4, } field_id_item = {} field_id_items = [] field_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['field_ids_off']), byteorder='big', signed=False) for i in range(self.dex_header["field_ids_size"]): current_type_ids_off = field_ids_off + i * (field_id_item_struct['class_idx']+field_id_item_struct['type_idx']+field_id_item_struct['name_idx']) self.fo.seek(current_type_ids_off) class_index = int.from_bytes(self.fo.read(field_id_item_struct['class_idx']), byteorder='little', signed=False) type_idx = int.from_bytes(self.fo.read(field_id_item_struct['type_idx']), byteorder='little', signed=False) name_idx = int.from_bytes(self.fo.read(field_id_item_struct['name_idx']), byteorder='little', signed=False) field_id_item['class_idx'] = self.parse_type_by_index(class_index) #print(f"{i}-{class_index}-{string_off}-{string}") field_id_item['type_idx'] = self.parse_type_by_index(type_idx) # print(f"{i}-{type_idx}-{string_off}-{string}") field_id_item['name_idx'] = self.parse_string_by_index(type_idx) field_id_items.append(field_id_item) for value in field_id_items: print(f"{value}") # 此函数实现解析dex文件所有method def parse_methods(self): method_id_item_struct ={ 'class_idx':2, 'proto_idx':2, 'name_idx':4, } method_id_item = {} method_id_items = [] method_ids_off = int.from_bytes(binascii.a2b_hex(self.dex_header['method_ids_off']), byteorder='big', signed=False) for i in range(self.dex_header["field_ids_size"]): current_type_ids_off = method_ids_off + i * (method_id_item_struct['class_idx']+method_id_item_struct['proto_idx']+method_id_item_struct['name_idx']) self.fo.seek(current_type_ids_off) class_idx = int.from_bytes(self.fo.read(method_id_item_struct['class_idx']), byteorder='little', signed=False) proto_idx = int.from_bytes(self.fo.read(method_id_item_struct['proto_idx']), byteorder='little', signed=False) name_idx = int.from_bytes(self.fo.read(method_id_item_struct['name_idx']), byteorder='little', signed=False) method_id_item['class_idx'] = self.parse_type_by_index(class_idx) method_id_item['proto_idx'] = self.parse_string_by_index(name_idx) method_id_item['name_idx'] = self.get_proto_by_index(proto_idx) method_id_items.append(method_id_item) for value in method_id_items: print(f"{value}") # 以下函数都用于解析dex文件中的class def parse_code_item(self,code_off): self.fo.seek(code_off) # 本段代码使用到的寄存器数目。 registers_size = int.from_bytes(self.fo.read(2), byteorder='little', signed=False) # method传入参数的数目 。 ins_size = int.from_bytes(self.fo.read(2), byteorder='little', signed=False) # 本段代码调用其它method 时需要的参数个数 。 outs_size = int.from_bytes(self.fo.read(2), byteorder='little', signed=False) # try_item 结构的个数 。 tries_size = int.from_bytes(self.fo.read(2), byteorder='little', signed=False) # 偏移地址 ,指向本段代码的 debug 信息存放位置 ,是一个 debug_info_item 结构。 debug_info_off = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 指令列表的大小 ,以 16-bit 为单位 。 insns 是 instructions 的缩写 。 insns_size = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 指令列表 insns = [] for i in range(insns_size): insns.append(int.from_bytes(self.fo.read(2), byteorder='little', signed=False)) def parse_encoded_method(self): method_idx_diff = self.read_uleb128() access_flags = self.read_uleb128() code_off = self.read_uleb128() return [method_idx_diff,access_flags,code_off] def parse_encoded_field(self,): filed_idx_diff = self.read_uleb128() access_flags = self.read_uleb128() return [filed_idx_diff,access_flags] def parse_class_data_item(self,class_data_off): self.fo.seek(class_data_off) static_fields_size = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) instance_fields_size = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) direct_methods_size = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) virtual_methods_size = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) static_fields = [] instance_fields = [] direct_methods = [] virtual_methods = [] for i in range(1,static_fields_size): static_fields.append(self.parse_encoded_field(i)) for i in range(1,instance_fields_size): instance_fields.append(self.parse_encoded_field(i)) for i in range(1,direct_methods_size): direct_methods.append(self.parse_encoded_method(i)) for i in range(1,virtual_methods_size): virtual_methods.append(self.parse_encoded_method(i)) return [static_fields,instance_fields,direct_methods,virtual_methods] def parse_class(self): self.dex_class_def = { 'class_idx': 4, 'access_flags': 4, 'super_class_idx': 4, 'interfaces_off': 4, 'source_file_idx': 4, 'annotations_off': 4, 'class_date_off': 4, 'static_values_off': 4 } class_defs_off = int.from_bytes(binascii.a2b_hex(self.dex_header['class_defs_off']), byteorder='big', signed=False) for i in range(self.dex_header["class_defs_size"]): current_class_defs_off = class_defs_off + i * 32 self.fo.seek(current_class_defs_off) # 描述具体的class类型,值是type_ids的一个index。值必须是一个class类型,不能是数组类型或者基本类型。 class_idx = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 描述class的访问类型,诸如public,final,static等。在dex-format.html里“access_flagsDefinitions” 有具体的描述。 access_flags = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 描述supperclass的类型,值的形式跟class_idx一样 。 superclass_idx = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 值为偏移地址,指向class的interfaces, 被指向的数据结构为type_list。class若没有interfaces,值为 0。 interfaces_off = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 表示源代码文件的信息,值是string_ids的一个index。若此项信息缺失,此项值赋值为NO_INDEX=0xffff ffff source_file_idx = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 值是一个偏移地址,指向的内容是该class的注释,位置在data区,格式为annotations_direcotry_item。若没有此项内容,值为0 。 annotions_off = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 值是一个偏移地址,指向的内容是该class的使用到的数据,位置在data区,格式为class_data_item。 # 若没有此项内容,值为0。该结构里有很多内容,详细描述该class的field,method, method里的执行代码等信息。 class_data_off = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) # 值是一个偏移地址,指向data区里的一个列表(list),格式为encoded_array_item。若没有此项内容,值为 0。 static_value_off = int.from_bytes(self.fo.read(4), byteorder='little', signed=False) class_data_item_dict = self.parse_class_data_item(class_data_off) def __del__(self): self.fo.close() if __name__ == "__main__": # 措定要解析的dex文件的位置 dex_file = "classes.dex" parse_dex_obj = parse_dex(dex_file) parse_dex_obj.parse_strings() parse_dex_obj.parse_types() parse_dex_obj.parse_protos() parse_dex_obj.parse_fields() parse_dex_obj.parse_methods() parse_dex_obj.parse_class() for k, v in parse_dex_obj.dex_header.items(): print(f"dex_header--{k}: {v}")
参考:
https://blog.csdn.net/jiangwei0910410003/article/details/50668549