1. 程式人生 > >Python3解析dex檔案

Python3解析dex檔案

一、說明

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