android .dex檔案探究
在我們寫Java程式碼的時候,生成的檔案是.java檔案,但是JVM並不識別這個,所以會先轉成class檔案,而在Android端,Android上的Davlik虛擬機器能執行.dex。所以dex檔案中包含了所有的app程式碼,可利用反編譯工具獲取java程式碼。
即dex檔案就是Android Dalvik虛擬機器執行的程式。
為了能夠加深印象,我們先構造一個dex檔案。
public class DEX {
int a = 0;
static String b = "HelloDalvik";
public int getNumber(int i,int j){
int e = 3;
return e+i+j;
}
public static void main(String[] args){
int c= 1;
int d = 2;
DEX dex = new DEX();
String sayNumber = String.valueOf(dex.getNumber(c,d));
System.out.println("HelloDalVik "+sayNumber);
}
}
先執行 javac DEX.java
這樣會生成一個.class檔案,然後在SDK中找到dx.bat,
執行 xx\xxx\xxx\dx --dex --output=DEX.java DEX.class
這樣就會生成DEX.dex檔案,如果出現does not match path 的錯誤,就,這樣執行:
D:\SDK\build-tools\27.0.1\dx --dex --output=DEX.dex com\example\asus1\rexiufu\DEX.class
然後我們使用010 Editor來開啟這個DEX.dex檔案
DEX檔案結構
我們先看Header:
檔案頭包含了dex檔案的資訊,所有資料的大致分佈情況
然後我們看看Header的資料圖:在Editor中檢視:
可以看到第一個,magic[8],它代表dex檔案中的檔案標識,一般被稱為魔數。是用來識別dex這種檔案的,它可以判斷當前的dex檔案是否有效,可以看到它用來8個1位元組的無符號數來表示,我們在Editor中可以看到是“64 65 78 0A 30 33 35 00”這8個位元組,這些位元組都是16進製表示的。這8個位元組用ASCII碼轉換為dex.035(“.”不是轉換來的)。目前,dex的魔數固定為dex.035
然後第二個,checksum,它是dex檔案的校驗和,通過它可以判斷dex檔案是否被損壞或者被篡改。它佔用4個位元組
我們可以看到它的值和它對應的四個位元組,剛好是反著的。這是由於dex檔案中採用的是小位元組序的編碼方法,也就是低位上儲存的就是低位元組的內容。
第三個是SHA1 signature[20],signature欄位用於檢驗dex檔案,其實就是把整個dex檔案用SHA-1簽名得到的一個值。這裡佔用了20個位元組。
第四個是fileSize,表示整個檔案的大小,佔用4個位元組
第五個是headerSize,表示DexHeader頭結構的大小,佔用4個位元組。
第六個是endianTag,代表位元組序標記,用於執行dex執行環境的cpu,預設值為0x12345678,對應在101Editor中為“78 56 34 12”
接下來兩個分別是linkSize和linkOff,這連個欄位,分別指定了連結段的大小和檔案偏移,通常情況下它們都為0,。linkSize為0的話表示靜態連線。
再下來就是mapOff欄位,它指定了DexMapList的檔案偏移,就是dex檔案結構圖中的最後一層。
接下來的兩個StringIdsSize和StringIdsOff:這兩個欄位指定了dex檔案中所有用到的字串的個數和位置偏移。我們先看StringIdsSize,現在它的是十進位制值為28,也就是說我們這個dex檔案一共有28個字串,StringIdsOff的值為“70 00 00 00”,表示字串的偏移位置為70h,然後我們找到70h的地方:
在第4箇中,顯示的字串是“HelloDalVik”,
然後我們再看看dex的結構圖:
然後我們繼續看
接下來是typeIdsSize和typeIdsOff,它們代表類的型別的數量和位置偏移,都佔4個位元組,typeIdsSize的的值為9,表示dex檔案中用到的類的型別一共有9個,typeIdsOff的值為“E0 00 00 00 ”,即偏移位置在E0:
接下來兩個是protoIdsSize和protoIdsOff,它們表示dex檔案中方法原型的個數和位置偏移,現在protoIdsSize的值為7,說明有7個方法原型,然後位置偏移是104h
這裡涉及到一個數據結構:
struct DexProtoId{
u4 shortyIdx; /*指向DexStringId列表的索引*/
u4 returnTypeIdx; /*指向DexTypeId列表的索引*/
u4 parametersOff; /*指向DexTypeList的位置偏移*/
}
可以看到,這個資料結構由三個變數組成。第一個shortyIdx,它指向我們上面分析的DexStringId列表的索引,代表的是方法宣告字串。第二個returnTypeIdx它指向的是我們上班分析的DexTypeId列表索引,代表的是方法返回型別字串。第三個parameterOff指向的是DexTypeList的位置索引,這又是一個新的資料結構,這裡面儲存的是方法的引數類別。
可以看到這三個引數,方法宣告字串,返回型別,引數列表,這基本上確定了我們一個方法的大體內容。
struct DexTypeList{
u4 size; /*DexTypeItem的個數*/
DexTypeItem list[1]; /*DexTypeItem結構*/
}
struct DexTypeItem{
u2 typeIdx; /*指向DexTypeId列表的索引*/
}
這樣的話,我們可以看到,第一個方法為 int(int,int)
我們繼續看下面的兩個,fieldIdsSize和fieldIdsOff,指向的是dex檔案中欄位名的資訊。這裡fieldIdsSize的大小為3,fieldIdsOff的值為“58 01 00 00 ”:
又涉及到有個資料結構:
struct DexFieldId{
u2 classIdx; /*類的型別,指向DexTypeId列表的索引*/
u2 typeIdx; /*欄位型別,指向DexTypeId列表的索引*/
u4 nameIdx; /*欄位名,指向DexStringId列表的索引*/
}
我們可以看到第一個DexFieldId是Dex中的a
繼續繼續,methodIdsSize和methodIdsOff,指明瞭方法所在的類,方法的宣告以及方法名,現在methodIdsSize的值為10,methodIdsOff的值為“70 01 00 00”,
它涉及到的資料結構:
struct DexMethodId{
u2 classIdx; /*類的型別,指向DexTypeId列表的索引*/
u2 protoIdx; /*宣告型別,指向DexProtoId列表的索引*/
u4 nameIdx; /*方法名,指向DexStringId列表的索引*/
}
分析可以得到,第一個方法為 void DEX.<clinit>()
接下來是classDefsSize和classDefsOff,這兩個欄位指明的是dex檔案中類的定義的相關資訊,在這裡,classDefsSize的值是1,classDefsOff的是“C0 01 00 00”,
這裡涉及的資料結構:
struct DexClassDef{
u4 classIdx; /*類的型別,指向DexTypeId列表的索引*/
u4 accessFlags; /*訪問標誌*/
u4 superclassIdx; /*父類型別,指向DexTypeId列表的索引*/
u4 interfacesOff; /*介面,指向DexTypeList的偏移*/
u4 sourceFileIdx; /*原始檔名,指向DexStringId列表的索引*/
u4 annotationsOff; /*註解,指向DexAnnotationsDirectoryItem結構*/
u4 classDataOff; /*指向DexClassData結構的偏移*/
u4 staticValuesOff; /*指向DexEncodedArray結構的偏移*/
}
我們可以看到,這個找到的類是DEX
接下來是DataSize和DataOff,這裡DataOff的值是“E0 01 00 00 ”,找到01E0h,這裡存放的是DexCode。
dex檔案的載入流程
https://blog.csdn.net/jsqfengbao/article/details/52103439
https://www.jianshu.com/p/c9fd64e0b934
參考:
https://blog.csdn.net/sinat_18268881/article/details/55832757