您的位置:首页 > 其它

安卓逆向系列教程(二)APK 和 DEX

2017-01-27 20:09 218 查看

安卓逆向系列教程(二)APK 和 DEX

作者:飞龙

APK

APK 是 Android 软件包的分发格式,它本身是个 Zip 压缩包。APK 根目录下可能出现的目录和文件有:

名称用途
META-INF
存放元数据
AndroidManifest.xml
编译后的全局配置文件
assets
存放资源文件,不会编译
classes.dex
编译并打包后的源代码
lib
存放二进制共享库,含有
armeabi-*
mips
x86
等文件夹,对应具体的平台
res
存放资源文件
resources.arsc
编译并打包后的
res/values
中的文件

res

res 中可能出现的目录如下:

名称用途
anim
存放编译后的动画 XML 文件(
<XXXAnimation>
color
存放编译后的选择器 XML 文件(
<selector>
drawable-*
存放图片,
*
为不同分辨率,图片按照不同分辨率归类。其中带
.9
的图片为可拉伸的图片。
layout
存放编译后的布局 XML 文件(
<XXXLayout>
menu
存放编译后的菜单 XML 文件(
<menu>
mipmap-*
存放使用 mipmap 技术加速的图片,一般用来存放应用图标,其它同
drawable-*
raw
存放资源文件,不会编译,比如音乐、视频、纯文本等
xml
存放编译后的自定义 XML 文件

resources.arsc

在 APK 中是找不到
res/values
这个目录的,因为它里面的文件编译后打包成了
resources.arsc
。为了理解它,我们先看一看原始的
res/values


res/values
中保存资源 XML 文件,根节点为
<resources>
。一般可能会出现以下几种文件:

名称用途
arrays.xml
存放整数数组和字符串数组,使用
<integer-array>
<string-array>
定义,元素使用
<item>
定义
bools.xml
存放布尔值,使用
<bool>
定义
colors.xml
存放颜色,使用
<color>
定义
dimens.xml
存放尺寸,使用
<dimen>
定义
drawables.xml
存放颜色,使用
<drawable>
定义
ids.xml
存放 ID,使用
<item type="id">
定义
integers.xml
存放整数,使用
<integers>
定义
strings.xml
存放字符串,使用
<strings>
定义
styles.xml
存放颜色,使用
<style>
定义,元素使用
<item>
定义
res/values
中的文件名称是无所谓的,这些名称只是约定。也就是说,任何
res/values
中的文件中的字符串都会出现在
R.strings
里面。

虽然我们在 APK 中无法直接看到这些文件,但是反编译之后就可以了。反编译之后,我们也会找到一个
public.xml
文件,是
res
里所有东西的索引:

<resources>
<public type="drawable" name="ic_launcher" id="0x7f020000" />
<public type="layout" name="activity_main" id="0x7f030000" />
<public type="layout" name="activity_sub" id="0x7f030001" />
<public type="dimen" name="activity_horizontal_margin" id="0x7f040000" />
<public type="dimen" name="activity_vertical_margin" id="0x7f040001" />
<public type="string" name="action_settings" id="0x7f050000" />
<public type="string" name="app_name" id="0x7f050001" />
<public type="string" name="hello_world" id="0x7f050002" />
<public type="string" name="title_activity_sub" id="0x7f050003" />
<public type="style" name="AppTheme" id="0x7f060000" />
<public type="menu" name="main" id="0x7f070000" />
<public type="menu" name="sub" id="0x7f070001" />
<public type="id" name="button1" id="0x7f080000" />
<public type="id" name="action_settings" id="0x7f080001" />
</resources>


DEX

DEX 即 Dalvik Executable,Dalvik 可执行文件。它的结构如下:

struct DexFile{
DexHeader    Header;
DexStringId  StringIds[stringIdsSize];
DexTypeId    TypeIds[typeIdsSize];
DexFieldId   FieldIds[fieldIdsSize];
DexMethodId  MethodIds[methodIdsSize];
DexProtoId   ProtoIds[protoIdsSize];
DexClassDef  ClassDefs[classDefsSize];
DexData      Data;
DexLink      LinkData;
};


我们可以看到,它可以分为九个区段,如下:

Header
StringIds
TypeIds
FieldIds
MethodIds
ProtoIds
ClassDefs
Data
LinkData
大体结构如这张图所示:



另外,在讲解各个区段之前,需要首先了解一些数据类型的定义:

类型定义
u1
等同于
uint8_t
,表示 1 字节的无符号数
u2
等同于
uint16_t
,表示 2 字节的无符号数
u4
等同于
uint32_t
,表示 4 字节的无符号数
u8
等同于
uint64_t
,表示 8 字节的无符号数

Header 区段

Header 区段用于储存版本标识、校验和、文件大小、各部分的大小及偏移。结构以及描述如下:

struct DexHeader {
u1  magic[8];           /* 版本标识 */
u4  checksum;           /* adler32 检验和 */
u1  signature[kSHA1DigestLen]; /* SHA-1 哈希值 */
u4  fileSize;           /* 整个文件大小 */
u4  headerSize;         /* Header 区段大小 */
u4  endianTag;          /* 字节序标记 */
u4  linkSize;           /* 链接区段大小 */
u4  linkOff;            /* 链接区段偏移 */
u4  mapOff;             /* MapList 的偏移 */
u4  stringIdsSize;      /* StringId 的个数 */
u4  stringIdsOff;       /* StringIds 区段偏移 */
u4  typeIdsSize;        /* TypeId 的个数 */
u4  typeIdsOff;         /* TypeIds 区段偏移 */
u4  protoIdsSize;       /* ProtoId 的个数 */
u4  protoIdsOff;        /* ProtoIds 区段偏移 */
u4  fieldIdsSize;       /* FieldId 的个数 */
u4  fieldIdsOff;        /* FieldIds 区段偏移 */
u4  methodIdsSize;      /* MethodId 的个数 */
u4  methodIdsOff;       /* MethodIds 区段偏移 */
u4  classDefsSize;      /* ClassDef 的个数 */
u4  classDefsOff;       /* ClassDefs 区段偏移 */
u4  dataSize;           /* 数据区段的大小 */
u4  dataOff;            /* 数据区段的文件偏移 */
};


有几个条目需要特别提醒。

magic
:必须为
DEX_FILE_MAGIC


ubyte[8] DEX_FILE_MAGIC = { 0x64 0x65 0x78 0x0a 0x30 0x33 0x35 0x00 }
= "dex\n035\0";


checksum
:是整个文件除去它本身以及魔数的校验和。

signature
:是整个文件除去它本身、校验和以及魔数的哈希值。

headerSize
:一般为 70。

endianTag
:有两种顺序,小端和大端,定义如下:

uint ENDIAN_CONSTANT = 0x12345678; /* 小端序 */
uint REVERSE_ENDIAN_CONSTANT = 0x78563412; /* 大端序 */


一般为小端序,反正我还没见过大端的。

stringIdsOff
:由于前一个区段的偏移加上它的长度一般为后一个区段的偏移,所以这个条目一般也为 70。

xxxSize
:要注意有几个是个数,后缀也是
Size


xxxOff
:如果对应的
xxxSize
为 0,那么它也为 0(很奇怪)。

StringIds 区段

StringIds 区段包含
stringIdsSize
DexStringId
结构,如下:

struct DexStringId {
u4 stringDataOff;   /* 字符串内容,字符串数据偏移 */
};


其中数据偏移指向 Data 区段的字符串数据。

TypeIds 区段

TypeIds 包含
typeIdsSize
DexTypeId
结构,如下:

struct DexTypeId {
u4 descriptorIdx;    /* 类型的完全限定符,指向 DexStringId 列表的索引 */
};


索引是一个从 0 开始的数字,表示对应第几个
DexStringId
。这些
DexStringId
指向的字符串都是类型名称,比如
I
Ljava/lang/String;
之类的。
DexTypeId
的索引也会用于后面的结构。

ProtoIds 区段

ProtoIds 包含
ProtoIdsSize
DexProtoId
结构。这里的 Proto 指方法原型,包含返回类型和参数类型。

struct DexProtoId {
u4 shortyIdx;       /* 原型缩写,指向 DexStringId 列表的索引 */
u4 returnTypeIdx;   /* 返回类型,指向 DexTypeId 列表的索引 */
u4 parametersOff;   /* 参数类型列表,指向 DexTypeList 的偏移 */
};

struct DexTypeList {
u4 size;             /* 接下来 DexTypeItem 的个数 */
DexTypeItem list[size]; /* DexTypeItem 结构 */
};

struct DexTypeItem {
u2 typeIdx;    /* 参数类型,指向 DexTypeId 列表的索引 */
};


原型缩写是把所有返回类型和参数类型的名称拼在一起,对象的话只写
L
。比如
int(int,int)
写为
III
void()
写为
V
void(String)
写为
VL


参数类型列表一般保存在
Data
区段中,如果没有,
parametersOff
为 0。

FieldIds 区段

TypeIds 包含
fieldIdsSize
DexFieldId
结构,如下:

struct DexFieldId {
u2 classIdx;   /* 类的类型,指向 DexTypeId 列表的索引 */
u2 typeIdx;    /* 字段类型,指向 DexTypeId 列表的索引 */
u4 nameIdx;    /* 字段名称,指向 DexStringId 列表的索引 */
};


MethodIds 区段

MethodIds 包含
methodIdsSize
DexMethodId
结构,如下:

struct DexMethodId {
u2 classIdx;  /* 类的类型,指向 DexTypeId 列表的索引 */
u2 protoIdx;  /* 方法原型,指向 DexProtoId 列表的索引 */
u4 nameIdx;   /* 方法名称,指向 DexStringId 列表的索引 */
};


ClassDefs 区段

ClassDefs 包含
classDefsSize
DexClassDef
结构,如下:

struct DexClassDef {
u4 classIdx;         /* 类的类型,指向 DexTypeId 列表的索引 */
u4 accessFlags;      /* 访问标志 */
u4 superclassIdx;    /* 父类类型,指向 DexTypeId列表的索引 */
u4 interfacesOff;    /* 接口,指向 DexTypeList 的偏移 */
u4 sourceFileIdx;    /* 源文件名,指向 DexStringId 列表的索引 */
u4 annotationsOff;   /* 注解,指向 DexAnnotationsDirectoryItem 结构 */
u4 classDataOff;     /* 指向 DexClassData 结构的偏移 */
u4 staticValuesOff;  /* 指向 DexEncodedArray 结构的偏移 */
};

struct DexClassData {
DexClassDataHeader header;                    /* 各个字段与方法的个数 */
DexField staticFields[staticFieldsSize];      /* 静态字段 */
DexField instanceFields[instanceFieldsSize];  /* 实例字段 */
DexMethod directMethods[directMethodsSize];   /* 直接方法 */
DexMethod virtualMethods[virtualMethodsSize]; /* 虚方法 */
};

struct DexClassDataHeader {
u4 staticFieldsSize;   /* 静态字段个数 */
u4 instanceFieldsSize; /* 实例字段个数 */
u4 directMethodsSize;  /* 直接方法个数 */
u4 virtualMethodsSize; /* 虚方法个数 */
};

struct DexField {
u4 fieldIdx;    /* 指向 DexFieldId 的索引 */
u4 accessFlags; /* 访问标志 */
};

struct DexMethod {
u4 methodIdx;   /* 指向 DexMethodId 的索引 */
u4 accessFlags; /* 访问标志 */
u4 codeOff;     /* 方法指令,指向DexCode结构的偏移 */
};

struct DexCode {
u2 registersSize;    /* 使用的寄存器个数 */
u2 insSize;          /* 参数个数 */
u2 outsSize;         /* 调用其他方法时使用的寄存器个数 */
u2 triesSize;        /* Try/Catch个数 */
u4 debugInfoOff;     /* 指向调试信息的偏移 */
u4 insnsSize;        /* 指令集个数,以2字节为单位 */
u2 insns[insnsSize]; /* 指令集 */
};


DexClassData
DexCode
保存在 Data 区段中。

Data 区段

这个区段中除了存放二级结构和字符串,还有个重要的结构叫做
DexMapList
,它实际上 DEX 中所有东西的索引,包括各种二级结构、字符串和它本身。DEX 中同类结构都会保存在一起,所以一类结构只占用一个条目。

struct DexMapList {
u4 size;                  /* 条目个数 */
DexMapItem list[size];    /* 条目列表 */
};

struct DexMapItem {
u2 type;      /* 结构类型,kDexType 开头 */
u2 unused;    /* 未使用,用于字节对齐 */
u4 size;      /* 连续存放的结构个数 */
u4 offset;    /* 结构的偏移 */
};

/* 结构类型代码 */
enum {
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,
kDexTypeEncodedArrayItem         = 0x2005,
kDexTypeAnnotationsDirectoryItem = 0x2006,
};


参考

Android Dex文件结构解析

Dalvik Executable Format

DEX 结构图解
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: