前段時間忙於破解移動和電信的 apk ,挺久沒有更新博客了,最近在寫個工具,主要功能是經過配置對 dex 文件中的類型、函數、屬性進行隱藏,達到防止被靜態分析的效果。因此在寫工具前必須對 dex 的文件格式有個清晰的認識,相對於 elf 文件格式 dex 文件格式會簡單一些。html
原文連接: DEX文件格式分析java
分析 dex 文件格式最好的方式是找個介紹文檔,本身再寫一個簡單的 demo 而後用 010Editor 對照着分析。文檔能夠參考官方文檔http://source.android.com/devices/tech/dalvik/dex-format.html,英文差的也能夠找個中文的,好比說我。。。。。。android
010Editor 這個工具比較好用,以前分析 elf 文件也是用的它。其實只要裝了模板,能夠分析不少文件。雖然是收費軟件,有30天免費試用。可是若是你用的是 mac ? 試用期到了, 刪一下這個文件 ? ~/.config/SweetScape/010 Editor.ini
。c++
dex 文件能夠分爲3個模塊,頭文件(header)、索引區(xxxx_ids)、數據區(data)。頭文件概況的描述了整個 dex 文件的分佈,包括每個索引區的大小跟偏移。索引區的ids 是 identifiers
的縮寫,表示每一個數據的標識,索引區主要是指向數據區的偏移。算法
010Editor 中除了數據區(data)沒有顯示出來,其餘區段都有顯示,另外 link_data 在模板中被定爲 map_list數組
header 描述了 dex 文件信息,和其餘各個區的索引。010Editor(寫010Editor 有點麻煩下面直接寫010)中用結構體 struct header_item
來描述 header。數據結構
其中用到了兩種數據類型char、uint。這裏的 char 是 C/C++ 中的char 佔 8-bit, java 中的 char 是佔 16-bit 有點區別,可是咱們能夠他來表示 short/ushort 這個之後介紹最近寫的工具會介紹。官方文檔是用 ubyte來定義的,那仍是按官方的來吧。
結構體描述:ide
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 其餘元素都是成對出現的。_off 表示元素的偏移量,_size 表示元素的個數。其他的6個描述主要是 dex 文件的信息。函數
magic: 這個是固定值,用於識別 dex 文件。轉化爲字符串爲:工具
{0x64, 0x65, 0x78, 0x0A, 0x30, 0x33, 0x35, 0x00} = "dex\n035\0"
中間是一個換行,後面035是版本號。
checksum: 文件校驗碼,使用 alder32 算法校驗文件除去 maigc、checksum 外餘下的全部文件區域,用於檢 查文件錯誤。
signature: 使用 SHA-1 算法 hash 除去 magic、checksum 和 signature 外餘下的全部文件區域, 用於惟一識別本文件 。
file_size: dex 文件大小
header_size: header 區域的大小,目前是固定爲 0x70
endian_tag: 大小端標籤,dex 文件格式爲小端,固定值爲 0x12345678 常量
map_off: map_item 的偏移地址,該 item 屬於 data 區裏的內容,值要大於等於 data_off 的大小,處於 dex 文件的末端。
string_ids 區段描述了 dex 文件中全部的字符串。格式很簡單隻有一個偏移量,偏移量指向了 string_data 區段的一個字符串:
上述描述裏提到了 LEB128 ( little endian base 128 ) 格式,是基於 1 個 byte 的一種不定長度的編碼方式。若第一個 byte 的最高位爲1,則表示還須要下一個 byte 來描述,直至最後一個 byte 的最高位爲 0。每一個 byte 的其他 bit 用來表示數據,以下表所示。實際中 LEB128 最大隻能達到 32-bit 能夠閱讀 dalvik 中的Leb128.h源碼看出來。
數據結構爲:
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 是比較關鍵的,後續的區段不少都是直接指向 string_ids 的 index。在寫工具進行比較的時候也須要提取到 string_ids。
type_ids 區索引了 dex 文件裏的全部數據類型,包括 class 類型,數組類型(array types)和基本類型
(primitive types)。區段裏的元素格式爲 type_ids_item,結構描述以下 :
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 的字符串。
proto 的意思是 method prototype 表明 java 語言裏的一個 method 的原型 。proto_ids 裏的元素爲 proto_id_item,結構以下:
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 原型的參數列表 type_list,若 method 沒有參數,值爲0。參數列表的格式是 type_list,下面會有描述。
filed_ids 區裏面有 dex 文件引用的全部的 field。區段的元素格式是 field_id_item,結構以下:
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 。
method_ids 是索引區的最後一個條目,描述了 dex 文件裏的全部的 method。method_ids 的元素格式是 method_id_item,結構跟 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。
class_def 區段主要是對 class 的定義,它的結構很複雜,看的我有點暈,一層套一層。先看一張 010 的結構圖:
看着都暈,別說解析的時候了。
class_def_item 結構描述以下:
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 類型,不能是數組類型或者基本類型。
access_flags: 描述 class 的訪問類型,諸如 public , final , static 等。在 dex-format.html 裏 "access_flags Definitions" 有具體的描述 。
superclass_idx: 描述 supperclass 的類型,值的形式跟 class_idx 同樣 。
interfaces_off: 值爲偏移地址,指向 class 的 interfaces,被指向的數據結構爲 type_list
。class 若沒有 interfaces 值爲 0。
source_file_idx: 表示源代碼文件的信息,值是 string_ids 的一個 index。若此項信息缺失,此項值賦值爲 NO_INDEX=0xffff ffff。
annotions_off: 值是一個偏移地址,指向的內容是該 class 的註釋,位置在 data 區,格式爲 annotations_direcotry_item
。若沒有此項內容,值爲 0 。
class_data_off: 值是一個偏移地址,指向的內容是該 class 的使用到的數據,位置在 data 區,格式爲 class_data_item
。若沒有此項內容值爲 0。該結構裏有不少內容,詳細描述該 class 的 field、method, method 裏的執行代碼等信息,後面會介紹 class_data_item
。
static_value_off: 值是一個偏移地址 ,指向 data 區裏的一個列表 (list),格式爲 encoded_array_item
。若沒有此項內容值爲 0。
type_list 在 data 區段,class_def_item->interface_off 就是指的這裏的數據。數據結構以下:
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: 表示類型個數
type_idx: 對應一個 type_ids 的 index
class_def_item->annotations_off 指向的數據區段,定義了 annotation 相關的數據描述,數據結構以下:
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: 表示屬性的個數
annotated_methods_size: 表示方法的個數
annotated_parameters_size: 表示參數的個數
class_data_off 指向 data 區裏的 class_data_item 結構,class_data_item 裏存放着本 class 使用到的各類數據,下面是 class_data_item 的結構 :
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: 靜態成員變量的個數
instance_fields_size: 實例成員變量個數
direct_methods_size: 直接函數個數
virtual_methods_size: 虛函數個數
下面幾個就是對於的描述
encoded_field
method_idx_diff: 前綴 methd_idx 表示它的值是 method_ids 的一個 index ,後綴 _diff 表示它是於另 外一個 method_idx 的一個差值 ,就是相對於 encodeed_method [] 數組裏上一個元素的 method_idx 的差值 。 其實 encoded_filed - > field_idx_diff 表示的也是相同的意思 ,只是編譯出來的 Hello.dex 文件裏沒有使用到 class filed 因此沒有仔細講 ,詳細的參考 dex_format.html 的官網文檔。
access_flags: 訪問權限,好比 public、private、static、final 等。
code_off: 一個指向 data 區的偏移地址,目標是本 method 的代碼實現。被指向的結構是code_item,有近 10 項元素。
code_item 結構裏描述着某個 method 的具體實現,它的結構描述以下:
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 項標誌爲 optional , 表示可能有 ,也可能沒有 ,根據具體的代碼來。
registers_size: 本段代碼使用到的寄存器數目。
ins_size: method 傳入參數的數目 。
outs_size: 本段代碼調用其它 method 時須要的參數個數 。
tries_size: try_item 結構的個數 。
debug_off: 偏移地址,指向本段代碼的 debug 信息存放位置,是一個 debug_info_item
結構。
insns_size: 指令列表的大小,以 16-bit 爲單位。 insns 是 instructions 的縮寫 。
padding: 值爲 0,用於對齊字節 。
tries 和 handlers: 用於處理 java 中的 exception,常見的語法有 try catch。
class_def_item->static_value_off 偏移指向該區段數據。
uleb128 unsigned LEB128, valriable length struct encoded_array_item { encoded_array value; } struct encoded_array { uleb128 size; encoded_value values[size]; }
size : 表示encoded_value 個數
encoded_value: 這個我也沒分析出來怎麼搞得?
map_list 中大部分 item 跟 header 中的相應描述相同,都是介紹了各個區的偏移和大小,可是 map_list 中描述的更加全面,包括了 HEADER_ITEM 、TYPE_LIST、STRING_DATA_ITEM、DEBUG_INFO_ITEM 等信息。
010 中map_list 表示爲:
數據結構爲:
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,後續就是對應的 size 個 map_item 描述。 map_item 結構有 4 個元素: type 表示該 map_item 的類型,Dalvik Executable Format 裏 Type Code 的定義; size 表示再細分此 item,該類型的個數;offset 是第一個元素的針對文件初始位置的偏移量; unuse 是用對齊字節的,無實際用處。