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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM