天天看點

Dex檔案頭及解析機制分析

Dex檔案頭主要包括校驗和以及其他結構的偏移位址和長度資訊。

字段名稱 偏移值 長度 描述
magic 0x0 8 'Magic'值,即魔數字段,格式如”dex/n035/0”,其中的035表示結構的版本。
checksum 0x8 4 校驗碼。
signature 0xC 20 SHA-1簽名。
file_size 0x20 4 Dex檔案的總長度。
header_size 0x24 4 檔案頭長度,009版本=0x5C,035版本=0x70。
endian_tag 0x28 4 辨別位元組順序的常量,根據這個常量可以判斷檔案是否交換了位元組順序,預設情況下=0x78563412。
link_size 0x2C 4 連接配接段的大小,如果為0就表示是靜态連接配接。
link_off 0x30 4 連接配接段的開始位置,從本檔案頭開始算起。如果連接配接段的大小為0,這裡也是0。
map_off 0x34 4 map資料基位址。
string_ids_size 0x38 4 字元串清單的字元串個數。
string_ids_off 0x3C 4 字元串清單表基位址。
type_ids_size 0x40 4 類型清單裡類型個數。
type_ids_off 0x44 4 類型清單基位址。
proto_ids_size 0x48 4 原型清單裡原型個數。
proto_ids_off 0x4C 4 原型清單基位址。
field_ids_size 0x50 4 字段清單裡字段個數。
field_ids_off 0x54 4 字段清單基位址。
method_ids_size 0x58 4 方法清單裡方法個數。
method_ids_off 0x5C 4 方法清單基位址。
class_defs_size 0x60 4 類定義類表中類的個數。
class_defs_off 0x64 4 類定義清單基位址。
data_size 0x68 4 資料段的大小,必須以4位元組對齊。
data_off 0x6C 4 資料段基位址

魔數字段

     魔數字段,主要就是Dex檔案的辨別符,它占用4個位元組,在目前的源碼裡是 “dex\n”,它的作用主要是用來辨別dex檔案的,比如有一個檔案也以dex為字尾名,僅此并不會被認為是Davlik虛拟機運作的檔案,還要判斷這 四個位元組。另外Davlik虛拟機也有優化的Dex,也是通過個字段來區分的,當它是優化的Dex檔案時,它的值就變成”dey\n”了。根據這四個字 節,就可以識别不同類型的Dex檔案了。

      跟在“dex\n”後面的是版本字段,主要用來辨別Dex檔案的版本。目前支援的版本号為“035\0”,不管是否優化的版本,都是使用這個版本号。

檢驗碼字段

     主要用來檢查從這個字段開始到檔案結尾,這段資料是否完整,有沒有人修改過,或者傳送過程中是否有出錯等等。通常用來檢查資料是否完整的算法,有 CRC32、有SHA128等,但這裡采用并不是這兩類,而采用一個比較特别的算法,叫做adler32,這是在開源zlib裡常用的算法,用來檢查檔案 是否完整性。該算法由MarkAdler發明,其可靠程度跟CRC32差不多,不過還是弱一點點,但它有一個很好的優點,就是使用軟體來計算檢驗碼時比較 CRC32要快很多。可見Android系統,就算法上就已經為移動裝置進行優化了。

     Java中可使用java.util.zip.Adler32類做校驗操作

SHA-1簽名字段

     dex檔案頭裡,前面已經有了面有一個4位元組的檢驗字段碼了,為什麼還會有SHA-1簽名字段呢?不是重複了嗎?可是仔細考慮一下,這樣設計自有道理。因 為dex檔案一般都不是很小,簡單的應用程式都有幾十K,這麼多資料使用一個4位元組的檢驗碼,重複的機率還是有的,也就是說當檔案裡的資料修改了,還是很 有可能檢驗不出來的。這時檢驗碼就失去了作用,需要使用更加強大的檢驗碼,這就是SHA-1。SHA-1校驗碼有20個位元組,比前面的檢驗碼多了16個字 節,幾乎不會不同的檔案計算出來的檢驗是一樣的。設計兩個檢驗碼的目的,就是先使用第一個檢驗碼進行快速檢查,這樣可以先把簡單出錯的dex檔案丢掉了, 接着再使用第二個複雜的檢驗碼進行複雜計算,驗證檔案是否完整,這樣確定執行的檔案完整和安全。

      SHA(Secure Hash Algorithm, 安全雜湊演算法)是美國國家安全局設計,美國國家标準與技術研究院釋出的一系列密碼散列函數。SHA-1看起來和MD5算法很像,也許是Ron Rivest在SHA-1的設計中起了一定的作用。SHA-1的内部比MD5更強,其摘要比MD5的16位元組長4個位元組,這個算法成功經受了密碼分析專家 的攻擊,也因而受到密碼學界的廣泛推崇。這個算法在目前網絡上的簽名,BT軟體裡就有大量使用,比如在BT裡要計算是否同一個種子時,就是利用檔案的簽名 來判斷的。同一份8G的電影從幾千BT使用者那裡下載下傳,也不會出現錯誤的資料,導緻電影不播放。

map_off字段

這個字段主要儲存map開始位置,就是從檔案頭開始到map資料的長度,通過這個索引就可以找到map資料。map的資料結構如下:

名稱 大小 說明
size 4位元組 map裡項的個數
list 變長 每一項定義為12位元組,項的個數由上面項大小決定。

map資料排列結構定義如下:

<span class="coMULTI" style="line-height:20px">/*
*Direct-mapped "map_list".
*/</span>
 
<span class="kw4" style="line-height:20px">typedef</span> <span class="kw4" style="line-height:20px">struct</span> DexMapList <span class="br0" style="line-height:20px">{</span>
    u4 size<span class="sy0" style="line-height:20px">;</span> <span class="coMULTI" style="line-height:20px">/* #of entries inlist */</span>
    DexMapItem list<span class="br0" style="line-height:20px">[</span><span class="nu0" style="line-height:20px">1</span><span class="br0" style="line-height:20px">]</span><span class="sy0" style="line-height:20px">;</span> <span class="coMULTI" style="line-height:20px">/* entries */</span>
<span class="br0" style="line-height:20px">}</span>DexMapList<span class="sy0" style="line-height:20px">;</span>      

每一個map項的結構定義如下:

<span class="coMULTI" style="line-height:20px">/*
*Direct-mapped "map_item".
*/</span>
 
<span class="kw4" style="line-height:20px">typedef</span> <span class="kw4" style="line-height:20px">struct</span> DexMapItem <span class="br0" style="line-height:20px">{</span>
    u2 type<span class="sy0" style="line-height:20px">;</span> <span class="coMULTI" style="line-height:20px">/* type code (seekDexType* above) */</span>
    u2 unused<span class="sy0" style="line-height:20px">;</span>
    u4 size<span class="sy0" style="line-height:20px">;</span> <span class="coMULTI" style="line-height:20px">/* count of items ofthe indicated type */</span>
    u4 offset<span class="sy0" style="line-height:20px">;</span> <span class="coMULTI" style="line-height:20px">/* file offset tothe start of data */</span>
<span class="br0" style="line-height:20px">}</span>DexMapItem<span class="sy0" style="line-height:20px">;</span>      

DexMapItem結構定義每一項的資料意義:類型、類型個數、類型開始位置。

其中的類型定義如下:

<span class="coMULTI" style="line-height:20px">/*map item type codes */</span>
<span class="kw2" style="line-height:20px">enum</span><span class="br0" style="line-height:20px">{</span>
    kDexTypeHeaderItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0000</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeStringIdItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0001</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeTypeIdItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0002</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeProtoIdItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0003</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeFieldIdItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0004</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeMethodIdItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0005</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeClassDefItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x0006</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeMapList <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x1000</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeTypeList <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x1001</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeAnnotationSetRefList <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x1002</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeAnnotationSetItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x1003</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeClassDataItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2000</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeCodeItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2001</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeStringDataItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2002</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeDebugInfoItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2003</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeAnnotationItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2004</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeEncodedArrayItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2005</span><span class="sy0" style="line-height:20px">,</span>
    kDexTypeAnnotationsDirectoryItem <span class="sy0" style="line-height:20px">=</span> <span class="nu12" style="line-height:20px">0x2006</span><span class="sy0" style="line-height:20px">,</span>
<span class="br0" style="line-height:20px">}</span><span class="sy0" style="line-height:20px">;</span>      

從上面的類型可知,它包括了在dex檔案裡可能出現的所有類型。可以看出這裡的類型與檔案頭裡定義的類型有很多是一樣的,這裡的類型其實就是檔案頭裡定義 的類型。其實這個map的資料,就是頭裡類型的重複,完全是為了檢驗作用而存在的。當Android系統加載dex檔案時,如果比較檔案頭類型個數與 map裡類型不一緻時,就會停止使用這個dex檔案

string_ids_size/off字段

這兩個字段主要用來辨別字元串資源。源程式編譯後,程式裡用到的字元串都儲存在這個資料段裡,以便解釋執行這個dex檔案使用。其中包括調用庫函數裡的類名稱描述,用于輸出顯示的字元串等。

string_ids_size辨別了有多少個字元串,string_ids_off辨別字元串資料區的開始位置。字元串的存儲結構如下:

<span class="coMULTI" style="line-height:20px">/*
 * Direct-mapped "string_id_item".
 */</span>
<span class="kw4" style="line-height:20px">typedef</span> <span class="kw4" style="line-height:20px">struct</span> DexStringId <span class="br0" style="line-height:20px">{</span>
    u4  stringDataOff<span class="sy0" style="line-height:20px">;</span>      <span class="coMULTI" style="line-height:20px">/* file offset to string_data_item */</span>
<span class="br0" style="line-height:20px">}</span> DexStringId<span class="sy0" style="line-height:20px">;</span>      

可以看出這個資料區儲存的隻是字元串表的位址索引。如果要找到字元串的實際資料,還需要通過個位址索引找到檔案的相應開始位置,然後才能得到字元串資料。 每一個字元串項的索引占用4個位元組,是以這個資料區的大小就為4*string_ids_size。實際資料區中的字元串采用UTF8格式儲存。

例如,如果dex檔案使用16進制顯示出來内容如下:

063c 696e 6974 3e00

其實際資料則是”<init>\0”

另外這段資料中不僅包括字元串的字元串的内容和結束标志,在最開頭的位置還标明了字元串的長度。上例中第一個位元組06就是表示這個字元串有6個字元。

關于字元串的長度有兩點需要注意的地方:

1、關于長度的編碼格式

dex檔案裡采用了變長方式表示字元串長度。一個字元串的長度可能是一個位元組(小于256)或者4個位元組(1G大小以上)。字元串的長度大多數都是小于 256個位元組,是以需要使用一種編碼,既可以表示一個位元組的長度,也可以表示4個位元組的長度,并且1個位元組的長度占絕大多數。能滿足這種表示的編碼方式有 很多,但dex檔案裡采用的是uleb128方式。leb128編碼是一種變長編碼,每個位元組采用7位來表達原來的資料,最高位用來表示是否有後繼位元組。

它的編碼算法如下:

<span class="coMULTI" style="line-height:20px">/*
 * Writes a 32-bit value in unsigned ULEB128 format.
 * Returns the updated pointer.
 */</span>
DEX_INLINE u1<span class="sy0" style="line-height:20px">*</span> writeUnsignedLeb128<span class="br0" style="line-height:20px">(</span>u1<span class="sy0" style="line-height:20px">*</span> ptr<span class="sy0" style="line-height:20px">,</span> u4 data<span class="br0" style="line-height:20px">)</span>
<span class="br0" style="line-height:20px">{</span>
    <span class="kw1" style="line-height:20px">while</span> <span class="br0" style="line-height:20px">(</span><span class="kw2" style="line-height:20px">true</span><span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
        u1 out <span class="sy0" style="line-height:20px">=</span> data <span class="sy0" style="line-height:20px">&</span> <span class="nu12" style="line-height:20px">0x7f</span><span class="sy0" style="line-height:20px">;</span>
        <span class="kw1" style="line-height:20px">if</span> <span class="br0" style="line-height:20px">(</span>out <span class="sy0" style="line-height:20px">!=</span> data<span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
            <span class="sy0" style="line-height:20px">*</span>ptr<span class="sy0" style="line-height:20px">++</span> <span class="sy0" style="line-height:20px">=</span> out <span class="sy0" style="line-height:20px">|</span> <span class="nu12" style="line-height:20px">0x80</span><span class="sy0" style="line-height:20px">;</span>
            data <span class="sy0" style="line-height:20px">>>=</span> <span class="nu0" style="line-height:20px">7</span><span class="sy0" style="line-height:20px">;</span>
        <span class="br0" style="line-height:20px">}</span> <span class="kw1" style="line-height:20px">else</span> <span class="br0" style="line-height:20px">{</span>
            <span class="sy0" style="line-height:20px">*</span>ptr<span class="sy0" style="line-height:20px">++</span> <span class="sy0" style="line-height:20px">=</span> out<span class="sy0" style="line-height:20px">;</span>
            <span class="kw2" style="line-height:20px">break</span><span class="sy0" style="line-height:20px">;</span>
        <span class="br0" style="line-height:20px">}</span>
    <span class="br0" style="line-height:20px">}</span>
    <span class="kw1" style="line-height:20px">return</span> ptr<span class="sy0" style="line-height:20px">;</span>
<span class="br0" style="line-height:20px">}</span>      

它的解碼算法如下:

<span class="coMULTI" style="line-height:20px">/*
 * Reads an unsigned LEB128 value, updating the given pointer to point
 * just past the end of the read value. This function tolerates
 * non-zero high-order bits in the fifth encoded byte.
 */</span>
DEX_INLINE <span class="kw4" style="line-height:20px">int</span> readUnsignedLeb128<span class="br0" style="line-height:20px">(</span><span class="kw4" style="line-height:20px">const</span> u1<span class="sy0" style="line-height:20px">**</span> pStream<span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
    <span class="kw4" style="line-height:20px">const</span> u1<span class="sy0" style="line-height:20px">*</span> ptr <span class="sy0" style="line-height:20px">=</span> <span class="sy0" style="line-height:20px">*</span>pStream<span class="sy0" style="line-height:20px">;</span>
    <span class="kw4" style="line-height:20px">int</span> result <span class="sy0" style="line-height:20px">=</span> <span class="sy0" style="line-height:20px">*</span><span class="br0" style="line-height:20px">(</span>ptr<span class="sy0" style="line-height:20px">++</span><span class="br0" style="line-height:20px">)</span><span class="sy0" style="line-height:20px">;</span>
   <span class="kw1" style="line-height:20px">if</span> <span class="br0" style="line-height:20px">(</span>result <span class="sy0" style="line-height:20px">></span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
        <span class="kw4" style="line-height:20px">int</span> cur <span class="sy0" style="line-height:20px">=</span> <span class="sy0" style="line-height:20px">*</span><span class="br0" style="line-height:20px">(</span>ptr<span class="sy0" style="line-height:20px">++</span><span class="br0" style="line-height:20px">)</span><span class="sy0" style="line-height:20px">;</span>
        result <span class="sy0" style="line-height:20px">=</span> <span class="br0" style="line-height:20px">(</span>result <span class="sy0" style="line-height:20px">&</span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="sy0" style="line-height:20px">|</span> <span class="br0" style="line-height:20px">(</span><span class="br0" style="line-height:20px">(</span>cur <span class="sy0" style="line-height:20px">&</span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="sy0" style="line-height:20px"><<</span> <span class="nu0" style="line-height:20px">7</span><span class="br0" style="line-height:20px">)</span><span class="sy0" style="line-height:20px">;</span>
        <span class="kw1" style="line-height:20px">if</span> <span class="br0" style="line-height:20px">(</span>cur <span class="sy0" style="line-height:20px">></span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
            cur <span class="sy0" style="line-height:20px">=</span> <span class="sy0" style="line-height:20px">*</span><span class="br0" style="line-height:20px">(</span>ptr<span class="sy0" style="line-height:20px">++</span><span class="br0" style="line-height:20px">)</span><span class="sy0" style="line-height:20px">;</span>
            result <span class="sy0" style="line-height:20px">|=</span> <span class="br0" style="line-height:20px">(</span>cur <span class="sy0" style="line-height:20px">&</span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="sy0" style="line-height:20px"><<</span> <span class="nu0" style="line-height:20px">14</span><span class="sy0" style="line-height:20px">;</span>
            <span class="kw1" style="line-height:20px">if</span> <span class="br0" style="line-height:20px">(</span>cur <span class="sy0" style="line-height:20px">></span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
                cur <span class="sy0" style="line-height:20px">=</span> <span class="sy0" style="line-height:20px">*</span><span class="br0" style="line-height:20px">(</span>ptr<span class="sy0" style="line-height:20px">++</span><span class="br0" style="line-height:20px">)</span><span class="sy0" style="line-height:20px">;</span>
                result <span class="sy0" style="line-height:20px">|=</span> <span class="br0" style="line-height:20px">(</span>cur <span class="sy0" style="line-height:20px">&</span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="sy0" style="line-height:20px"><<</span> <span class="nu0" style="line-height:20px">21</span><span class="sy0" style="line-height:20px">;</span>
                <span class="kw1" style="line-height:20px">if</span> <span class="br0" style="line-height:20px">(</span>cur <span class="sy0" style="line-height:20px">></span> <span class="nu12" style="line-height:20px">0x7f</span><span class="br0" style="line-height:20px">)</span> <span class="br0" style="line-height:20px">{</span>
                    <span class="coMULTI" style="line-height:20px">/*
                     * Note: We don't check to see if cur is out of
                     * range here, meaning we tolerate garbage in the
                     * high four-order bits.
                     */</span>
                    cur <span class="sy0" style="line-height:20px">=</span> <span class="sy0" style="line-height:20px">*</span><span class="br0" style="line-height:20px">(</span>ptr<span class="sy0" style="line-height:20px">++</span><span class="br0" style="line-height:20px">)</span><span class="sy0" style="line-height:20px">;</span>
                    result <span class="sy0" style="line-height:20px">|=</span> cur <span class="sy0" style="line-height:20px"><<</span> <span class="nu0" style="line-height:20px">28</span><span class="sy0" style="line-height:20px">;</span>
                <span class="br0" style="line-height:20px">}</span>
            <span class="br0" style="line-height:20px">}</span>
        <span class="br0" style="line-height:20px">}</span>
    <span class="br0" style="line-height:20px">}</span>
    <span class="sy0" style="line-height:20px">*</span>pStream <span class="sy0" style="line-height:20px">=</span> ptr<span class="sy0" style="line-height:20px">;</span>
    <span class="kw1" style="line-height:20px">return</span> result<span class="sy0" style="line-height:20px">;</span>
<span class="br0" style="line-height:20px">}</span>      

根據上面的算法分析上面例子字元串,取得第一個位元組是06,最高位為0,是以沒有後繼位元組,那麼取出這個位元組裡7位有效資料,就是6,也就是說這個字元串是6個位元組,但不包括結束字元“\0”。

2、關于長度的意義

由于字元串内容采用的是UTF-8格式編碼,表示一個字元的位元組數是不定的。即有時是一個位元組表示一個字元,有時是兩個、三個甚至四個位元組表示一個字元。 而這裡的長度代表的并不是整個字元串所占用的位元組數,表示這個字元串包含的字元個數。是以在讀取時需要注意,尤其是在包含中文字元時,往往會因為讀取的長 度不正确導緻字元串被截斷。