Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android逆向之旅---解析編譯之後的Dex文件格式

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

編輯:關於Android編程

一、前言

新的一年又開始了,大家是否還記得去年年末的時候,我們還有一件事沒有做,那就是解析Android中編譯之後的classes.dex文件格式,我們在去年的時候已經介紹了:

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

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

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

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

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

\

有了這張神圖,那麼接下來我們就可以來介紹dex的文件結構了,首先還是來看一張大體的結構圖:

\

 

二、准備工作

我們在講解數據結構之前,我們需要先創建一個簡單的例子來幫助我們來解析,我們需要得到一個簡單的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/ 有讀、寫和執行的權限 ,否則無法達到預期效果 。

三、講解數據結構

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

第一、頭部信息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 map_item = new ArrayList();

}
定義位置 : 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) 後面的各個數據結構的大小和偏移值,都是成對出現的

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

 

第二、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 utf16_size = new ArrayList();
	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 byteAryList = new ArrayList();
	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這個方法是讀取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數據結構

 

第三、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來訪問即可。

 

第四、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 parametersList = new ArrayList();
	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 type_idx = new ArrayList();//參數的類型
	
}

 

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結尾的字段,這個一般都是索引值,所以我們要注意的是,區分這個索引值到底是對應的哪張表格,是字符串池,還是類型池等信息,這個如果弄混淆的話,那麼解析就會出現混亂了。這個後面其他數據結構都是需要注意的。

 

第五、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 ,這部分的二進制描述如下 :

\

注意:這裡的字段都是索引值,一定要區分是哪個池子的索引值,還有就是,這個數據結構我們後面也要使用到,所以需要用一個池子來存儲。

 

第六、 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; -> ()V
1:LHello; -> main([Ljava/lang/String;)V
2:Ljava/io/PrintStream; -> println(Ljava/lang/String;)V
3: Ljava/lang/Object; -> ()V
至此 ,索引區的內容描述完畢 ,包括 string_ids , type_ids,proto_ids , field_ids , method_ids 。每個索引區域裡存放著指向具體數據的偏移地址 (如 string_ids ) , 或者存放的數據是其它索引區域裡面的 index 號。

注意:這裡的字段都是索引值,一定要區分是哪個池子的索引值,還有就是,這個數據結構我們後面也要使用到,所以需要用一個池子來存儲。

 

第八、class_defs數據結構

上面我們介紹了所有的索引區域,終於到了最後一個數據結構了,但是我們現在還不能開心,因為這個數據結構是最復雜的,所以解析下來還是很費勁的。因為他的層次太深了。

1、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");
}
}
 

2、 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關於元素的格式 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 一個是 , 一個是 main , 這裡我們需要用我們在string_ids那塊介紹到的一個方法就是解碼uleb125類型的方法得到正確的value值。

 

3、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末尾的 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 。

 

4、 分析 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 是 , 若是分析的話 ,思路和流程跟 main 一樣 。走到這裡,心裡很踏實了。

 

四、解析代碼

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

1、解析頭部信息:

 

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;

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

 

解析結果:

\

 

2、解析string_ids索引區

 

/************************解析字符串********************************/
public static void parseStringIds(byte[] srcByte){
	int idSize = StringIdsItem.getSize();
	int countIds = stringIdsSize;
	for(int i=0;i

 

解析結果:

\

 

3、解析type_ids索引區

 

/***************************解析類型******************************/
public static void parseTypeIds(byte[] srcByte){
	int idSize = TypeIdsItem.getSize();
	int countIds = typeIdsSize;
	for(int i=0;i

 

解析結果:

\

 

4、解析proto_ids索引區

/***************************解析Proto***************************/
public static void parseProtoIds(byte[] srcByte){
	int idSize = ProtoIdsItem.getSize();
	int countIds = protoIdsSize;
	for(int i=0;i parametersList = new ArrayList();
	List typeList = new ArrayList(size);
	for(int i=0;i

解析結果:

\

5、解析field_ids索引區

 

/***************************解析字段****************************/
public static void parseFieldIds(byte[] srcByte){
	int idSize = FieldIdsItem.getSize();
	int countIds = fieldIdsSize;
	for(int i=0;i

 

解析結果:

\

 

6、解析method_ids索引區

 

/***************************解析方法*****************************/
public static void parseMethodIds(byte[] srcByte){
	int idSize = MethodIdsItem.getSize();
	int countIds = methodIdsSize;
	for(int i=0;i 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);
	}

}

 

\

 

7、解析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解析結果:

 

\ 這裡我們看到解析結果我們可能有點看不懂,其實這裡我是沒有在繼續解讀下去了,為什麼,因為我們通過class_def的數據結構解析可以看到,我們需要借助《Bytecode for Dalvik VM》這個來進行查閱具體的指令,然後翻譯成具體的指令代碼,關於這個指令表可以參考這裡:http://www.netmite.com/android/mydroid/dalvik/docs/dalvik-bytecode.html,所以具體解析並不復雜,所以這裡就不在詳細解析了,具體的解析思路,可以參考class_def的數據結構解析那一塊的內容,上面又說道。

五、技術總結和概述

 

到這裡我們就解析完了dex文件的所有東東,講解的內容有點多,在這裡就來總結一下:

第一、學習到的技術

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

2、我們了解到了Android中的DVM指令,如何翻譯指令代碼。

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

第二、未解決的問題

我們在整個解析的過程中會發現,我們這裡只是用一個非常簡單的dex來做案例解析,所以解析起來也很容易,但是我們實際的過程中,不會這麼簡單的,一個類可能實現多個接口,內部類,注解等信息的時候,解析起來肯定還要復雜,那麼我們這篇文章主要的目的是介紹一下dex的文件格式,目的不是說去解決實際中項目的問題,所以後面在解析復雜的dex的時候,我們也只能遇到什麼問題就去解決一下。

 

第三、我們解析dex的目的是啥?

我們開始的時候,並沒有介紹說解析dex干啥?那麼現在可以說,解析完dex之後我們有很多事都可以做了。

1、我們可以檢測一個apk中是否包含了指定系統的api(當然這些api沒有被混淆),同樣也可以檢測這個apk是否包含了廣告,以前我們可以通過解析AndroidManifest.xml文件中的service,activity,receiver,meta等信息來判斷,因為現在的廣告sdk都需要添加這些東西,如果我們可以解析dex的話,那麼我們可以得到他的所有字符串內容,就是string_ids池,這樣就可以判斷調用了哪些api。那麼就可以判斷這個apk的一些行為了,當然這裡還有一個問題,假如dex加密了我們就蛋疼了。好吧,那就牽涉出第二件事了。

2、我們在之前說過如何對apk進行加固,其實就是加密apk/dex文件內容,那麼這時候我們必須要了解dex的文件結構信息,因為我們先加密dex,然後在動態加載dex進行解密即可。

3、我們可以更好的逆向工作,其實說到這裡,我們看看apktool源碼也知道,他內部的反編譯原理就是這些,只是他會將指令翻譯成smail代碼,這個網上是有相對應的jar包api的,所以我們知道了dex的數據結構,那麼原理肯定就知道了,同樣還有一個dex2jar工具原理也是類似的。

六、總結

到這裡我們就介紹完了dex文件格式的解析工作,至此我們也解析完了Android中編譯之後的所有文件格式,我之所以介紹這幾篇文章,一來是更好的了解Android中生成apk的流程,其次是我們能更好的的解決反編譯過程中遇到的問題,我們需要去解決。這篇文章解析起來還是很費勁的,累死了,也是2016年第一篇文章,謝謝大家的支持~~。記得點贊呀~~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved