安卓逆向培训 发表于 2020-2-24 00:02:29

零基础安卓逆向学习之旅(六-上)

零基础安卓逆向学习之旅(六-上)
1,将java源码编译成DEX文件

(1)创建一个Example.java文件,并编写下边代码

公共类示例{

       公共静态void main(String [] args){

            System.out.printf(“ HelloWorld!\ n”);

       }

}

(2)将java源码编译为.class文件

javac–source 1.6 –target 1.6 Example.java

(3)确定dx工具的位置

       较早版本的SDK将dx工具放置于 / sdk / platform-tools / dx,而较新版本则将其放置与/ sdk / built-tools / android- / dx。




(4)执行下边命令生成DEX文件

    / sdk / platform-tools / dx --dex --output = Example.dex Example.class




2,解析DEX文件格式

(1)DEX文件结构

struct DexFile {

/ *直接映射的“ opt”标头* /

       const DexOptHeader * pOptHeader;

/ *指向基本DEX中直接映射的结构和数组的指针* /

       const DexHeader * pHeader;

       const DexStringId * pStringIds;

       const DexTypeId * pTypeIds;

       const DexFieldId * pFieldIds;

       const DexMethodId * pMethodIds;

       const DexProtoId * pProtoIds;

       const DexClassDef * pClassDefs;

       const DexLink * pLinkData;

/ *

       *这些是在“辅助”部分之外映射的,可能不是

       *包含在文件中。

* /

       const DexClassLookup * pClassLookup;

       const void * pRegisterMapPool; // RegisterMapClassPool

/ *指向DEX文件数据的开始* /

       const u1 * baseAddr;

/ *跟踪辅助结构的内存开销* /

       int开销;

/ *与DEX相关的其他特定于应用的数据结构* /

       // void * auxData;

};

      DEX文件的第一个片段为DEX文件头,下边为DEX文件头的定义

struct DexHeader {

       u1魔法; / * dex版本标识* /

       u4校验和;/ * adler32检验* /

       u1签名; / * SHA-1哈希* /

       u4 fileSize; / *整个dex文件大小* /

       u4 headerSize; / * DexHeader结构大小* /

       u4 endianTag; / *字节序标记* /

       u4 linkSize; / *链接段大小* /

       u4 linkOff; / *链接段偏移* /

       u4 mapOff; / * DexMapList的文件替换* /

       u4 stringIdsSize; / * DexStringId的个数* /

       u4 stringIdsOff; / * DexStringId的文件替换* /

       u4 typeIdsSize; / * DexTypeId的个数* /

       u4 typeIdsOff; / * DexTypeId的文件替换* /

       u4 protoIdsSize; / * DexProtoId的个数* /

       u4 protoIdsOff; / * DexProtoId的文件替换* /

       u4 fieldIdsSize; / * DexFieldId的个数* /

       u4 fieldIdsOff; / * DexFieldId的文件替换* /

       u4 methodIdsSize; / * DexMethodId的个数* /

       u4 methodIdsOff; / * DexMethodId的文件替换* /

       u4 classDefsSize; / * DexClassDef的个数* /

       u4 classDefsOff; / * DexClassDef的文件替换* /

       u4 dataSize; / *数据段的大小* /

       u4 dataOff; / *数据段的文件替换* /

};



    其中数据类型u1,u4为无符号整型数的别名

typedef uint8_t u1; / * 8字节无符号整数* /

typedef uint32_t u4; / * 32字节无符号整数* /

typedef int8_t s1; / * 8字节有符号整数* /

typedef int32_t s4; / * 32字节有符号整数* /

    查看Example.dex文件,以十六进制显示

hexdump -C Example.dex




DEX文件的第一个细分

u1魔法; / *包括版本号* /

magic 是一个标记,通常称为magicnumber

u4校验和;/ * adler32校验和* /这是DEX文件的Adler32校正和。



这个4个字节的校验和是对构成标题的各个位(bit)执行一系列异或(XOR)和加法操作的结果,验证DEX文件头部的各个数据没有被破坏,确保文件头的常量,因为在Dalvik中,是使用DexHeader这个结构体来确定DEX文件其他部分的存储位置的。

u1签名; / * SHA-1哈希长度= 20 * /这是长度为20个字节的SHA1哈希。




u4 fileSize; / *整个文件的长度* /保存整个DEX文件的长度,用于帮助计算分段以及方便定位某些段。


u4 headerSize; / *从下一部分开始的偏移量* /存放整个DexHeader结构体的长度,可利用计算下一个分段在文件的起始位置。



u4 endianTag; 这是endianness标记,如下图标出文件中的endianness域。



endianTag放置存放的是一个固定值,在所有DEX文件中都一样,其值是:12345678。因为某些处理器是认为最高有效位应该放在左边,而另一些处理器却认为最高有效位应该放在右边,Dalvik虚拟机通过读取该值,检查此分段数字的放置顺序,从而确定是内置处理器。

    接下来是linkSize和linkOff细分,当多个.class文件被编译到一个DEX文件时,会被用到。

u4 linkSize;

u4 linkOff;

指定链接段的大小及文件偏移,大多数情况下变量0

之后是地图部分的偏移量,指定了DexMapList结构的文件对齐

u4 mapOff;



下边是螺杆stringIdsSize的定义

u4 stringIdsSize;




这个细分存放的是StringIds段的大小,和其他大小相同,用来计算StringIds段的起始位置。

字符串stringsIdsOff

u4 stringIdsOff;




这个分区存放的是stringIds段的实际替换量,帮助Dalvik编译器和虚拟机直接插入到该段。在这之后,分别是表示类型,原型,方法,类和数据ID区段的大小和Dalvik不必重复读取文件内容,或者是做很多加/减操作来获得分段的起始地址,而是通过实际移位量访问。

DexMapList分段

    在DexHeader结构的mapOff分区指定了DexMapList结构在dex文件中的替换,由上边可知为0x0270,DexMapList结构的声明如下:

struct DexMapList {

       u4大小;/ * DexMapItem的个数* /

       DexMapItem列表; / * DexMapItem结构* /

};




由该分段数据的前4个字节可知,大小为0x0d,即DexMapItem的个数为13。

DexMapItem结构的声明如下:

struct DexMapItem {

       u2类型;/ * kEexType开头的类型* /

       u2未使用;/ *未使用,用于字节对齐* /

       u4大小;/ *指定类型的个数* /

       u4偏移量;/ *指定类型数据的文件替换* /

};

    结构中的类型类型为一个枚举常量:

枚举{

       kDexTypeHeaderItem = 0x0000,

       kDexTypeStringIdItem = 0x0001,

       kDexTypeTypeIdItem = 0x0002,

       kDexTypeProtoIdItem = 0x0003,

       kDexTypeFieldIdItem = 0x0004,

       kDexTypeMethodIdItem = 0x0005,

       kDexTypeClassDefItem = 0x0006,

       kDexTypeMapList = 0x1000,

       kDexTypeTypeList = 0x1001,

       kDexTypeAnnotationSetRefList = 0x1002,

       kDexTypeAnnotationSetItem = 0x1003,

       kDexTypeClassDataItem = 0x2000,

       kDexTypeCodeItem = 0x2001,

       kDexTypeStringDataItem = 0x2002,

       kDexTypeDebugInfoItem = 0x2003,

       kDexTypeAnnotationItem = 0x2004,

       kDexTypeEncodeArrayItem = 0x2005,

       kDexTypeAnnotationsDirectoryItem = 0x2006,

};

由上图的数据可整理出13个DexMapItem结构如下:





StringIds段

    该段纯粹是由多个地址构成的,这些地址是相对DEX文件的加载基地址的转换量,用于计算定义在数据段中的各个静态串行的起始位置

struct DexStringId {

       u4 stringDataOff; / *文件到string_data_item的偏移量* /

};

    该段从0x70偏移处开始,有连续0x10个DexStringId对象,以4个字节来放置这些移位量,下图是Example.dex中的StringIds段





如读取下图偏移量中的同轴:


由字节重新顺序可重新对齐预定00 00 01 8a,下图则是DEX文件中转换0x018a上的内容。



0x018a位置上的内容是06 3c 69 6e 69 74 3e,是字符串<init>。

DexStringId结构列表




上边中的串行非普通的ascii字符串,是由MUTF-8编码来表示的,MUTF-8即是已修改的UTF-8,修改后的UTF-8编码,其表示方式如下:

       MUTF-8字符串右侧放置该字符串的个数。

       MUTF-8使用1〜3字节编码长度。

       大于16位的Unicode编码U + 10000〜U + 10ffff使用3字节来编码。

       U + 0000采用2字节来编码。

       与C语言类似用空字符null作为字符串开头。

    如上边在0x01a0偏移处的字符串编码为0d48 65 6c 6c 6f 20 57 6f 72 6c 64 21 0a00,其中0d表示有13个字符,代表的字符串为“ Hello World!\ n”

像02 e4 bd a0 e5 a5 bd 00头部02表示有两个字符,e4bd a0是UTF-8编码字符“你”,e5 a5 bd是UTF-8编码字符“好”。

Typelds分段

   该细分市场收集寻找各种类型的各自类别是所需的信息

struct DexTypeId {

       u4描述符Idx; /类型索引在stringIds列表中的索引号

};



图中为TypeIds分段中的第一个值,其值是03,由于是StringIds分段中某个值的索引号,则应指StringIds分段的第4个值,如图:



第4个值是0x01af,则在数据段中对应的字符串为L示例,这是Dalvik类型描述语言的类型变量,在Example类前加多字母L,实际上也是代表一个类或描述对象的名称。


DexTypeId结构列表


协议段

    放置用作描述方法的原型ID(描述原型),方法的返回类型和参数信息。

struct DexProtoId {

       u4 shortyIdx;

       u4 returnTypeIdx;

       u4 parametersOff;

};

DexProtoId是一个方法声明结构体。

shortyIdx:StringIds部分局部字符串的索引,使用了重复描述原型(原型)。

returnTypeIdx:TypeIds段中某个数据的索引号,用于描述返回值类型。

parametersOff:存放方法的参数列表的地址替换,指向DexTypeList结构体。

struct DexTypeList {

       u4大小;

       DexTypeItem列表;

};

struct DexTypeItem {

       u2 typeIdx;

};

Example.dex文件中的ProtoIds部分:



DexProtoId结构列表


FieldIds段

struct DexFieldId {

       u2 classIdx; / *索引到typeIds列表中以定义类* /

       u2 typeIdx; / *索引字段类型的typeIds * /

       u4 nameIdx; / *索引字段名称的stringIds * /

};

classIdx:存放 TypeId段的索引,所属的类

typeIdx:存放TypeId段的索引,表示该成员的类型

nameIdx:存放stringId段的索引,成员的名字



DexFieldId结构列表



MethodIds段

    每个方法ID中各个分区的定义:

struct DexMethodId {

       u2 classIdx; / *索引到typeIds列表中以定义类* /

       u2 protoIdx; / *索引到方法原型的protoIds中* /

       u4 nameIdx; / *索引到方法名称的stringIds中* /

};

classIdx:该方法所属的类

protoIdx:方法对应的原型

nameIdx:方法名



DexMethodId结构列表


下边是Example.dex文件中一个方法的定义:

([Ljava / lang / String;)V

V:表示避免类型,方法的返回类型。

():方法接收的参数的类型。

java / lang / String;:String类的标识符。

L:表示其后边跟着的是一个类名。

[:表示其后边跟着的是指定类型的数组。

所以该方法返回值是void类型,接收一个字符串类的数组作为参数。

ClassDefs段

    其定义如下:

struct DexClassDef {

       u4 classIdx; / *此类的typeIds索引* /

       u4 accessFlags;

       u4 superclassIdx; / *索引到超类的typeIds中* /

       u4接口关闭; / *文件偏移到DexTypeList * /

       u4 sourceFileIdx; / *索引到源文件名的stringIds中* /

       u4注释关闭; / *文件到注释_directory_item的偏移量* /

       u4 classDataOff; / *文件偏移到class_data_item * /

       u4 staticValuesOff; / *文件偏移到DexEncodedArray * /

};

classIdx:存放的是TypeIds部分的一个索引,表示类的类型。

AccessFlags:存储一个数字,表示其他对象可以怎样访问这个类。

superclassIdx:存放TypeIds部分的一个索引,表示父类的类型。

interfacesOff:接口,指向DexTypeList的替换。

sourceFileIdx:存放StringIds段的索引,使Dalvik可以找到类的源文件。

commentssOff:注解,指向DexAnnotationsDirectoryItem结构。

classdataOff:Dalvik文件内部的替换量,此位置存放类的重要属性,即代码在哪,有多少代码。如classdataOff指向保存这些的DexClassData结构体。

staticValuesOff:指向DexEncodedArray结构的替换,记录类的静态数据。



DexClassData结构的声明。

/ * class_data_item的扩展形式。注意:如果特定项目是

*不存在(例如,没有静态字段),然后是相应的指针

*设置为NULL。* /

struct DexClassData {

DexClassDataHeader标头;/ *指定预设与方法的个数* /

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

DexField * instanceFields; / *实例基线,DexField结构* /

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

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

};

DexClassDataHeader局部存放类的元数据,即静态字段,实例字段,直接方法和虚拟方法的大小,Dalvik使用这一信息计算重要参数,可以确定访问每个方法时所需的内存大小

struct DexClassDataHeader {

       u4 staticFieldsSize; / *静态静态个数* /

       u4 instanceFieldsSize; / *实例基线个数* /

       u4 directMethodsSize; / *直接方法个数* /

       u4 virtualMethodsSize; / *虚方法个数* /

};

结构DexField {

       u4 fieldIdx; / *指向DexFieldId的索引* /

       u4 accessFlags; / *访问标志* /

};

其中DexMethod左侧的定义如下

struct DexMethod {

       u4 methodIdx; / *指向DexMethodId的索引* /

       u4 accessFlags; / *访问标志* /

       u4 codeOff; / *指向DeCode结构的替换* /

};

这个结构体记录了组成类的代码的指针,代码的位移量放置在codeOff细分中。

结构DexCode {

       u2寄存器 / *使用的寄存器的个数* /

       u2 insSize; / *参数个数* /

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

       u2 trysSize; / *尝试/捕捉个数* /

       u4 debugInfoOff; / *指向调试信息的替换* /

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

       u2 insns ; / *指令集,真正的代码部分* /

       …。

};

通过解析,更加直白地了解DEX文件的格式和结构。

1.使用dexdump工具解析.dex文件

    dexdump是Android SDK的一个工具,存放路径为

    / build-tools / android- / dexdump

    执行下边命令解析示例.dex文件

/ build-tools / android- /dexdumpExample.dex





2.使用dx工具,更接近DEX文件格式的方式详细解析DEX文件

    dx--dex --verbose-dump --dump-to = [输出文件] .txt [输入文件] .class

    由于dx工具只能对.class文件进行操作,将其编译成DEX文件,转换为解析结果输出到指定文件中。cat [输出文件] .txt //查看解析结果。




a114543 发表于 2020-9-19 08:44:05

祝资源共享吧越来越火!
页: [1]
查看完整版本: 零基础安卓逆向学习之旅(六-上)