Android_dex详解

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()。这个位置就是程序的起始位置

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值