DEX文件格式分析
前段時間忙于破解移動和電信的 apk ,挺久沒有更新博客了,最近在寫個工具,主要功能是通過配置對 dex 文件中的類型、函數(shù)、屬性進(jìn)行隱藏,達(dá)到防止被靜態(tài)分析的效果。所以在寫工具前必須對 dex 的文件格式有個清晰的認(rèn)識,相對于 elf 文件格式 dex 文件格式會簡單一些。
原文鏈接: DEX文件格式分析
0x00 前言
分析 dex 文件格式最好的方式是找個介紹文檔,自己再寫一個簡單的 demo 然后用 010Editor 對照著分析。文檔可以參考官方文檔http://source.android.com/devices/tech/dalvik/dex-format.html,英文差的也可以找個中文的,比如說我。。。。。。
010Editor 這個工具比較好用,之前分析 elf 文件也是用的它。其實只要裝了模板,可以分析很多文件。雖然是收費軟件,有30天免費試用。但是如果你用的是 mac 😏 試用期到了, 刪一下這個文件 🙄 ~/.config/SweetScape/010 Editor.ini。
0x01 文件布局
dex 文件可以分為3個模塊,頭文件(header)、索引區(qū)(xxxx_ids)、數(shù)據(jù)區(qū)(data)。頭文件概況的描述了整個 dex 文件的分布,包括每一個索引區(qū)的大小跟偏移。索引區(qū)的ids 是 identifiers 的縮寫,表示每個數(shù)據(jù)的標(biāo)識,索引區(qū)主要是指向數(shù)據(jù)區(qū)的偏移。
010Editor 中除了數(shù)據(jù)區(qū)(data)沒有顯示出來,其他區(qū)段都有顯示,另外 link_data 在模板中被定為 map_list
0x02 header
header 描述了 dex 文件信息,和其他各個區(qū)的索引。010Editor(寫010Editor 有點麻煩下面直接寫010)中用結(jié)構(gòu)體 struct header_item 來描述 header。
其中用到了兩種數(shù)據(jù)類型char、uint。這里的 char 是 C/C++ 中的char 占 8-bit, java 中的 char 是占 16-bit 有點區(qū)別,但是我們可以他來表示 short/ushort 這個以后介紹最近寫的工具會介紹。官方文檔是用 ubyte來定義的,那還是按官方的來吧。結(jié)構(gòu)體描述:
- ubyte 8-bit unsinged int
- uint 32-bit unsigned int, little-endian
- struct header_item
- {
- ubyte[8] magic;
- unit checksum;
- ubyte[20] signature;
- uint file_size;
- uint header_size;
- unit endian_tag;
- uint link_size;
- uint link_off;
- uint map_off;
- uint string_ids_size;
- uint string_ids_off;
- uint type_ids_size;
- uint type_ids_off;
- uint proto_ids_size;
- uint proto_ids_off;
- uint method_ids_size;
- uint method_ids_off;
- uint class_defs_size;
- uint class_defs_off;
- uint data_size;
- uint data_off;
- }
除了 magic、checksum、signature、file_size、endian_tag、map_off 其他元素都是成對出現(xiàn)的。_off 表示元素的偏移量,_size 表示元素的個數(shù)。其余的6個描述主要是 dex 文件的信息。
- magic: 這個是固定值,用于識別 dex 文件。轉(zhuǎn)化為字符串為:
- {0x64, 0x65, 0x78, 0x0A, 0x30, 0x33, 0x35, 0x00} = "dex\n035\0"
中間是一個換行,后面035是版本號。
- checksum: 文件校驗碼,使用 alder32 算法校驗文件除去 maigc、checksum 外余下的所有文件區(qū)域,用于檢 查文件錯誤。
- signature: 使用 SHA-1 算法 hash 除去 magic、checksum 和 signature 外余下的所有文件區(qū)域, 用于唯一識別本文件 。
- file_size: dex 文件大小
- header_size: header 區(qū)域的大小,目前是固定為 0x70
- endian_tag: 大小端標(biāo)簽,dex 文件格式為小端,固定值為 0x12345678 常量
- map_off: map_item 的偏移地址,該 item 屬于 data 區(qū)里的內(nèi)容,值要大于等于 data_off 的大小,處于 dex 文件的末端。
0x03 string_ids
string_ids 區(qū)段描述了 dex 文件中所有的字符串。格式很簡單只有一個偏移量,偏移量指向了 string_data 區(qū)段的一個字符串:
上述描述里提到了 LEB128 ( little endian base 128 ) 格式,是基于 1 個 byte 的一種不定長度的編碼方式。若第一個 byte 的最高位為1,則表示還需要下一個 byte 來描述,直至最后一個 byte 的最高位為 0。每個 byte 的其余 bit 用來表示數(shù)據(jù),如下表所示。實際中 LEB128 最大只能達(dá)到 32-bit 可以閱讀 dalvik 中的Leb128.h源碼看出來。
數(shù)據(jù)結(jié)構(gòu)為:
- ubyte 8-bit unsinged int
- uint 32-bit unsigned int, little-endian
- uleb128 unsigned LEB128, valriable length
- struct string_ids_item
- {
- uint string_data_off;
- }
- struct string_data_item
- {
- uleb128 utf16_size;
- ubyte data;
- }
其中 data 保存的就是字符串的值。string_ids 是比較關(guān)鍵的,后續(xù)的區(qū)段很多都是直接指向 string_ids 的 index。在寫工具進(jìn)行比較的時候也需要提取到 string_id
0x04 type_ids
type_ids 區(qū)索引了 dex 文件里的所有數(shù)據(jù)類型,包括 class 類型,數(shù)組類型(array types)和基本類型(primitive types)。區(qū)段里的元素格式為 type_ids_item,結(jié)構(gòu)描述如下 :
- uint 32-bit unsigned int, little-endian
- struct type_ids_item
- {
- uint descriptor_idx; //-->string_ids
- }
type_ids_item 里面 descriptor_idx 的值的意思,是 string_ids 里的 index 序號,是用來描述此 type 的字符串。
0x05 proto_ids
proto 的意思是 method prototype 代表 java 語言里的一個 method 的原型 。proto_ids 里的元素為 proto_id_item,結(jié)構(gòu)如下:
- uint 32-bit unsigned int, little-endian
- struct proto_id_item
- {
- uint shorty_idx; //-->string_ids
- uint return_type_idx; //-->type_ids
- uint parameters_off;
- }
- shorty_idx: 跟 type_ids 一樣,它的值是一個 string_ids 的 index 號 ,最終是一個簡短的字符串描述,用來說明該 method 原型。
- return_type_idx: 它的值是一個 type_ids 的 index 號 ,表示該 method 原型的返回值類型。
- parameters_off: 指向 method 原型的參數(shù)列表 type_list,若 method 沒有參數(shù),值為0。參數(shù)列表的格式是 type_list,下面會有描述。
0x06 field_ids
filed_ids 區(qū)里面有 dex 文件引用的所有的 field。區(qū)段的元素格式是 field_id_item,結(jié)構(gòu)如下:
- ushort 16-bit unsigned int, little-endian
- uint 32-bit unsigned int, little-endian
- struct filed_id_item
- {
- ushort class_idx; //-->type_ids
- ushort type_idx; //-->type_ids
- uint name_idx; //-->string_ids
- }
- class_idx: 表示 field 所屬的 class 類型,class_idx 的值是 type_ids 的一個 index,并且必須指向一個 class 類型。
- type_idx: 表示本 field 的類型,它的值也是 type_ids 的一個 index 。
- name_idx: 表示本 field 的名稱,它的值是 string_ids 的一個 index 。
0x07 method_ids
method_ids 是索引區(qū)的最后一個條目,描述了 dex 文件里的所有的 method。method_ids 的元素格式是 method_id_item,結(jié)構(gòu)跟 fields_ids 很相似:
- ushort 16-bit unsigned int, little-endian
- uint 32-bit unsigned int, little-endian
- struct filed_id_item
- {
- ushort class_idx; //-->type_ids
- ushort proto_idx; //-->proto_ids
- uint name_idx; //-->string_ids
- }
- class_idx: 表示 method 所屬的 class 類型,class_idx 的值是 type_ids 的一個 index,并且必須指向一個 class 類型。<font color=red>ushort類型也是為什么我們說一個 dex 只能有 65535 個方法的原因,多了必須分包</font>。
- proto_idx: 表示 method 的類型,它的值也是 type_ids 的一個 index。
- name_idx: 表示 method 的名稱,它的值是 string_ids 的一個 index。
0x08 class_defs
class_def 區(qū)段主要是對 class 的定義,它的結(jié)構(gòu)很復(fù)雜,看的我有點暈,一層套一層。先看一張 010 的結(jié)構(gòu)圖:
看著都暈,別說解析的時候了。
class_def_item
class_def_item 結(jié)構(gòu)描述如下:
- uint 32-bit unsigned int, little-endian
- struct class_def_item
- {
- uint class_idx; //-->type_ids
- uint access_flags;
- uint superclass_idx; //-->type_ids
- uint interface_off; //-->type_list
- uint source_file_idx; //-->string_ids
- uint annotations_off; //-->annotation_directory_item
- uint class_data_off; //-->class_data_item
- uint static_value_off; //-->encoded_array_item
- }
- class_idx: 描述具體的 class 類型,值是 type_ids 的一個 index 。值必須是一個 class 類型,不能是數(shù)組類型或者基本類型。
- access_flags: 描述 class 的訪問類型,諸如 public , final , static 等。在 dex-format.html 里 "access_flags Definitions" 有具體的描述 。
- superclass_idx: 描述 supperclass 的類型,值的形式跟 class_idx 一樣 。
- interfaces_off: 值為偏移地址,指向 class 的 interfaces,被指向的數(shù)據(jù)結(jié)構(gòu)為 type_list 。class 若沒有 interfaces 值為 0。
- source_file_idx: 表示源代碼文件的信息,值是 string_ids 的一個 index。若此項信息缺失,此項值賦值為 NO_INDEX=0xffff ffff。
- annotions_off: 值是一個偏移地址,指向的內(nèi)容是該 class 的注釋,位置在 data 區(qū),格式為 annotations_direcotry_item。若沒有此項內(nèi)容,值為 0 。
- class_data_off: 值是一個偏移地址,指向的內(nèi)容是該 class 的使用到的數(shù)據(jù),位置在 data 區(qū),格式為 class_data_item。若沒有此項內(nèi)容值為 0。該結(jié)構(gòu)里有很多內(nèi)容,詳細(xì)描述該 class 的 field、method, method 里的執(zhí)行代碼等信息,后面會介紹class_data_item。
- static_value_off: 值是一個偏移地址 ,指向 data 區(qū)里的一個列表 (list),格式為 encoded_array_item。若沒有此項內(nèi)容值為 0。
type_list
type_list 在 data 區(qū)段,class_def_item->interface_off 就是指的這里的數(shù)據(jù)。數(shù)據(jù)結(jié)構(gòu)如下:
- uint 32-bit unsigned int, little-endian
- struct type_list
- {
- uint size;
- type_item list [size]
- }
- struct type_item
- {
- ushort type_idx //-->type_ids
- }
- size: 表示類型個數(shù)
- type_idx: 對應(yīng)一個 type_ids 的 index
annotations_directory_item
class_def_item->annotations_off 指向的數(shù)據(jù)區(qū)段,定義了 annotation 相關(guān)的數(shù)據(jù)描述,數(shù)據(jù)結(jié)構(gòu)如下:
- uint 32-bit unsigned int, little-endian
- struct annotation_directory_item
- {
- uint class_annotations_off; //-->annotation_set_item
- uint fields_size;
- uint annotated_methods_size;
- uint annotated_parameters_size;
- field_annotation field_annotations[fields_size];
- method_annotation method_annotations[annotated_methods_size];
- parameter_annotation parameter_annotations[annotated_parameters_size];
- }
- struct field_annotation
- {
- uint field_idx;
- uint annotations_off; //-->annotation_set_item
- }
- struct method_annotation
- {
- uint method_idx;
- uint annotations_off; //-->annotation_set_item
- }
- struct parameter_annotation
- {
- uint method_idx;
- uint annotations_off; //-->annotation_set_ref_list
- }
- class_annotations_off: 這個偏移指向了 annotation_set_item 具體的可以看 dex-format.html 上的介紹。
- fields_size: 表示屬性的個數(shù)
- annotated_methods_size: 表示方法的個數(shù)
- annotated_parameters_size: 表示參數(shù)的個數(shù)
class_data_item
class_data_off 指向 data 區(qū)里的 class_data_item 結(jié)構(gòu),class_data_item 里存放著本 class 使用到的各種數(shù)據(jù),下面是 class_data_item 的結(jié)構(gòu) :
- uleb128 unsigned little-endian base 128
- struct class_data_item
- {
- uleb128 static_fields_size;
- uleb128 instance_fields_size;
- uleb128 direct_methods_size;
- uleb128 virtual_methods_size;
- encoded_field static_fields[static_fields_size];
- encoded_field instance_fields[instance_fields_size];
- encoded_method direct_methods[direct_methods_size];
- encoded_method virtual_methods[virtual_methods_size];
- }
- struct encoded_field
- {
- uleb128 filed_idx_diff;
- uleb128 access_flags;
- }
- struct encoded_method
- {
- uleb128 method_idx_diff;
- uleb128 access_flags;
- uleb128 code_off;
- }
class_data_item
- static_fields_size: 靜態(tài)成員變量的個數(shù)
- instance_fields_size: 實例成員變量個數(shù)
- direct_methods_size: 直接函數(shù)個數(shù)
- virtual_methods_size: 虛函數(shù)個數(shù)
下面幾個就是對于的描述
encoded_field
- method_idx_diff: 前綴 methd_idx 表示它的值是 method_ids 的一個 index ,后綴 _diff 表示它是于另 外一個 method_idx 的一個差值 ,就是相對于 encodeed_method [] 數(shù)組里上一個元素的 method_idx 的差值 。 其實 encoded_filed - > field_idx_diff 表示的也是相同的意思 ,只是編譯出來的 Hello.dex 文件里沒有使用到 class filed 所以沒有仔細(xì)講 ,詳細(xì)的參考 dex_format.html 的官網(wǎng)文檔。
- access_flags: 訪問權(quán)限,比如 public、private、static、final 等。
- code_off: 一個指向 data 區(qū)的偏移地址,目標(biāo)是本 method 的代碼實現(xiàn)。被指向的結(jié)構(gòu)是code_item,有近 10 項元素。
code_item
code_item 結(jié)構(gòu)里描述著某個 method 的具體實現(xiàn),它的結(jié)構(gòu)描述如下:
- struct code_item
- {
- ushort registers_size;
- ushort ins_size;
- ushort outs_size;
- ushort tries_size;
- uint debug_info_off;
- uint insns_size;
- ushort insns [insns_size];
- ushort paddding; // optional
- try_item tries [tyies_size]; // optional
- encoded_catch_handler_list handlers; // optional
- }
末尾的 3 項標(biāo)志為 optional , 表示可能有 ,也可能沒有 ,根據(jù)具體的代碼來。
- registers_size: 本段代碼使用到的寄存器數(shù)目。
- ins_size: method 傳入?yún)?shù)的數(shù)目 。
- outs_size: 本段代碼調(diào)用其它 method 時需要的參數(shù)個數(shù) 。
- tries_size: try_item 結(jié)構(gòu)的個數(shù) 。
- debug_off: 偏移地址,指向本段代碼的 debug 信息存放位置,是一個 debug_info_item 結(jié)構(gòu)。
- insns_size: 指令列表的大小,以 16-bit 為單位。 insns 是 instructions 的縮寫 。
- padding: 值為 0,用于對齊字節(jié) 。
- tries 和 handlers: 用于處理 java 中的 exception,常見的語法有 try catch。
encoded_array_item
class_def_item->static_value_off 偏移指向該區(qū)段數(shù)據(jù)。
- uleb128 unsigned LEB128, valriable length
- struct encoded_array_item
- {
- encoded_array value;
- }
- struct encoded_array
- {
- uleb128 size;
- encoded_value values[size];
- }
- size : 表示encoded_value 個數(shù)
- encoded_value: 這個我也沒分析出來怎么搞得🙄
0x09 map_list
map_list 中大部分 item 跟 header 中的相應(yīng)描述相同,都是介紹了各個區(qū)的偏移和大小,但是 map_list 中描述的更加全面,包括了 HEADER_ITEM 、TYPE_LIST、STRING_DATA_ITEM、DEBUG_INFO_ITEM 等信息。
010 中map_list 表示為:
數(shù)據(jù)結(jié)構(gòu)為:
- ushort 16-bit unsigned int, little-endian
- uint 32-bit unsigned int, little-endian
- struct map_list
- {
- uint size;
- map_item list [size];
- }
- struct map_item
- {
- ushort type;
- ushort unuse;
- uint size;
- uint offset;
- }
map_list 里先用一個 uint 描述后面有 size 個 map_item,后續(xù)就是對應(yīng)的 size 個 map_item 描述。 map_item 結(jié)構(gòu)有 4 個元素: type 表示該 map_item 的類型,Dalvik Executable Format 里 Type Code 的定義; size 表示再細(xì)分此 item,該類型的個數(shù);offset 是第一個元素的針對文件初始位置的偏移量; unuse 是用對齊字節(jié)的,無實際用處。