Android_dex详解

来源:互联网 发布:java培训多少 编辑:程序博客网 时间:2024/06/18 07:11

DEX详解:

         Dex文件分为dex和odex。odex是优化版的dex。先介绍dex:


DEX使用的数据结构为

U1:表示一位无符号位。

U2:表示两位无符号位。

U4:表示四位无符号位。

U8:表示八位无符号位。

sleb128:有符号,长度1~5字节。

uleb128:无符号,长度1~5符号。

uleb128p1:无符号uleb128值加一,长度1~5。

其中u1~u4没问题,但是后三个需要解释一下。


上图是两个字节的leb128,在实际使用的时候系统会去读取第一个字节的最高位,如果是1,表示这个长度不够需要在读取第二个字节,如果还是一则读取第三个,直到有一个的最高位是0则表示读取结束。但是最多读取五个字节,如果这五个都是一则表示apk出错,会停止安装。

         下面实际解析两个,比如字符序列为C0 83 92 25为例子:

C0: 1 1000000 83: 1 0000011 92: 1 0010010 25: 0 0100101

         可以看到前三个的最高位都为1,读到25最高位为0则停止读取。

在真正读取的时候把最高位去掉,倒叙连起来就是0100101 0010010 0000011 1000000就是4A481C0。所以起uleb128就是0x4A481C0,uleb128p1就是uleb128+1=0x4A481C1。Sleb128把其左移四位,最高位就是符号位,这里是0,就是正的。

         再比如字符序列 d1 c2b3 40。计算leb为11010001 11000010 10110011 01000000

整理就是1000000 0110011 1000010 1010001=80CE151。左移四位最高位是1,为负数,就是0xF80CE151。

数据结构

字段名称

偏移值

长度

描述

U1

Magic[8]

0x0

8

'Magic'值,即魔数字段,格式如”dex/n035/0”,其中的035表示结构的版本。

U4

checksum

0x8

4

校验码。

U1

signature

0xC

20

SHA-1签名。

U4

file_size

0x20

4

Dex文件的总长度。

U4

header_size

0x24

4

文件头长度,009版本=0x5C,035版本=0x70

U4

endian_tag

0x28

4

标识字节顺序的常量,根据这个常量可以判断文件是否交换了字节顺序,缺省情况下=0x78563412

U4

link_size

0x2C

4

连接段的大小,如果为0就表示是静态连接。

U4

link_off

0x30

4

连接段的开始位置,从本文件头开始算起。如果连接段的大小为0,这里也是0

U4

map_off

0x34

4

map数据基地址。

U4

string_ids_size

0x38

4

字符串列表的字符串个数。

U4

string_ids_off

0x3C

4

字符串列表表基地址。

U4

type_ids_size

0x40

4

类型列表里类型个数。

U4

type_ids_off

0x44

4

类型列表基地址。

U4

proto_ids_size

0x48

4

原型列表里原型个数。

U4

proto_ids_off

0x4C

4

原型列表基地址。

U4

field_ids_size

0x50

4

字段列表里字段个数。

U4

field_ids_off

0x54

4

字段列表基地址。

U4

method_ids_size

0x58

4

方法列表里方法个数。

U4

method_ids_off

0x5C

4

方法列表基地址。

U4

class_defs_size

0x60

4

类定义类表中类的个数。

U4

class_defs_off

0x64

4

类定义列表基地址。

U4

data_size

0x68

4

数据段的大小,必须以4字节对齐。

U4

data_off

0x6C

4

数据段基地址




上图和上表就是dex的文件头的结构和各个位置的意思。其中最开始的64 65 78 0A 30 33 3500(dex.035.)表示这是按照dex解析的。

 

string_ids_size和string_ids_off

这两个字段表示dex中用到的所有的字符串内容的大小和偏移值,我们需要解析完这部分,然后用一个字符串池存起来,后面有其他的数据结构会用索引值来访问字符串,这个池子也是非常重要的。

 

type_ids_size和type_ids_off

这两个字段表示dex中的类型数据结构的大小和偏移值,比如类类型,基本类型等信息

 

proto_ids_size和type_ids_off

这两个字段表示dex中的元数据信息数据结构的大小和偏移值,描述方法的元数据信息,比如方法的返回类型,参数类型等信息

 

field_ids_size和field_ids_off

这两个字段表示dex中的字段信息数据结构的大小和偏移值

 

method_ids_size和method_ids_off

这两个字段表示dex中的方法信息数据结构的大小和偏移值

 

class_defs_size和class_defs_off

这两个字段表示dex中的类信息数据结构的大小和偏移值,这个数据结构是整个dex中最复杂的数据结构,他内部层次很深,包含了很多其他的数据结构,所以解析起来也很麻烦。

 

data_size和data_off

这两个字段表示dex中数据区域的结构信息的大小和偏移值,这个结构中存放的是数据区域,比如我们定义的常量值等信息。

 

DEX虚拟机解析dex文件的内容,最终都将其映射成DexMapList数据结构。DexHeader结构的mapoff字段指明了DexMapList结构在dex文件中的偏移。

其中DexMapList的结构如下:

typedef structDexMapList {

               u4 size;                                //表明接下来有多少个DexMapItem结构

             DexMapItem list[1];       // DexMapItem结构

}DexMapList;

 

DexMapItem结构如下:

         typedefstruct DexMapItem {

u2 type;             //KDexType开头类型,枚举以下结构

                u2 unused;       //暂时未使用

             u4 size;              //制定类型个数

             u4 offset;          //指定类型数据的文件偏移

}DexMapItem;

 

DexMapItem结构定义如下:

enum{

   kDexTypeHeaderItem                                = 0x0000, //头文件

   kDexTypeStringIdItem                                 = 0x0001,

   kDexTypeTypeIdItem                                  = 0x0002,

   kDexTypeProtoIdItem                                = 0x0003,

   kDexTypeFieldIdItem                                  = 0x0004,

    kDexTypeMethodIdItem                           =0x0005, //方法

   kDexTypeClassDefItem                             = 0x0006, //类

   kDexTypeMapList                                        = 0x1000, //Map

   kDexTypeTypeList                                        = 0x1001,

   kDexTypeAnnotationSetRefList               = 0x1002,

   kDexTypeAnnotationSetItem                  = 0x1003,

   kDexTypeClassDataItem                           =0x2000,

   kDexTypeCodeItem                                     =0x2001,

   kDexTypeStringDataItem                         = 0x2002, //字符

   kDexTypeDebugInfoItem                           =0x2003,

   kDexTypeAnnotationItem                         =0x2004,

   kDexTypeEncodedArrayItem                    =0x2005,

   kDexTypeAnnotationsDirectoryItem      =0x2006,

};

 

以下新建一个安卓工程,自动生成helloworld为例:放在010中找到mapoff。


map_off

00 01 87 00

string_ids_size

00 00 15 66

string_ids_off

00 00 00 70

type_ids_size

00 00 02 F2

type_ids_off

00 00 56 08

proto_ids_size

00 00 03 F2

proto_ids_off

00 00 61 D0

field_ids_size

00 00 04 A9

field_ids_off

00 00 91 28

method_ids_size

00 00 12 D4

method_ids_off

00 00 B6 70

class_defs_size

00 00 01 C7

class_defs_off

00 01 4D 10

data_size

00 07 F4 44

data_off

00 01 87 00


可以看到mapoff=00018700。跳到00018700看到下图:


对照数据结构可以看到个数为0x11=17。则表示有17个DexMapItem。之后观察DexMapItem结构可以发现其占用三个字节。第一个表示其type,第二个表示个数,第三个表示偏移地址。所以可以得到下表:

Type

个数

偏移地址

kDexTypeHeaderItem

1

0x00000000

kDexTypeStringIdItem

5478

0x00000070

kDexTypeTypeIdItem

754

0x00005608

kDexTypeProtoIdItem

1010

0x000061D0

kDexTypeFieldIdItem

1193

0x00009128

kDexTypeMethodIdItem

4820

0x0000B670

kDexTypeClassDefItem

455

0x00014D10

kDexTypeMapList

1

0X00018700

kDexTypeTypeList

611

0X000187DC

kDexTypeAnnotationSetItem

486

0X00019EC8

kDexTypeClassDataItem

444

0X0001B2DC

kDexTypeCodeItem

3083

0X00022CDC

kDexTypeStringDataItem

5478

0X0005BD74

kDexTypeDebugInfoItem

3083

0X000786A4

kDexTypeAnnotationItem

559

0X00091824

kDexTypeEncodedArrayItem

65

0X000955D4

kDexTypeAnnotationsDirectoryItem

373

0X00095D44

         两个表比较发现有些地址是相同的,这里为什么要重复的去保存相同的地址,是为了校验,如果比对发现不相同就停止安装。

下面开始逐步解析这个表,其结构虽然不复杂,但是调用有点乱。

kDexTypeHeaderItem就是之前的文件头,占0x70个字节。描述了整个DEX文件结构。

 

kDexTypeStringIdItem对应了string_ids_off和string_ids_size。在0x70的位置往下有5478个字符串地址如下图:


在对应的位置可以看到下图:


可以看到这一片往下保存的都是字符串。这里使用的解析方式是MUTF-8,其解析方式如下

最高位表示个数,之后按照最高位规定的个数读取,最后结尾为00。例如下:

我们随便找两个连续偏移为0005BD9F 0005BDC7


可以看到0005BD9F地址是0x26,往后读取38位到0005BDC6为00。下一个字符串的偏移为0005BDC7刚好在前一个字符串00后面。MUTF-8最开始的大小不包括最后的结束符。

 

kDexTypeTypeIdItem对应type_ids_size和type_ids_off。在0x00005608偏移的位置0x00005608,如图:


kDexTypeTypeIdItem结构指向的结构是DexTypeId,表示应用程序代码中使用到的具体类型。结构如下

struct DexTypeId{

                   u4              descriptorIdx;           //指向DexStringId列表索引

};

这里需要注意:这里的不是地址,而是指向stringID的列表索引,说白了这里的0000 01 98,不是地址00000198,而是指kDexTypeStringIdItem的第0x198的那个字符串。因为在编译的时候已经将字符串统一存在了一个池子中,需要的时候按照ID去字符串池中取。

这里比如前三个:00000198,000001B6,00000350:对应的十进制为:408,438,848.

00000198对应的字符串地址为:,对应的字符串如下:


为:B

000001B6对应的字符串地址为:,对应的字符串如下:


为:C

00000350对应的字符串地址为:,对应的字符串如下:


为:Landroid/app/Activity;

 

以上三个大致可以发现这就是smali中的原始类型B,C和类Landroid/app/Activity。

 

kDexTypeProtoIdItem对应proto_ids_size和proto_ids_off字段,指向DexProtoId,表示方法声明的结构体,结构如下:

   struct DexProtoId {

       u4 shortyIdx;                      //指向DexStringId列表的索引

       u4 returnTypeIdx;            //指向DexTypeId列表的索引

       u4 parametersOff;                    //指向DexTypeList的偏移

};

         并且其中shortyIdx为方法声明字符串,returnTypeIdx为方法返回类型字符串,parametersOff为指向一个DexTypeList结构体,存放了方法的参数列表。声明如下:

         StructDexTypeList{

                   U4    size;                             //DexTypeItrm的个数

                   DexTypeItrm    list[1];       //DexTypeItrm结构

};

DexTypeItrm结构如下:

         StructDexTypeItrm{

                   U2    typeIdx;                      //指向DexTypeId列表的索引

};

由上可知kDexTypeProtoIdItem的地址为 ,并且有1010个。下图就是指向的DexProtoId结构


在去DexTypeId索引的时候,起始位置是0,不是1fuck

可以看到每三个字节为一个结构体:我们随机提取两个:

BF 01 00 00 0100 00 00 4C 88 01 00;对应就是1BF,1,1884C

所以shortyIdx表示DexStringId的第447个,找到其地址为00 05E1 8C。在这个地址下找到字符串为:CI

returnTypeIdx 指向DexTypeId的第1个就是上面找到的C

parametersOff偏移地址为1884C,对应数据如图


Size=1,DexTypeItrm=0x4.指向DexTypeId的第四个地址是:BF 0200 00。找到对应的字符串地址为:4C FB 05 00。找到对应字符串为I

    所以可以知道这个的方法有一个参数并且类型是I,返回值类型为C,方法声明为CI

 

5D 02 00 00 0300 00 00 EC 87 01 00;对应就是25D,3,187EC

所以shortyIdx表示DexStringId的第605个,找到其地址为00 05F2 63。在这个地址下找到字符串为:FF

returnTypeIdx 指向DexTypeId的第3个对应的字符串ID是593,对应的字符串地址为86F1 05 00,对应字符串为:F

parametersOff偏移地址为187EC,对应数据如图


Size=1,DexTypeItrm=0x3.指向DexTypeId的第三个地址找到对应的字符串地址为:86 F1 05 00。找到对应字符串为:F

   所以可以知道这个的方法有一个参数并且类型是F,返回值类型为F, 方法声明为FF

 

kDexTypeFieldIdItem对应着field_ids_size和field_ids_off字段指向的结构为DexFieldId:表示代码中的字段。结构如下:

   struct DexFieldId {

       u2 classIdx;     //指向DexTypeId列表索引,表示字段所属的类

       u2 typeIdx;      //指向DexTypeId列表索引,表示字段类型

       u4 nameIdx;   //指向DexStringId列表索引,表示字段名

   };

找到偏移为0x00009128的DexFieldId结构体位置,有1193个结构体,如图:


这里一定要注意读取顺序,两位和四位的读取方式一定要注意,比如读取classldxtypeldx,是两位两位读取,如果按照四位如nameidx方式读取理解,就会把参数位置弄混。

根据结构体可以知道每两个字节代表一个结构体,我们随机选取几个来分析:

00 10 00 04 00 00 09 57:classIdx=16;typeIdx=4;nameIdx=0x957=2391

typeIdx地址为BF 02 00 00,对应string地址为4C FB 0500,字符串为:I

classIdx地址为57 03 00 00,对应string地址为B0 03 0600,字符串为:Landroid/app/Notification;

nameIdx字符串地址为42 D3 06 00,对应字符串为audioStreamType。

我们在smali中可以看到如下图:


看到上图就可以知道这个结构体包含的是什么了。

就是 classIdx->nameIdx: typeIdx.

 

kDexTypeMethodIdItem对应method_ids_size和method_ids_off,指向DexMethodId结构体,表示代码中使用的方法,结构体如下:

   struct DexMethodId {

       u2 classIdx;     //指向DexTypeId列表索引,表示方法所属的类

       u2 protoIdx;    //指向DexProtoId列表索引,表示方法原型

       u4 nameIdx;   //指向DexStringId列表索引,表示方法名;

   };

在偏移0x0000B670位置有4820 DexMethodId结构体个如下图:


选取第一个00 06 03 7E 00 00 0B DF为例:classIdx=0x6, protoIdx=0x37E,nameIdx=00000BDF

classIdx:对应的type地址为4D 03 0000,字符串地址为4E 02 06 00,字符串为Landroid/accessibilityservice/AccessibilityServiceInfo;

protoIdx:对应DexProtoId的第0x37E=894个,地址和内容如图。


根据DexProtoId结构解析:shortyIdx=0x857;returnTypeIdx=0x2D8;parametersOff=0

shortyIdx对应字符串为23 C306 00:Z

returnTypeIdx字符串为EB C3 0600:[B

parametersOff不存在

nameIdx:对应地址为E8 F6 06 00字符串为:getCanRetrieveWindowContent

对应smali可以看到下图

可以发现,实现了getCanRetrieveWindowContent方法。这里都是实现的各种方法。

 

kDexTypeClassDefItem对应着class_defs_size和class_defs_off字段。其指向的结构体为

         typedefstruct DexClassDef {

            u4 classIdx;            //类的类型,指向DexTypeId列表

            u4 accessFlags;              //访问标志

            u4 superclassIdx;    //父类类型,指向DexTypeId列表

            u4 interfacesOff;      //接口,指向DexTypeList偏移

            u4 sourceFileIdx;      //源文件名,指向DexStringId列表索引

            u4 annotationsOff;   //注解,指向DexAnnotationsDirectoryItem结构

            u4 classDataOff;              //指向DexclassData结构

            u4 staticValuesOff;   //指向DexEncodedArray结构的偏移

 }DexClassDef;

accessFlags字段是类的访问标志,是以ACC_开头的一个枚举值。如果存在接口interfacesOff就会指向一个DexTypeList结构,否则为0.

 

DexclassData结构如下:

         typedefstruct DexClassData {

            DexClassDataHeader     header;                       //指定字段与方法的个数

            DexField*                staticFields;               //静态字段,DexField结构

            DexField*                   instanceFields;                   //实例字段,DexField结构

            DexMethod*              directMethods;        //直接方法,DexMethod结构

            DexMethod*              virtualMethods;       //虚方法,DexMethod结构

        } DexClassData;

 

其中DexClassDataHeader结构记录了当前类中字段与方法的数目,声明如下:

         typedefstruct DexClassDataHeader {

            u4 staticFieldsSize;           //静态字段个数

            u4 instanceFieldsSize;      //实例字段个数

            u4 directMethodsSize;    //直接方法个数

            u4 virtualMethodsSize;   //虚方法个数

        } DexClassDataHeader;

 

DexField结构描述了字段的类型和访问标志,结构如下:

         typedefstruct DexField {

            u4 fieldIdx;           //指向DexFieldId的索引

            u4 accessFlags;        //访问标志

        } DexField;

 

DexMethod结构描述了方法的原型,名称,访问标志以及代码数据块

typedef structDexMethod {

            u4 methodIdx;      //指向DexMethodId索引

            u4 accessFlags;        //访问标志

            u4 codeOff;            //指向DexCode结构的偏移

} DexMethod;

 

DexCode结构详细的描述了方法的信息以及方法指令的内容

         typedefstruct DexCode {

            u2 registersSize;  //使用的寄存器个数

            u2 insSize;              //参数个数

            u2 outsSize;           //调用其它方法时使用的寄存器个数

            u2 triesSize;           //Try/Catch个数

            u4 debugInfoOff; //指向调试信息的偏移

            u4 insnsSize;       //指令集个数,以2字节为单位

            u2 insns[1];            //指令集

                   //2字节对齐

                   //try_item[triesSize]        DexTry结构

                   //Try/Catch中handler个数

                   //catch_handler_item[handlersSize],DexCatchHandler结构

 }DexCode;

 

在0x00014D10偏移的位置找到DexClassDef结构起始位置如图:


这里我们选取如下的位置:


classIdx;                             =0000 00 71    DexTypeId=BB 03 00 00  stringoff=80 14 06 00  A9 14 06 00

string=Landroid/support/v4/app/DialogFragment;

Landroid/support/v4/app/Fragment$1

accessFlags;              =00 00 00 10    对应下表:不生子类

superclassIdx;         =00 00 02 A6    DexTypeId=18 06 00 00  stringoff= 36 92 06 00 4C 9206 00

                   string=Ljava/lang/Runnable;

Ljava/lang/RuntimeException;

interfacesOff;          =00 01 90 58    DexTypeList=0100 00 00 52 00 00 00 size=1 typeid=0x52

                   DexTypeId=9C 03 00 00 stringoff= FF 0C 06 00

                   String=Landroid/os/Parcelable;

sourceFileIdx;          =00 00 01 A0    stringoff=94 DE 05 00

                   string=BackStackRecord.java

annotationsOff;      =00 09 5F 3C

classDataOff;        =00 01 B5 57             header=01 09 03 03 这里采用的是uleb128的取值方式,所以对应的         staticFieldsSize;                 0x1   静态字段个数

instanceFieldsSize;           0x9   实例字段个数

directMethodsSize;          0x3   直接方法个数

virtualMethodsSize;         0x3   虚方法个数

         首先是DexField结构:fieldIdx =0x67  从0开始第103个 内容:71 00 04 00 84 0E00 00

                                               classIdx=0x71  BB030000 Landroid/support/v4/app/BackStackState;

                                               typeIdx=0x4              string=I

                                               nameIdx=0XE84       string= mBreadCrumbShortTitleRes

accessFlags = 19  ACC_PUBLIC| ACC_STATIC|ACC_FINAL

staticValuesOff;       =00 00 00 00

 

 

Name

Value

For Classes (and InnerClass annotations)

For Fields

For Methods

ACC_PUBLIC

0x1

public: visible everywhere

public: visible everywhere

public: visible everywhere

ACC_PRIVATE

0x2

* private: only visible to defining class

private: only visible to defining class

private: only visible to defining class

ACC_PROTECTED

0x4

* protected: visible to package and subclasses

protected: visible to package and subclasses

protected: visible to package and subclasses

ACC_STATIC

0x8

* static: is not constructed with an outer this reference

static: global to defining class

static: does not take a this argument

ACC_FINAL

0x10

final: not subclassable

final: immutable after construction

final: not overridable

ACC_SYNCHRONIZED

0x20

synchronized: associated lock automatically acquired around call to this method.Note: This is only valid to set when ACC_NATIVE is also set.

ACC_BRIDGE

0x40

bridge method, added automatically by compiler as a type-safe bridge

ACC_VOLATILE

0x40

volatile: special access rules to help with thread safety

ACC_TRANSIENT

0x80

transient: not to be saved by default serialization

ACC_VARARGS

0x80

last argument should be treated as a “rest” argument by compiler

ACC_NATIVE

0x100

native: implemented in native code

ACC_INTERFACE

0x200

interface: multiply-implementable abstract class

ACC_ABSTRACT

0x400

abstract: not directly instantiable

abstract: unimplemented by this class

ACC_STRICT

0x800

strictfp: strict rules for floating-point arithmetic

ACC_SYNTHETIC

0x1000

not directly defined in source code

not directly defined in source code

not directly defined in source code

ACC_ANNOTATION

0x2000

declared as an annotation class

ACC_ENUM

0x4000

declared as an enumerated type

declared as an enumerated value

(unused)

0x8000

ACC_CONSTRUCTOR

0x10000

constructor method (class or instance initializer)

ACC_DECLARED_SYNCHRONIZED

0x20000

declared synchronized. Note: This has no effect on execution (other than in reflection of this flag, per se).


1.<activityandroid:label="@string/app_name"android:name=".FileBrowser">

2.<intent-filter>

3.<actionandroid:name="android.intent.action.MAIN"/>

4.<categoryandroid:name="android.intent.category.LAUNCHER"/>

依次解释为:

1. android:label表示Activity的标题。android:name指定了Activity具体的类

2. <intent-filter>表示这个类的启动方式

3.当包含"android.intent.action.MAIN"的时候,这个表示上面的android:name=中保存的类是Activity的主类。

4.当包含"android.intent.category.LAUNCHER"的时候表示可以通过LAUNCHER来启动。

         如果没有发现"android.intent.action.MAIN""android.intent.category.LAUNCHER"表示这个程序安装成功后不存在界面和图标,是隐藏的。

 

这个时候我们可以去Activity的主类中找到OnCreate()。这个位置就是程序的起始位置

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 骑车骑的屁股疼怎么办 爬山时屁股摔紫青了怎么办 宝宝不肯脱裤子拉粑粑怎么办 国家对无地农民怎么办 生完孩子骨架变大怎么办 17岁长高很慢怎么办? 出月子腿着凉了怎么办 脚着凉了脚疼怎么办 腿着凉了特别疼怎么办 孩子骨龄大2两年怎么办 和人吃饭很尴尬怎么办 头不自觉向右偏怎么办 靠墙站立腰疼怎么办 小腿酸痛乏力肌肉萎缩怎么办 搬重物后手臂疼怎么办 和尚鹦鹉吃了盐怎么办 刚买鹦鹉不上手怎么办 word的文件时间改了怎么办 图强gps编码丢失怎么办 武统台湾后岛民怎么办 没有你我怎么办是什么歌 ios 12软件闪退怎么办 来大姨妈想吐怎么办 3岁宝宝体重轻怎么办 硕士延期毕业考上博士怎么办 中国人移民欧洲饮食不习惯怎么办 出车祸了报警警察不管怎么办 高中的孩子不好好上学怎么办 和老公消费观念不合拍怎么办 去医院没带现金怎么办 微信读书下架了怎么办 24岁血压有点高怎么办 吃鸡鼠标弹出来怎么办 电脑分辨率调错了怎么办 猎豹sc9打不开门怎么办 苹果手机卡顿反应慢怎么办 金立手机卡顿反应慢怎么办 20天宝宝黄疸219怎么办? 智慧树选修挂科怎么办 军人被纠察抓了怎么办 我家的小孩很凶怎么办