安卓逆向系列教程(二)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 结构图解
相关文章推荐
- 安卓逆向系列教程 4.11 优酷 APK 去广告
- 安卓逆向系列教程 4.6 去广告
- 安卓反编译揭秘(爱加密系列教程十一)伪加密APK文件被破坏
- 安卓逆向系列教程 4.7 修改游戏金币
- 安卓逆向系列教程 4.13 MagSearch 1.8 爆破
- 安卓逆向系列教程 4.3 登山赛车内购破解
- 安卓逆向系列教程 4.10 玄奥八字
- 飞龙的安卓逆向系列教程
- 安卓逆向系列教程 4.8 去广告 II
- 安卓逆向系列教程 4.5 糖果星星达人
- 安卓逆向系列教程 4.1 字符串资源
- (爱加密系列教程十一) 关于某银行apk的安全分析
- (爱加密系列教程十五) 利用无效字节码指令引发逆向工具崩溃(二)
- 安卓逆向学习笔记(1) - 反编译classes.dex获取apk的java源代码
- 反编译android(安卓)apk文件_安卓的安装包(图文教程)
- 安卓反编译揭秘-爱加密系列教程二)
- 【Android SDK程序逆向分析与破解系列】之一:Android安装程序APK分析
- 安卓教程:提取APK程序里图片资源的方法
- cocos2d-x 3.0rc0系列教程--(2)编译安卓项目