1. 程式人生 > >Python3解析dex文件

Python3解析dex文件

_proto_ target enc 格式 __del__ ins start rect html

一、說明

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

Python3解析dex文件