Android逆向之旅---解析編譯以後的Dex文件格式

1、前言

新的一年又開始了,你們是否還記得去年年底的時候,咱們還有一件事沒有作,那就是解析Android中編譯以後的classes.dex文件格式,咱們在去年的時候已經介紹了:html

如何解析編譯以後的xml文件格式:java

http://blog.csdn.net/jiangwei0910410003/article/details/50568487
android

如何解析編譯以後的resource.arsc文件格式:算法

http://blog.csdn.net/jiangwei0910410003/article/details/50628894
shell

那麼咱們還剩下一個文件格式就是classes.dex了,那麼今天咱們就來看看最後一個文件格式解析,關於Android中的dex文件的相關知識這裏就不作太多的解釋了,網上有不少資料能夠參考,並且,咱們在以前介紹的一篇加固apk的那篇文章中也介紹了一點dex的格式知識點:http://blog.csdn.net/jiangwei0910410003/article/details/48415225,咱們按照以前的解析思路來,首先仍是來一張神圖:api


有了這張神圖,那麼接下來咱們就能夠來介紹dex的文件結構了,首先仍是來看一張大致的結構圖:數組



2、準備工做

咱們在講解數據結構以前,咱們須要先建立一個簡單的例子來幫助咱們來解析,咱們須要獲得一個簡單的dex文件,這裏咱們不借助任何的IDE工具,就能夠構造一個dex文件出來。藉助的工具很簡單:javac,dx命令便可。微信

建立 java 源文件 ,內容以下
代碼:
public class Hello
{
public static void main(String[] argc)
{
System.out.println("Hello, Android!\n");
}
}

在當前工做路徑下 , 編譯方法以下 :
(1) 編譯成 java class 文件
執行命令 : javac Hello.java
編譯完成後 ,目錄下生成 Hello.class 文件 。可使用命令 java Hello 來測試下 ,會輸出代碼中的 「Hello, Android!」 的字符串 。
(2) 編譯成 dex 文件
編譯工具在 Android SDK 的路徑以下 ,其中 19.0.1 是Android SDK build_tools 的版本 ,請按照在本地安裝的 build_tools 版原本 。建議該路徑加載到 PATH 路徑下 ,不然引用 dx 工具時須要使用絕對路徑 :./build-tools/19.0.1/dx
執行命令 : dx --dex --output=Hello.dex Hello.class
編譯正常會生成 Hello.dex 文件 。
3. 使用 ADB 運行測試
測試命令和輸出結果以下 :
$ adb root
$ adb push Hello.dex /sdcard/
$ adb shell
root@maguro:/ # dalvikvm -cp /sdcard/Hello.dex Hello
Hello, Android!

4. 重要說明
(1) 測試環境使用真機和 Android 虛擬機均可以的 。核心的命令是
dalvikvm -cp /sdcard/Hello.dex Hello
-cp 是 class path 的縮寫 ,後面的 Hello 是要運行的 Class 的名稱 。網上有描述說輸入 dalvikvm --help
能夠看到 dalvikvm 的幫助文檔 ,可是在 Android4.4 的官方模擬器和本身的手機上測試都提示找不到
Class 路徑 ,在Android 老的版本 ( 4.3 ) 上測試仍是有輸出的 。
(2) 由於命令在執行時 , dalvikvm 會在 /data/dalvik-cache/ 目錄下建立 .dex 文件 ,所以要求 ADB 的
執行 Shell 對目錄 /data/dalvik-cache/ 有讀、寫和執行的權限 ,不然沒法達到預期效果 。

數據結構


3、講解數據結構

下面咱們按照這張大致的思路圖來一一講解各個數據結構app

第1、頭部信息Header結構

dex文件裏的header。除了描述.dex文件的文件信息外,還有文件裏其它各個區域的索引。header對應成結構體類型,邏輯上的描述我用結構體header_item來理解它。先給出結構體裏面用到的數據類型ubyte和uint的解釋,而後再是結構體的描述,後面對各類結構描述的時候也是用的這種方法。

代碼定義:

package com.wjdiankong.parsedex.struct;

import com.wjdiankong.parsedex.Utils;

public class HeaderType {
	
	/**
	 * struct header_item
		{
		ubyte[8] magic;
		unit checksum;
		ubyte[20] siganature;
		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;
		}
	 */
	public byte[] magic = new byte[8];
	public int checksum;
	public byte[] siganature = new byte[20];
	public int file_size;
	public int header_size;
	public int endian_tag;
	public int link_size;
	public int link_off;
	public int map_off;
	public int string_ids_size;
	public int string_ids_off;
	public int type_ids_size;
	public int type_ids_off;
	public int proto_ids_size;
	public int proto_ids_off;
	public int field_ids_size;
	public int field_ids_off;
	public int method_ids_size;
	public int method_ids_off;
	public int class_defs_size;
	public int class_defs_off;
	public int data_size;
	public int data_off;
	
	@Override
	public String toString(){
		return "magic:"+Utils.bytesToHexString(magic)+"\n"
				+ "checksum:"+checksum + "\n"
				+ "siganature:"+Utils.bytesToHexString(siganature) + "\n"
				+ "file_size:"+file_size + "\n"
				+ "header_size:"+header_size + "\n"
				+ "endian_tag:"+endian_tag + "\n"
				+ "link_size:"+link_size + "\n"
				+ "link_off:"+Utils.bytesToHexString(Utils.int2Byte(link_off)) + "\n"
				+ "map_off:"+Utils.bytesToHexString(Utils.int2Byte(map_off)) + "\n"
				+ "string_ids_size:"+string_ids_size + "\n"
				+ "string_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(string_ids_off)) + "\n"
				+ "type_ids_size:"+type_ids_size + "\n"
				+ "type_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(type_ids_off)) + "\n"
				+ "proto_ids_size:"+proto_ids_size + "\n"
				+ "proto_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(proto_ids_off)) + "\n"
				+ "field_ids_size:"+field_ids_size + "\n"
				+ "field_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(field_ids_off)) + "\n"
				+ "method_ids_size:"+method_ids_size + "\n"
				+ "method_ids_off:"+Utils.bytesToHexString(Utils.int2Byte(method_ids_off)) + "\n"
				+ "class_defs_size:"+class_defs_size + "\n"
				+ "class_defs_off:"+Utils.bytesToHexString(Utils.int2Byte(class_defs_off)) + "\n"
				+ "data_size:"+data_size + "\n"
				+ "data_off:"+Utils.bytesToHexString(Utils.int2Byte(data_off));
				
				
	}

}

查看Hex以下:


咱們用一張圖來描述各個字段的長度:


裏面一對一對以_size和_off爲後綴的描述:data_size是以Byte爲單位描述data區的大小,其他的
_size都是描述該區裏元素的個數;_off描述相對與文件起始位置的偏移量。其他的6個是描述.dex文件信
息的,各項說明以下:
(1) magic value
這 8 個 字節通常是常量 ,爲了使 .dex 文件可以被識別出來 ,它必須出如今 .dex 文件的最開頭的
位置 。數組的值能夠轉換爲一個字符串以下 :
{ 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 } = "dex\n035\0"
中間是一個 ‘\n' 符號 ,後面 035 是 Dex 文件格式的版本 。
(2) checksum 和 signature
文件校驗碼 ,使用alder32 算法校驗文件除去 maigc ,checksum 外餘下的全部文件區域 ,用於檢
查文件錯誤 。
signature , 使用 SHA-1 算法 hash 除去 magic ,checksum 和 signature 外餘下的全部文件區域 ,
用於惟一識別本文件 。
(3) file_size
Dex 文件的大小 。
(4) header_size
header 區域的大小 ,單位 Byte ,通常固定爲 0x70 常量 。
(5) endian_tag
大小端標籤 ,標準 .dex 文件格式爲 小端 ,此項通常固定爲 0x1234 5678 常量 。

(6) link_size和link_off

這個兩個字段是表示連接數據的大小和偏移值

(7) map_off
map item 的偏移地址 ,該 item 屬於 data 區裏的內容 ,值要大於等於 data_off 的大小 。結構如
map_list 描述 :

package com.wjdiankong.parsedex.struct;

import java.util.ArrayList;
import java.util.List;

public class MapList {
	
	/**
	 * struct maplist
		{
		uint size;
		map_item list [size];
		}
	 */
	
	public int size;
	public List<MapItem> map_item = new ArrayList<MapItem>();

}
定義位置 : data區
引用位置 :header 區 。
map_list 裏先用一個 uint 描述後面有 size 個 map_item , 後續就是對應的 size 個 map_item 描述 。
map_item 結構有 4 個元素 : type 表示該 map_item 的類型 ,本節能用到的描述以下 ,詳細Dalvik
Executable Format 裏 Type Code 的定義 ;size 表示再細分此 item , 該類型的個數 ;offset 是第一個元
素的針對文件初始位置的偏移量 ; unuse 是用對齊字節的 ,無實際用處 。結構定義以下:

package com.wjdiankong.parsedex.struct;

public class MapItem {
	
	/**
	 * struct map_item
		{
		ushort type;
		ushort unuse;
		uint size;
		uint offset;
		}
	 */
	
	public short type;
	public short unuse;
	public int size;
	public int offset;
	
	public static int getSize(){
		return 2 + 2 + 4 + 4;
	}
	
	@Override
	public String toString(){
		return "type:"+type+",unuse:"+unuse+",size:"+size+",offset:"+offset;
	}

}
header->map_off = 0x0244 , 偏移爲 0244 的位置值爲 0x 000d 。

每一個 map_item 描述佔用 12 Byte , 整個 map_list 佔用 12 * size + 4 個字節 。因此整個 map_list 佔用空
間爲 12 * 13 + 4 = 160 = 0x00a0 , 佔用空間爲 0x 0244 ~ 0x 02E3 。從文件內容上看 ,也是從 0x 0244
到文件結束的位置 。


地址 0x0244 的一個 uinit 的值爲 0x0000 000d ,map_list - > size = 0x0d = 13 ,說明後續有 13 個
map_item 。根據 map_item 的結構描述在0x0248 ~ 0x02e3 裏的值 ,整理出這段二進制所表示的 13 個
map_item 內容 ,匯成表格以下 :
map_list - > map_item 裏的內容 ,有部分 item 跟 header 裏面相應 item 的 offset 地址描述相同 。但
map_list 描述的更爲全面些 ,又包括了 HEADER_ITEM , TYPE_LIST , STRING_DATA_ITEM 等 ,
最後還有它本身 TYPE_MAP_LIST 。
至此 , header 部分描述完畢 ,它包括描述 .dex 文件的信息 ,其他各索引區和 data 區的偏移信息 , 一個
map_list 結構 。map_list 裏除了對索引區和數據區的偏移地址又一次描述 ,也有其它諸如 HEAD_ITEM ,
DEBUG_INFO_ITEM 等信息 。

(8) string_ids_size和string_ids_off

這兩個字段表示dex中用到的全部的字符串內容的大小和偏移值,咱們須要解析完這部分,而後用一個字符串池存起來,後面有其餘的數據結構會用索引值來訪問字符串,這個池子也是很是重要的。後面會詳細介紹string_ids的數據結構

(9) type_ids_size和type_ids_off

這兩個字段表示dex中的類型數據結構的大小和偏移值,好比類類型,基本類型等信息,後面會詳細介紹type_ids的數據結構

(10) proto_ids_size和type_ids_off

這兩個字段表示dex中的元數據信息數據結構的大小和偏移值,描述方法的元數據信息,好比方法的返回類型,參數類型等信息,後面會詳細介紹proto_ids的數據結構

(11) field_ids_size和field_ids_off

這兩個字段表示dex中的字段信息數據結構的大小和偏移值,後面會詳細介紹field_ids的數據結構

(12) method_ids_size和method_ids_off

這兩個字段表示dex中的方法信息數據結構的大小和偏移值,後面會詳細介紹method_ids的數據結構

(13) class_defs_size和class_defs_off

這兩個字段表示dex中的類信息數據結構的大小和偏移值,這個數據結構是整個dex中最複雜的數據結構,他內部層次很深,包含了不少其餘的數據結構,因此解析起來也很麻煩,因此後面會着重講解這個數據結構

(14) data_size和data_off

這兩個字段表示dex中數據區域的結構信息的大小和偏移值,這個結構中存放的是數據區域,好比咱們定義的常量值等信息。

到這裏咱們就看完了dex的頭部信息,頭部包含的信息仍是不少的,主要就兩個個部分:

1) 魔數+簽名+文件大小等信息

2) 後面的各個數據結構的大小和偏移值,都是成對出現的

下面咱們就來開始介紹各個數據結構的信息


第2、string_ids數據結構

string_ids 區索引了 .dex 文件全部的字符串 。 本區裏的元素格式爲 string_ids_item , 可使用結
構體以下描述 。

package com.wjdiankong.parsedex.struct;

import com.wjdiankong.parsedex.Utils;

public class StringIdsItem {
	
	/**
	 * struct string_ids_item
		{
		uint string_data_off;
		}
	 */
	
	public int string_data_off;
	
	public static int getSize(){
		return 4;
	}
	
	@Override
	public String toString(){
		return Utils.bytesToHexString(Utils.int2Byte(string_data_off));
	}

}
以 _ids 結尾的各個 section 裏放置的都是對應數據的偏移地址 ,只是一個索引 ,因此纔會在 dex文件佈局裏把這些區歸類爲 「索引區」 。
string_data_off 只是一個偏移地址 ,它指向的數據結構爲 string_data_item

package com.wjdiankong.parsedex.struct;

import java.util.ArrayList;
import java.util.List;

public class StringDataItem {
	
	/**
	 * struct string_data_item
		{
		uleb128 utf16_size;
		ubyte data;
		}
	 */
	
	/**
	 *  上述描述裏提到了 LEB128 ( little endian base 128 ) 格式 ,是基於 1 個 Byte 的一種不定長度的
		編碼方式 。若第一個 Byte 的最高位爲 1 ,則表示還須要下一個 Byte 來描述 ,直至最後一個 Byte 的最高
		位爲 0 。每一個 Byte 的其他 Bit 用來表示數據
	 */
	
	public List<Byte> utf16_size = new ArrayList<Byte>();
	public byte data;

}

延展

上述描述裏提到了 LEB128 ( little endian base 128 ) 格式 ,是基於 1 個 Byte 的一種不定長度的編碼方式 。若第一個 Byte 的最高位爲 1 ,則表示還須要下一個 Byte 來描述 ,直至最後一個 Byte 的最高位爲 0 。每一個 Byte 的其他 Bit 用來表示數據 。這裏既然介紹了uleb128這種數據類型,就在這裏解釋一下,由於後面會常常用到這個數據類型,這個數據類型的出現其實就是爲了解決一個問題,那就是減小內存的浪費,他就是表示int類型的數值,可是int類型四個字節有時候在使用的時候有點浪費,因此就應運而生了,他的原理也很簡單:


圖只是指示性的用兩個字節表示。編碼的每一個字節有效部分只有低7bits,每一個字節的最高bit用來指示是不是最後一個字節。
非最高字節的bit7爲0
最高字節的bit7爲1
將leb128編碼的數字轉換爲可讀數字的規則是:除去每一個字節的bit7,將每一個字節剩餘的7個bits拼接在一塊兒,即爲數字。
好比:
LEB128編碼的0x02b0 ---> 轉換後的數字0x0130
轉換過程:
0x02b0 => 0000 0010 1011 0000 =>去除最高位=> 000 0010 011 0000 =>按4bits重排 => 00 0001 0011 0000 => 0x130

底層代碼位於:android/dalvik/libdex/leb128.h

Java中也寫了一個工具類:

/**
 * 讀取C語言中的uleb類型
 * 目的是解決整型數值浪費問題
 * 長度不固定,在1~5個字節中浮動
 * @param srcByte
 * @param offset
 * @return
 */
public static byte[] readUnsignedLeb128(byte[] srcByte, int offset){
	List<Byte> byteAryList = new ArrayList<Byte>();
	byte bytes = Utils.copyByte(srcByte, offset, 1)[0];
	byte highBit = (byte)(bytes & 0x80);
	byteAryList.add(bytes);
	offset ++;
	while(highBit != 0){
		bytes = Utils.copyByte(srcByte, offset, 1)[0];
		highBit = (byte)(bytes & 0x80);
		offset ++;
		byteAryList.add(bytes);
	}
	byte[] byteAry = new byte[byteAryList.size()];
	for(int j=0;j<byteAryList.size();j++){
		byteAry[j] = byteAryList.get(j);
	}
	return byteAry;
}
這個方法是讀取dex中uleb128類型的數據,遇到一個字節最高位=0就中止讀下個字節的原理來實現便可

還有一個方法就是解碼uleb128類型的數據:

/**
 * 解碼leb128數據
 * 每一個字節去除最高位,而後進行拼接,從新構造一個int類型數值,從低位開始
 * @param byteAry
 * @return
 */
public static int decodeUleb128(byte[] byteAry) {
	int index = 0, cur;
	int result = byteAry[index];
	index++;

	if(byteAry.length == 1){
		return result;
	}

	if(byteAry.length == 2){
		cur = byteAry[index];
		index++;
		result = (result & 0x7f) | ((cur & 0x7f) << 7);
		return result;
	}

	if(byteAry.length == 3){
		cur = byteAry[index];
		index++;
		result |= (cur & 0x7f) << 14;
		return result;
	}

	if(byteAry.length == 4){
		cur = byteAry[index];
		index++;
		result |= (cur & 0x7f) << 21;
		return result;
	}

	if(byteAry.length == 5){
		cur = byteAry[index];
		index++;
		result |= cur << 28;
		return result;
	}

	return result;

}
這個原理很簡單,就是去除每一個字節的最高位,而後拼接剩下的7位,而後重新構造一個int類型的數據,位不夠就從低位開始左移。

咱們經過上面的uleb128的解釋來看,其實uleb128類型就是1~5個字節來回浮動,爲何是5呢?由於他要表示一個4個字節的int類型,可是每一個字節要去除最高位,那麼確定最多隻須要5個字節就能夠表示4個字節的int類型數據了。這裏就解釋了uleb128數據類型,下面咱們迴歸正題,繼續來看string_ids數據結構

根據 string_ids_item 和 string_data_item 的描述 ,加上 header 裏提供的入口位置 string_ids_size
= 0x0e , string_ids_off = 0x70 ,咱們能夠整理出 string_ids 及其對應的數據以下 :



string_ids_item 和 string_data_item 裏提取出的對應數據表格 :


string 裏的各類標誌符號 ,諸如 L , V , VL , [ 等在 .dex 文件裏有特殊的意思 。
string_ids 的終極奧義就是找到這些字符串 。其實使用二進制編輯器打開 .dex 文件時 ,通常工具默認翻譯成 ASCII 碼 ,總會一大片熟悉的字符白生生地非常親切, 也非常晃眼 。剛纔走過的一路子分析流程 ,就是順藤摸瓜找到它們是怎麼來的。之後的一些 type-ids , method_ids 也會引用到這一片熟悉的字符串。

注意:咱們後面的解析代碼會看到,其實咱們不必用那麼複雜的去解析uleb128類型,由於咱們會看到這個字符串和咱們以前解析xml和resource.arsc格式同樣,每一個字符串的第一個字節表示字符串的長度,那麼咱們只要知道每一個字符串的偏移地址就能夠解析出字符串的內容了,而每一個字符串的偏移地址是存放在string_ids_item中的。

到這裏咱們就解析完了dex中全部的字符串內容,咱們用一個字符串池來進行存儲便可。下面咱們來繼續看type_ids數據結構


第3、type_ids數據結構

這個數據結構中存放的數據主要是描述dex中全部的類型,好比類類型,基本類型等信息。type_ids 區索引了 dex 文件裏的全部數據類型 ,包括 class 類型 ,數組類型(array types)和基本類型(primitive types) 。 本區域裏的元素格式爲 type_ids_item , 結構描述以下 :

package com.wjdiankong.parsedex.struct;

import com.wjdiankong.parsedex.Utils;

public class TypeIdsItem {
	
	/**
	 * struct type_ids_item
		{
		uint descriptor_idx;
		}
	 */
	
	public int descriptor_idx;
	
	public static int getSize(){
		return 4;
	}
	
	@Override
	public String toString(){
		return Utils.bytesToHexString(Utils.int2Byte(descriptor_idx));
	}

}
type_ids_item 裏面 descriptor_idx 的值的意思 ,是 string_ids 裏的 index 序號 ,是用來描述此type 的字符串 。

根據 header 裏 type_ids_size = 0x07 , type_ids_off = 0xa8 , 找到對應的二進制描述區 。00000a0: 1a02


根據 type_id_item 的描述 ,整理出表格以下 。由於 type_id_item - > descriptor_idx 裏存放的是指向 string_ids 的 index 號 ,因此咱們也能獲得該 type 的字符串描述 。這裏出現了 3 個 type descriptor :
L 表示 class 的詳細描述 ,通常以分號表示 class 描述結束 ;
V 表示 void 返回類型 ,只有在返回值的時候有效 ;
[ 表示數組 ,[Ljava/lang/String; 能夠對應到 java 語言裏的 java.lang.String[] 類型 。


咱們後面的其餘數據結構也會使用到type_ids類型,因此咱們這裏解析完type_ids也是須要用一個池子來存放的,後面直接用索引index來訪問便可。


第4、proto_ids數據結構

proto 的意思是 method prototype 表明 java 語言裏的一個 method 的原型 。proto_ids 裏的元素爲 proto_id_item , 結構以下 。

package com.wjdiankong.parsedex.struct;

import java.util.ArrayList;
import java.util.List;

public class ProtoIdsItem {
	
	/**
	 * struct proto_id_item
		{
		uint shorty_idx;
		uint return_type_idx;
		uint parameters_off;
		}
	 */
	
	public int shorty_idx;
	public int return_type_idx;
	public int parameters_off;
	
	//這個不是公共字段,而是爲了存儲方法原型中的參數類型名和參數個數
	public List<String> parametersList = new ArrayList<String>();
	public int parameterCount;
	
	public static int getSize(){
		return 4 + 4 + 4;
	}
	
	@Override
	public String toString(){
		return "shorty_idx:"+shorty_idx+",return_type_idx:"+return_type_idx+",parameters_off:"+parameters_off;
	}

}
shorty_idx :跟 type_ids 同樣 ,它的值是一個 string_ids 的 index 號 ,最終是一個簡短的字符串描述 ,用來講明該 method 原型 
return_type_idx :它的值是一個 type_ids 的 index 號 ,表示該 method 原型的返回值類型 。
parameters_off :後綴 off 是 offset , 指向 method 原型的參數列表 type_list ; 若 method 沒有參數 ,值爲0 。參數列表的格式是 type_list ,結構從邏輯上以下描述 。size 表示參數的個數 ;type_idx 是對應參數的類型 ,它的值是一個 type_ids 的 index 號 ,跟 return_type_idx 是同一個品種的東西 。

package com.wjdiankong.parsedex.struct;

import java.util.ArrayList;
import java.util.List;

public class TypeList {
	
	/**
	 * struct type_list
		{
		uint size;
		ushort type_idx[size];
		}
	 */
	
	public int size;//參數的個數
	public List<Short> type_idx = new ArrayList<Short>();//參數的類型
	
}

header 裏 proto_ids_size = 0x03 , proto_ids_off = 0xc4 , 它的二進制描述區以下 :


根據 proto_id_item 和 type_list 的格式 ,對照這它們的二進制部分 ,整理出表格以下 :


能夠看出 ,有 3 個 method 原型 ,返回值都爲 void ,index = 0 的沒有參數傳入 ,index = 1 的傳入一個
String 參數 ,index=2 的傳入一個 String[] 類型的參數 。

注意:咱們在這裏會看到不少idx結尾的字段,這個通常都是索引值,因此咱們要注意的是,區分這個索引值究竟是對應的哪張表格,是字符串池,仍是類型池等信息,這個若是弄混淆的話,那麼解析就會出現混亂了。這個後面其餘數據結構都是須要注意的。


第5、field_ids數據結構

filed_ids 區裏面存放的是dex 文件引用的全部的 field 。本區的元素格式是 field_id_item ,邏輯結構描述如

package com.wjdiankong.parsedex.struct;

public class FieldIdsItem {
	
	/**
	 * struct filed_id_item
		{
		ushort class_idx;
		ushort type_idx;
		uint name_idx;
		}
	 */
	
	public short class_idx;
	public short type_idx;
	public int name_idx;
	
	public static int getSize(){
		return 2 + 2 + 4;
	}

	@Override
	public String toString(){
		return "class_idx:"+class_idx+",type_idx:"+type_idx+",name_idx:"+name_idx;
	}
	
}
class_idx :表示本 field 所屬的 class 類型 , class_idx 的值是 type_ids 的一個 index , 而且必須指向一個class 類型 。
type_idx :表示本 field 的類型 ,它的值也是 type_ids 的一個 index 。
name_idx : 表示本 field 的名稱 ,它的值是 string_ids 的一個 index 。

header 裏 field_ids_size = 1 , field_ids_off = 0xe8 。說明本 .dex 只有一個 field ,這部分的二進制描述以下 :


注意:這裏的字段都是索引值,必定要區分是哪一個池子的索引值,還有就是,這個數據結構咱們後面也要使用到,因此須要用一個池子來存儲。


第6、 method_ids數據結構

method_ids 是索引區的最後一個條目 ,它索引了 dex 文件裏的全部的 method.
method_ids 的元素格式是 method_id_item , 結構跟 fields_ids 很類似:

package com.wjdiankong.parsedex.struct;

public class MethodIdsItem {
	
	/**
	 * struct filed_id_item
		{
		ushort class_idx;
		ushort proto_idx;
		uint name_idx;
		}
	 */
	
	public short class_idx;
	public short proto_idx;
	public int name_idx;
	
	public static int getSize(){
		return 2 + 2 + 4;
	}
	
	@Override
	public String toString(){
		return "class_idx:"+class_idx+",proto_idx:"+proto_idx+",name_idx:"+name_idx;
	}

}
class_idx :表示本 method 所屬的 class 類型 , class_idx 的值是 type_ids 的一個 index , 而且必須指向一個 class 類型 。
name_idx :表示本 method 的名稱 ,它的值是 string_ids 的一個 index 。
proto_idx :描述該 method 的原型 ,指向 proto_ids 的一個 index 。
header 裏 method_ids_size = 0x04 , method_ids_off = 0xf0 。本部分的二進制描述以下 :


對 dex 反彙編的時候 ,經常使用的 method 表示方法是這種形式 :
Lpackage/name/ObjectName;->MethodName(III)Z
將上述表格裏的字符串再次整理下 ,method 的描述分別爲 :
0:Lhello; -> <init>()V
1:LHello; -> main([Ljava/lang/String;)V
2:Ljava/io/PrintStream; -> println(Ljava/lang/String;)V
3: Ljava/lang/Object; -> <init>()V
至此 ,索引區的內容描述完畢 ,包括 string_ids , type_ids,proto_ids , field_ids , method_ids 。每一個索引區域裏存放着指向具體數據的偏移地址 (如 string_ids ) , 或者存放的數據是其它索引區域裏面的 index 號。

注意:這裏的字段都是索引值,必定要區分是哪一個池子的索引值,還有就是,這個數據結構咱們後面也要使用到,因此須要用一個池子來存儲。


第8、class_defs數據結構

上面咱們介紹了全部的索引區域,終於到了最後一個數據結構了,可是咱們如今還不能開心,由於這個數據結構是最複雜的,因此解析下來仍是很費勁的。由於他的層次太深了。

一、class_def_item
從字面意思解釋 ,class_defs 區域裏存放着 class definitions , class 的定義 。它的結構較 dex 區都要複雜些 ,由於有些數據都直接指向了data 區裏面 。
class_defs 的數據格式爲 class_def_item , 結構描述以下 :

package com.wjdiankong.parsedex.struct;

public class ClassDefItem {
	
	/**
	 * struct class_def_item
		{
		uint class_idx;
		uint access_flags;
		uint superclass_idx;
		uint interfaces_off;
		uint source_file_idx;
		uint annotations_off;
		uint class_data_off;
		uint static_value_off;
		}
	 */
	
	public int class_idx;
	public int access_flags;
	public int superclass_idx;
	public int iterfaces_off;
	public int source_file_idx;
	public int annotations_off;
	public int class_data_off;
	public int static_value_off;
	
	public final static int 
			ACC_PUBLIC       = 0x00000001,       // class, field, method, ic
			ACC_PRIVATE      = 0x00000002,       // field, method, ic
			ACC_PROTECTED    = 0x00000004,       // field, method, ic
			ACC_STATIC       = 0x00000008,       // field, method, ic
			ACC_FINAL        = 0x00000010,       // class, field, method, ic
			ACC_SYNCHRONIZED = 0x00000020,       // method (only allowed on natives)
			ACC_SUPER        = 0x00000020,       // class (not used in Dalvik)
			ACC_VOLATILE     = 0x00000040,       // field
			ACC_BRIDGE       = 0x00000040,       // method (1.5)
			ACC_TRANSIENT    = 0x00000080,       // field
			ACC_VARARGS      = 0x00000080,       // method (1.5)
			ACC_NATIVE       = 0x00000100,       // method
			ACC_INTERFACE    = 0x00000200,       // class, ic
			ACC_ABSTRACT     = 0x00000400,       // class, method, ic
			ACC_STRICT       = 0x00000800,       // method
			ACC_SYNTHETIC    = 0x00001000,       // field, method, ic
			ACC_ANNOTATION   = 0x00002000,       // class, ic (1.5)
			ACC_ENUM         = 0x00004000,       // class, field, ic (1.5)
			ACC_CONSTRUCTOR  = 0x00010000,       // method (Dalvik only)
			ACC_DECLARED_SYNCHRONIZED = 0x00020000,       // method (Dalvik only)
			ACC_CLASS_MASK =
			(ACC_PUBLIC | ACC_FINAL | ACC_INTERFACE | ACC_ABSTRACT
					| ACC_SYNTHETIC | ACC_ANNOTATION | ACC_ENUM),
					ACC_INNER_CLASS_MASK =
					(ACC_CLASS_MASK | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC),
					ACC_FIELD_MASK =
					(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
							| ACC_VOLATILE | ACC_TRANSIENT | ACC_SYNTHETIC | ACC_ENUM),
							ACC_METHOD_MASK =
							(ACC_PUBLIC | ACC_PRIVATE | ACC_PROTECTED | ACC_STATIC | ACC_FINAL
									| ACC_SYNCHRONIZED | ACC_BRIDGE | ACC_VARARGS | ACC_NATIVE
									| ACC_ABSTRACT | ACC_STRICT | ACC_SYNTHETIC | ACC_CONSTRUCTOR
									| ACC_DECLARED_SYNCHRONIZED);
	
	public static int getSize(){
		return 4 * 8;
	}
	
	@Override
	public String toString(){
		return "class_idx:"+class_idx+",access_flags:"+access_flags+",superclass_idx:"+superclass_idx+",iterfaces_off:"+iterfaces_off
				+",source_file_idx:"+source_file_idx+",annotations_off:"+annotations_off+",class_data_off:"+class_data_off
				+",static_value_off:"+static_value_off;
	}

}
(1) class_idx:描述具體的 class 類型 ,值是 type_ids 的一個 index 。值必須是一個 class 類型 ,不能是數組類型或者基本類型 。
(2) access_flags: 描述 class 的訪問類型 ,諸如 public , final , static 等 。在 dex-format.html 裏 「access_flagsDefinitions」 有具體的描述 。
(3) superclass_idx:描述 supperclass 的類型 ,值的形式跟 class_idx 同樣 。
(4) interfaces_off:值爲偏移地址 ,指向 class 的 interfaces , 被指向的數據結構爲 type_list 。class 若沒有interfaces ,值爲 0。
(5) source_file_idx:表示源代碼文件的信息 ,值是 string_ids 的一個 index 。若此項信息缺失 ,此項值賦值爲NO_INDEX=0xffff ffff 
(6) annotions_off:值是一個偏移地址 ,指向的內容是該 class 的註釋 ,位置在 data 區,格式爲annotations_direcotry_item 。若沒有此項內容 ,值爲 0 。
(7) class_data_off:值是一個偏移地址 ,指向的內容是該 class 的使用到的數據 ,位置在 data 區,格式爲class_data_item 。若沒有此項內容 ,值爲 0 。該結構裏有不少內容 ,詳細描述該 class 的 field ,method, method 裏的執行代碼等信息 ,後面有一個比較大的篇幅來說述 class_data_item 。
(8) static_value_off:值是一個偏移地址 ,指向 data 區裏的一個列表 ( list ) ,格式爲 encoded_array_item。若沒有此項內容 ,值爲 0 。

header 裏 class_defs_size = 0x01 , class_defs_off = 0x 0110 。則此段二進制描述爲 :



其實最初被編譯的源碼只有幾行 ,和 class_def_item 的表格對照下 ,一目瞭然 。
source file : Hello.java
public class Hello
{
element value associated strinigs
class_idx 0x00 LHello;
access_flags 0x01 ACC_PUBLIC
superclass_idx 0x02 Ljava/lang/Object;
interface_off 0x00
source_file_idx 0x02 Hello.java
annotations_off 0x00
class_data_off 0x0234
static_value_off 0x00

public static void main(String[] argc)
{
System.out.println("Hello, Android!\n");
}
}


二、 class_def_item => class_data_item
class_data_off 指向 data 區裏的 class_data_item 結構 ,class_data_item 裏存放着本 class 使用到的各類數據 ,下面是 class_data_item 的邏輯結構 :

package com.wjdiankong.parsedex.struct;

public class ClassDataItem {
	
	/**
	 *  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_method_size ];
			encoded_method virtual_methods [ virtual_methods_size ];
		}
	 */
	
	//uleb128只用來編碼32位的整型數
	public int static_fields_size;
	public int instance_fields_size;
	public int direct_methods_size;
	public int virtual_methods_size;
	
	public EncodedField[] static_fields;
	public EncodedField[] instance_fields;
	public EncodedMethod[] direct_methods;
	public EncodedMethod[] virtual_methods;
	
	@Override
	public String toString(){
		return "static_fields_size:"+static_fields_size+",instance_fields_size:"
				+instance_fields_size+",direct_methods_size:"+direct_methods_size+",virtual_methods_size:"+virtual_methods_size
				+"\n"+getFieldsAndMethods();
	}
	
	private String getFieldsAndMethods(){
		StringBuilder sb = new StringBuilder();
		sb.append("static_fields:\n");
		for(int i=0;i<static_fields.length;i++){
			sb.append(static_fields[i]+"\n");
		}
		sb.append("instance_fields:\n");
		for(int i=0;i<instance_fields.length;i++){
			sb.append(instance_fields[i]+"\n");
		}
		sb.append("direct_methods:\n");
		for(int i=0;i<direct_methods.length;i++){
			sb.append(direct_methods[i]+"\n");
		}
		sb.append("virtual_methods:\n");
		for(int i=0;i<virtual_methods.length;i++){
			sb.append(virtual_methods[i]+"\n");
		}
		return sb.toString();
	}

}
關於元素的格式 uleb128 在 string_ids 裏有講述過 ,不贅述 。
encoded_field 的結構以下 :

package com.wjdiankong.parsedex.struct;

import com.wjdiankong.parsedex.Utils;

public class EncodedField {
	
	/**
	 * struct encoded_field
		{
			uleb128 filed_idx_diff; // index into filed_ids for ID of this filed
			uleb128 access_flags; // access flags like public, static etc.
		}
	 */
	public byte[] filed_idx_diff;
	public byte[] access_flags;
	
	@Override
	public String toString(){
		return "field_idx_diff:"+Utils.bytesToHexString(filed_idx_diff) + ",access_flags:"+Utils.bytesToHexString(filed_idx_diff);
	}

}

encoded_method 的結構以下 :

package com.wjdiankong.parsedex.struct;

import com.wjdiankong.parsedex.Utils;

public class EncodedMethod {
	
	/**
	 * struct encoded_method
		{
			uleb128 method_idx_diff;
			uleb128 access_flags;
			uleb128 code_off;
		}
	 */
	
	public byte[] method_idx_diff;
	public byte[] access_flags;
	public byte[] code_off;
	
	@Override
	public String toString(){
		return "method_idx_diff:"+Utils.bytesToHexString(method_idx_diff)+","+Utils.bytesToHexString(Utils.int2Byte(Utils.decodeUleb128(method_idx_diff)))
				+",access_flags:"+Utils.bytesToHexString(access_flags)+","+Utils.bytesToHexString(Utils.int2Byte(Utils.decodeUleb128(access_flags)))
				+",code_off:"+Utils.bytesToHexString(code_off)+","+Utils.bytesToHexString(Utils.int2Byte(Utils.decodeUleb128(code_off)));
	}

}

(1) 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 的官網文檔 
(2) access_flags:訪問權限 , 好比 public、private、static、final 等 。
(3) code_off:一個指向 data 區的偏移地址 ,目標是本 method 的代碼實現 。被指向的結構是
code_item ,有近 10 項元素 ,後面再詳細解釋 。
class_def_item -- > class_data_off = 0x 0234 。


名稱爲 LHello; 的 class 裏只有 2 個 directive methods 。 directive_methods 裏的值都是 uleb128 的原始二
進制值 。按照 directive_methods 的格式 encoded_method 再整理一次這 2 個 method 描述 ,獲得結果以下
表格所描述 :


method 一個是 <init> , 一個是 main , 這裏咱們須要用咱們在string_ids那塊介紹到的一個方法就是解碼uleb125類型的方法獲得正確的value值。


三、class_def_item => class_data_item => code_item
到這裏 ,邏輯的描述有點深刻了 。我本身都有點分析不過來 ,先理一下是怎麼走到這一步的 ,code_item
在 dex 裏處於一個什麼位置 。
(1) 一個 .dex 文件被分紅了 9 個區 ,詳細見 「1. dex 整個文件的佈局 」 。其中有一個索引區叫作
class_defs , 索引了 .dex 裏面用到的 class ,以及對這個 class 的描述 。
(2) class_defs 區 , 這裏面實際上是class_def_item 結構 。這個結構裏描述了 LHello; 的各類信息 ,諸如名稱 ,superclass , access flag, interface 等 。class_def_item 裏有一個元素 class_data_off , 指向data 區裏的一個 class_data_item 結構 ,用來描述 class 使用到的各類數據 。自此之後的結構都歸於 data區了 。
(3) class_data_item 結構 ,裏描述值着 class 裏使用到的 static field , instance field , direct_method ,和 virtual_method 的數目和描述 。例子 Hello.dex 裏 ,只有 2 個 direct_method , 其他的 field 和method 的數目都爲 0 。描述 direct_method 的結構叫作 encoded_method ,是用來詳細描述某個 method的 。
(4) encoded_method 結構 ,描述某個 method 的 method 類型 , access flags 和一個指向 code_item的偏移地址 ,裏面存放的是該 method 的具體實現 。
(5) code_item , 一層又一層 ,盜夢空間啊!簡要的說 ,code_item 結構裏描述着某個 method 的具體實現 。它的結構以下描述 :

package com.wjdiankong.parsedex.struct;

import com.wjdiankong.parsedex.Utils;

public class CodeItem {
	
	/**
	 * 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
		}
	 */
	
	public short registers_size;
	public short ins_size;
	public short outs_size;
	public short tries_size;
	public int debug_info_off;
	public int insns_size;
	public short[] insns;
	
	@Override
	public String toString(){
		return "regsize:"+registers_size+",ins_size:"+ins_size
				+",outs_size:"+outs_size+",tries_size:"+tries_size+",debug_info_off:"+debug_info_off
				+",insns_size:"+insns_size + "\ninsns:"+getInsnsStr();
	}
	
	private String getInsnsStr(){
		StringBuilder sb = new StringBuilder();
		for(int i=0;i<insns.length;i++){
			sb.append(Utils.bytesToHexString(Utils.short2Byte(insns[i]))+",");
		}
		return sb.toString();
	}
	
}
末尾的 3 項標誌爲 optional , 表示可能有 ,也可能沒有 ,根據具體的代碼來 。
(1) registers_size:本段代碼使用到的寄存器數目。
(2) ins_size:method傳入參數的數目 。
(3) outs_size: 本段代碼調用其它method 時須要的參數個數 。
(4) tries_size: try_item 結構的個數 。
(5) debug_off:偏移地址 ,指向本段代碼的 debug 信息存放位置 ,是一個 debug_info_item 結構。
(6) insns_size:指令列表的大小 ,以 16-bit 爲單位 。 insns 是 instructions 的縮寫 。
(7) padding:值爲 0 ,用於對齊字節 。
(8) tries 和 handlers:用於處理 java 中的 exception , 常見的語法有 try catch 。

四、 分析 main method 的執行代碼並與 smali 反編譯的結果比較
在 8.2 節裏有 2 個 method , 由於 main 裏的執行代碼是本身寫的 ,分析它會熟悉不少 。偏移地址是
directive_method [1] -> code_off = 0x0148 ,二進制描述以下 :


insns 數組裏的 8 個二進制原始數據 , 對這些數據的解析 ,須要對照官網的文檔 《Dalvik VM Instruction
Format》和《Bytecode for Dalvik VM》。
分析思路整理以下
(1) 《Dalvik VM Instruction Format》 裏操做符 op 都是位於首個 16bit 數據的低 8 bit ,起始的是 op =0x62。
(2) 在 《Bytecode for Dalvik VM》 裏找到對應的 Syntax 和 format 。
syntax = sget_object
format = 0x21c 。
(3) 在《Dalvik VM Instruction Format》裏查找 21c , 得知 op = 0x62 的指令佔據 2 個 16 bit 數據 ,格式是 AA|op BBBB ,解釋爲 op vAA, type@BBBB 。所以這 8 組 16 bit 數據裏 ,前 2 個是一組 。對比數據得 AA=0x00, BBBB = 0x0000。
(4)返回《Bytecode for Dalvik VM》裏查閱對 sget_object 的解釋, AA 的值表示 Value Register ,即0 號寄存器; BBBB 表示 static field 的 index ,就是以前分析的field_ids 區裏 Index = 0 指向的那個東西 ,當時的 fields_ids 的分析結果以下 :


對 field 經常使用的表述是
包含 field 的類型 -> field 名稱 :field 類型 。
這次指向的就是 Ljava/lang/System; -> out:Ljava/io/printStream;
(5) 綜上 ,前 2 個 16 bit 數據 0x 0062 0000 , 解釋爲
sget_object v0, Ljava/lang/System; -> out:Ljava/io/printStream;
其他的 6 個 16 bit 數據分析思路跟這個同樣 ,依次整理以下 :
0x011a 0x0001: const-string v1, 「Hello, Android!」

0x206e 0x0002 0x0010:

invoke-virtual {v0, v1}, Ljava/io/PrintStream; -> println(Ljava/lang/String;)V

0x000e: return-void
(6) 最後再整理下 main method , 用容易理解的方式表示出來就是 。
ACC_PUBLIC ACC_STATIC LHello;->main([Ljava/lang/String;)V
{
sget_object v0, Ljava/lang/System; -> out:Ljava/io/printStream;
const-string v1,Hello, Android!
invoke-virtual {v0, v1}, Ljava/io/PrintStream; -> println(Ljava/lang/String;)V
return-void
}
看起來很像 smali 格式語言 ,不妨使用 smali 反編譯下 Hello.dex , 看看 smali 生成的代碼跟方纔推導出
來的有什麼差別 。
.method public static main([Ljava/lang/String;)V
.registers 3
.prologue
.line 5
sget-object v0, Ljava/lang/System;->out:Ljava/io/PrintStream;
const-string v1, "Hello, Android!\n"
index 0
class_idx 0x04
type_idx 0x01
name_idx 0x0c
class string Ljava/lang/System;
type string Ljava/io/PrintStream;
name string out
invoke-virtual {v0, v1}, Ljava/io/PrintStream;->println(Ljava/lang/String;)V
.line 6
return-void
從內容上看 ,兩者形式上有些差別 ,但表述的是同一個 method 。這說明剛纔的分析走的路子是沒有跑偏
的 。另一個 method 是 <init> , 如果分析的話 ,思路和流程跟 main 同樣 。走到這裏,內心很踏實了。


4、解析代碼

上面咱們解析完了全部的數據結構區域,下面就來看看具體的解析代碼,因爲篇幅的緣由,這裏就不貼出所有的代碼了,只貼出核心的代碼:

一、解析頭部信息:

public static void praseDexHeader(byte[] byteSrc){
	HeaderType headerType = new HeaderType();
	//解析魔數
	byte[] magic = Utils.copyByte(byteSrc, 0, 8);
	headerType.magic = magic;

	//解析checksum
	byte[] checksumByte = Utils.copyByte(byteSrc, 8, 4);
	headerType.checksum = Utils.byte2int(checksumByte);

	//解析siganature
	byte[] siganature = Utils.copyByte(byteSrc, 12, 20);
	headerType.siganature = siganature;

	//解析file_size
	byte[] fileSizeByte = Utils.copyByte(byteSrc, 32, 4);
	headerType.file_size = Utils.byte2int(fileSizeByte);

	//解析header_size
	byte[] headerSizeByte = Utils.copyByte(byteSrc, 36, 4);
	headerType.header_size = Utils.byte2int(headerSizeByte);

	//解析endian_tag
	byte[] endianTagByte = Utils.copyByte(byteSrc, 40, 4);
	headerType.endian_tag = Utils.byte2int(endianTagByte);

	//解析link_size
	byte[] linkSizeByte = Utils.copyByte(byteSrc, 44, 4);
	headerType.link_size = Utils.byte2int(linkSizeByte);

	//解析link_off
	byte[] linkOffByte = Utils.copyByte(byteSrc, 48, 4);
	headerType.link_off = Utils.byte2int(linkOffByte);

	//解析map_off
	byte[] mapOffByte = Utils.copyByte(byteSrc, 52, 4);
	headerType.map_off = Utils.byte2int(mapOffByte);

	//解析string_ids_size
	byte[] stringIdsSizeByte = Utils.copyByte(byteSrc, 56, 4);
	headerType.string_ids_size = Utils.byte2int(stringIdsSizeByte);

	//解析string_ids_off
	byte[] stringIdsOffByte = Utils.copyByte(byteSrc, 60, 4);
	headerType.string_ids_off = Utils.byte2int(stringIdsOffByte);

	//解析type_ids_size
	byte[] typeIdsSizeByte = Utils.copyByte(byteSrc, 64, 4);
	headerType.type_ids_size = Utils.byte2int(typeIdsSizeByte);

	//解析type_ids_off
	byte[] typeIdsOffByte = Utils.copyByte(byteSrc, 68, 4);
	headerType.type_ids_off = Utils.byte2int(typeIdsOffByte);

	//解析proto_ids_size
	byte[] protoIdsSizeByte = Utils.copyByte(byteSrc, 72, 4);
	headerType.proto_ids_size = Utils.byte2int(protoIdsSizeByte);

	//解析proto_ids_off
	byte[] protoIdsOffByte = Utils.copyByte(byteSrc, 76, 4);
	headerType.proto_ids_off = Utils.byte2int(protoIdsOffByte);

	//解析field_ids_size
	byte[] fieldIdsSizeByte = Utils.copyByte(byteSrc, 80, 4);
	headerType.field_ids_size = Utils.byte2int(fieldIdsSizeByte);

	//解析field_ids_off
	byte[] fieldIdsOffByte = Utils.copyByte(byteSrc, 84, 4);
	headerType.field_ids_off = Utils.byte2int(fieldIdsOffByte);

	//解析method_ids_size
	byte[] methodIdsSizeByte = Utils.copyByte(byteSrc, 88, 4);
	headerType.method_ids_size = Utils.byte2int(methodIdsSizeByte);

	//解析method_ids_off
	byte[] methodIdsOffByte = Utils.copyByte(byteSrc, 92, 4);
	headerType.method_ids_off = Utils.byte2int(methodIdsOffByte);

	//解析class_defs_size
	byte[] classDefsSizeByte = Utils.copyByte(byteSrc, 96, 4);
	headerType.class_defs_size = Utils.byte2int(classDefsSizeByte);

	//解析class_defs_off
	byte[] classDefsOffByte = Utils.copyByte(byteSrc, 100, 4);
	headerType.class_defs_off = Utils.byte2int(classDefsOffByte);

	//解析data_size
	byte[] dataSizeByte = Utils.copyByte(byteSrc, 104, 4);
	headerType.data_size = Utils.byte2int(dataSizeByte);

	//解析data_off
	byte[] dataOffByte = Utils.copyByte(byteSrc, 108, 4);
	headerType.data_off = Utils.byte2int(dataOffByte);

	System.out.println("header:"+headerType);

	stringIdOffset = headerType.header_size;//header以後就是string ids

	stringIdsSize = headerType.string_ids_size;
	stringIdsOffset = headerType.string_ids_off;
	typeIdsSize = headerType.type_ids_size;
	typeIdsOffset = headerType.type_ids_off;
	fieldIdsSize = headerType.field_ids_size;
	fieldIdsOffset = headerType.field_ids_off;
	protoIdsSize = headerType.proto_ids_size;
	protoIdsOffset = headerType.proto_ids_off;
	methodIdsSize = headerType.method_ids_size;
	methodIdsOffset = headerType.method_ids_off;
	classIdsSize = headerType.class_defs_size;
	classIdsOffset = headerType.class_defs_off;

	mapListOffset = headerType.map_off;

}
這裏沒啥說的,就是記錄幾個索引區的偏移值和大小信息。

解析結果:



二、解析string_ids索引區

/************************解析字符串********************************/
public static void parseStringIds(byte[] srcByte){
	int idSize = StringIdsItem.getSize();
	int countIds = stringIdsSize;
	for(int i=0;i<countIds;i++){
		stringIdsList.add(parseStringIdsItem(Utils.copyByte(srcByte, stringIdsOffset+i*idSize, idSize)));
	}
	System.out.println("string size:"+stringIdsList.size());
}

public static void parseStringList(byte[] srcByte){
	//第一個字節仍是字符串的長度
	for(StringIdsItem item : stringIdsList){
		String str = getString(srcByte, item.string_data_off);
		System.out.println("str:"+str);
		stringList.add(str);
	}
}

解析結果:



三、解析type_ids索引區

/***************************解析類型******************************/
public static void parseTypeIds(byte[] srcByte){
	int idSize = TypeIdsItem.getSize();
	int countIds = typeIdsSize;
	for(int i=0;i<countIds;i++){
		typeIdsList.add(parseTypeIdsItem(Utils.copyByte(srcByte, typeIdsOffset+i*idSize, idSize)));
	}

	//這裏的descriptor_idx就是解析以後的字符串中的索引值
	for(TypeIdsItem item : typeIdsList){
		System.out.println("typeStr:"+stringList.get(item.descriptor_idx));
	}
}

解析結果:



四、解析proto_ids索引區

/***************************解析Proto***************************/
public static void parseProtoIds(byte[] srcByte){
	int idSize = ProtoIdsItem.getSize();
	int countIds = protoIdsSize;
	for(int i=0;i<countIds;i++){
		protoIdsList.add(parseProtoIdsItem(Utils.copyByte(srcByte, protoIdsOffset+i*idSize, idSize)));
	}

	for(ProtoIdsItem item : protoIdsList){
		System.out.println("proto:"+stringList.get(item.shorty_idx)+","+stringList.get(item.return_type_idx));
		//有的方法沒有參數,這個值就是0
		if(item.parameters_off != 0){
			item = parseParameterTypeList(srcByte, item.parameters_off, item);
		}
	}
}

//解析方法的全部參數類型
private static ProtoIdsItem parseParameterTypeList(byte[] srcByte, int startOff, ProtoIdsItem item){
	//解析size和size大小的List中的內容
	byte[] sizeByte = Utils.copyByte(srcByte, startOff, 4);
	int size = Utils.byte2int(sizeByte);
	List<String> parametersList = new ArrayList<String>();
	List<Short> typeList = new ArrayList<Short>(size);
	for(int i=0;i<size;i++){
		byte[] typeByte = Utils.copyByte(srcByte, startOff+4+2*i, 2);
		typeList.add(Utils.byte2Short(typeByte));
	}
	System.out.println("param count:"+size);
	for(int i=0;i<typeList.size();i++){
		System.out.println("type:"+stringList.get(typeList.get(i)));
		int index = typeIdsList.get(typeList.get(i)).descriptor_idx;
		parametersList.add(stringList.get(index));
	}

	item.parameterCount = size;
	item.parametersList = parametersList;

	return item;
}

解析結果:



五、解析field_ids索引區

/***************************解析字段****************************/
public static void parseFieldIds(byte[] srcByte){
	int idSize = FieldIdsItem.getSize();
	int countIds = fieldIdsSize;
	for(int i=0;i<countIds;i++){
		fieldIdsList.add(parseFieldIdsItem(Utils.copyByte(srcByte, fieldIdsOffset+i*idSize, idSize)));
	}

	for(FieldIdsItem item : fieldIdsList){
		int classIndex = typeIdsList.get(item.class_idx).descriptor_idx;
		int typeIndex = typeIdsList.get(item.type_idx).descriptor_idx;
		System.out.println("class:"+stringList.get(classIndex)+",name:"+stringList.get(item.name_idx)+",type:"+stringList.get(typeIndex));
	}
}

解析結果:



六、解析method_ids索引區

/***************************解析方法*****************************/
public static void parseMethodIds(byte[] srcByte){
	int idSize = MethodIdsItem.getSize();
	int countIds = methodIdsSize;
	for(int i=0;i<countIds;i++){
		methodIdsList.add(parseMethodIdsItem(Utils.copyByte(srcByte, methodIdsOffset+i*idSize, idSize)));
	}

	for(MethodIdsItem item : methodIdsList){
		int classIndex = typeIdsList.get(item.class_idx).descriptor_idx;
		int returnIndex = protoIdsList.get(item.proto_idx).return_type_idx;
		String returnTypeStr = stringList.get(typeIdsList.get(returnIndex).descriptor_idx);
		int shortIndex = protoIdsList.get(item.proto_idx).shorty_idx;
		String shortStr = stringList.get(shortIndex);
		List<String> paramList = protoIdsList.get(item.proto_idx).parametersList;
		StringBuilder parameters = new StringBuilder();
		parameters.append(returnTypeStr+"(");
		for(String str : paramList){
			parameters.append(str+",");
		}
		parameters.append(")"+shortStr);
		System.out.println("class:"+stringList.get(classIndex)+",name:"+stringList.get(item.name_idx)+",proto:"+parameters);
	}

}



七、解析class_def區域

/****************************解析類*****************************/
public static void parseClassIds(byte[] srcByte){
	System.out.println("classIdsOffset:"+Utils.bytesToHexString(Utils.int2Byte(classIdsOffset)));
	System.out.println("classIds:"+classIdsSize);
	int idSize = ClassDefItem.getSize();
	int countIds = classIdsSize;
	for(int i=0;i<countIds;i++){
		classIdsList.add(parseClassDefItem(Utils.copyByte(srcByte, classIdsOffset+i*idSize, idSize)));
	}
	for(ClassDefItem item : classIdsList){
		System.out.println("item:"+item);
		int classIdx = item.class_idx;
		TypeIdsItem typeItem = typeIdsList.get(classIdx);
		System.out.println("classIdx:"+stringList.get(typeItem.descriptor_idx));
		int superClassIdx = item.superclass_idx;
		TypeIdsItem superTypeItem = typeIdsList.get(superClassIdx);
		System.out.println("superitem:"+stringList.get(superTypeItem.descriptor_idx));
		int sourceIdx = item.source_file_idx;
		String sourceFile = stringList.get(sourceIdx);
		System.out.println("sourceFile:"+sourceFile);
		classDataMap.put(sourceFile, item);
	}
}
解析結果:


這裏咱們看到解析結果咱們可能有點看不懂,其實這裏我是沒有在繼續解讀下去了,爲何,由於咱們經過class_def的數據結構解析能夠看到,咱們須要藉助《Bytecode for Dalvik VM》這個來進行查閱具體的指令,而後翻譯成具體的指令代碼,關於這個指令表能夠參考這裏:http://www.netmite.com/android/mydroid/dalvik/docs/dalvik-bytecode.html,因此具體解析並不複雜,因此這裏就不在詳細解析了,具體的解析思路,能夠參考class_def的數據結構解析那一塊的內容,上面又說道。


項目下載地址:http://download.csdn.net/detail/jiangwei0910410003/9432263


注意:

咱們到這裏算是解析完了dex文件了,可是我如今要告訴你們,其實Android中有一個工具能夠爲咱們作這個事,不知道你們還記得咱們以前介紹解析AndroidManifest.xml和resource.arsc文件格式的時候,也是同樣的,直接用aapt命令就能夠查看了,這裏也是同樣的,只是這個工具是:dexdump

這個命令也是在AndroidSdk目錄下的build-tools下面,這裏咱們能夠將打印的結果重定向到demo.txt文件中


那麼咱們上面作的解析工做是否是就沒有用了呢?固然不是,咱們在後面會說道咱們解析dex格式有不少用途的。


5、技術總結和概述

到這裏咱們就解析完了dex文件的全部東東,講解的內容有點多,在這裏就來總結一下:

第1、學習到的技術

一、咱們學習到了如何不是用任何的IDE工具,就能夠構造一個dex文件出來,主要藉助於java和dx命令。同時,咱們也學會了一個能夠執行dex文件的命令:dalvikvm;不過這個命令須要root權限。

二、咱們瞭解到了Android中的DVM指令,如何翻譯指令代碼。

三、學習了一個數據類型:uleb128,如何將uleb128類型和int類型進行轉化。

第2、未解決的問題

咱們在整個解析的過程當中會發現,咱們這裏只是用一個很是簡單的dex來作案例解析,因此解析起來也很容易,可是咱們實際的過程當中,不會這麼簡單的,一個類可能實現多個接口,內部類,註解等信息的時候,解析起來確定還要複雜,那麼咱們這篇文章主要的目的是介紹一下dex的文件格式,目的不是說去解決實際中項目的問題,因此後面在解析複雜的dex的時候,咱們也只能遇到什麼問題就去解決一下。


第3、咱們解析dex的目的是啥?

咱們開始的時候,並無介紹說解析dex幹啥?那麼如今能夠說,解析完dex以後咱們有不少事均可以作了。

一、咱們能夠檢測一個apk中是否包含了指定系統的api(固然這些api沒有被混淆),一樣也能夠檢測這個apk是否包含了廣告,之前咱們能夠經過解析AndroidManifest.xml文件中的service,activity,receiver,meta等信息來判斷,由於如今的廣告sdk都須要添加這些東西,若是咱們能夠解析dex的話,那麼咱們能夠獲得他的全部字符串內容,就是string_ids池,這樣就能夠判斷調用了哪些api。那麼就能夠判斷這個apk的一些行爲了,固然這裏還有一個問題,假如dex加密了咱們就蛋疼了。好吧,那就牽涉出第二件事了。

二、咱們在以前說過如何對apk進行加固,其實就是加密apk/dex文件內容,那麼這時候咱們必需要了解dex的文件結構信息,由於咱們先加密dex,而後在動態加載dex進行解密便可。

三、咱們能夠更好的逆向工做,其實說到這裏,咱們看看apktool源碼也知道,他內部的反編譯原理就是這些,只是他會將指令翻譯成smail代碼,這個網上是有相對應的jar包api的,因此咱們知道了dex的數據結構,那麼原理確定就知道了,一樣還有一個dex2jar工具原理也是相似的。


6、總結

到這裏咱們就介紹完了dex文件格式的解析工做,至此咱們也解析完了Android中編譯以後的全部文件格式,我之因此介紹這幾篇文章,一來是更好的瞭解Android中生成apk的流程,其次是咱們能更好的的解決反編譯過程當中遇到的問題,咱們須要去解決。這篇文章解析起來仍是很費勁的,累死了,也是2016年第一篇文章,謝謝你們的支持~~。記得點贊呀~~

PS: 關注微信,最新Android技術實時推送

相關文章
相關標籤/搜索