了解应用资源文件的格式,有助于我们对apk文件的保护(修改resource.arsc 让apktool无法反编译),另一方面也可以通过资源的优化减少APK文件的大小。
arsc文件格式简介
我们知道APK文件是一个压缩文件,其文件结构为:
AndroidManifest.xml : 程序全局配置文件
**classes.dex :**Dalvik字节码
METE-INF:存放安装包签名文件的目录。
res :存放了二进制编译XML及图片资源文件的目录
resources.arsc :编译后的二进制资源文件,文件包含了二进制编译的String, style等资源。其二进制存储方式为小端编码方式(即低位在前,高位在后),整个文件的结构由一系列的chunk构成。
在一个apk中,对应chunck的数量一般有:
ResTable(1个)
ResStringPool(1个)
ResTable_Package(1~n个)
RES_TABLE_TYPE_SPEC(1~n个)
RES_TABLE_TYPE(1~n个)。
010eidt工具使用
使用010eidt工具可以通过模板方式分析Resources.arsc文件:
资源下载地址如下:链接:https://pan.baidu.com/s/1sV1J2VBehRji4OCCYpoF_Q 密码:ypbc
操作步骤如下:
- Templates -> Open Template -> 选中ARSCTemplate.bt模板文件(该文件定义了srsc文件的chunk格式)
- 修改apk文件后缀为zip,解压文件,找到resources.arsc 。
- File -> open File -> 选中resources.arsc文件。
- Templates -> Run Template / F5。
arsc文件解析
以helloworld.apk做为实例,帮助大家理解一下Resources.arsc文件的结构。Resources.arsc整体是由一系列的chunk构成。
每一个chunk均包含了相同结构的ResChunk_header,用来描述这个chunk的基本信息,如下图红色部分。
其定义的格式如下:
#define RES_STRING_POOL_TYPE 0x0001
#define RES_TABLE_TYPE 0x0002
#define RES_TABLE_PACKAGE_TYPE 0x0200
#define RES_TABLE_TYPE_TYPE 0x0201
#define RES_TABLE_TYPE_SPEC_TYPE 0x0202
//对于红色部分 它们的不同点在于type不同,如上定义了可能出现的type宏定义。
typedef struct {
ushort type; // Type identifier of the chunk
ushort headerSize; // Size of the chunk header
uint size; // Size of the entire chunk
} RESCHUNK_HEADER;
1. Resource Header 解析
上图为Resource Header的文件内容,定义了文件的主要的信息。由2部分组成,第一部分为RES_TABLE_TYPE,头大小,文件大小。第二部分为package count.其语义代码如下:
typedef struct {
RESCHUNK_HEADER header;
uint packageCount; // Number of ResTable structures
} RESTABLE_HEADER;
查看当前例子中Resource Header存储了什么内容:
02 00 位置0~ 1,共2字节,表示该chunk的类型,值为0x0002表示类型为RES_TABLE_TYPE.。
0C 00 位置2 ~ 3 ,共2字节,表示该chunk类型的头长度,值为0x000C表示该类型的头长度为12字节长度。
68950100 位置4~7,共4字节,表示该chunk的总长度,值为0x00019568表示该chunk的总长度(这里为整个文件的大小)为103784字节长度。
01 000000 被编译的资源包的个数,这里只有一个。
2. Global String Pool
上图为Global String Pool全局字符串资源的文件内容,这个字符串资源池包含了所有的在资源包里面所定义的资源项的全局字符串,包括android工程中部分资源文件名(
如res/drawable-hdpi/ic_launcher.png,res/layout/activity_main.xml等)及res/values/strings.xml中的字符串值(如helloworld,hello world,Settings)
由3部分组成,第一部分为RES_STRING_POOL_TYPE,头大小,块大小。第二部分为字符串数,style字符串数,flag字符串编码,字符串起始位置,style起始位置。第三部分为字符串、style偏移量。其语义代码如下:
typedef struct {
RESCHUNK_HEADER header;
uint stringCount; // Number of string uint indices
uint styleCount; // Number of span uint indices
uint flags;
uint stringsStart; // Index of the string data
uint stylesStart; // Index of the style data
...
} RESSTRINGPOOL_HEADER;
查看当前例子中Resource Header存储了什么内容:
01 00 位置0~ 1,共2字节,表示该chunk的类型,值为0x0001 表示类型为RES_STRING_POOL_TYPE.
1C 00 位置2 ~ 3 ,共2字节,表示该chunk类型的头长度,值为0x001C表示该类型的头长度为28字节长度。
A490000 位置4~7,共4字节,表示该chunk的总长度,值为0x000091A4表示该chunk的总长度为37284字节长度。
E1 03 00 00 位置0x08 ~ 0x0B,共4字节,表示字符串数量, 值为0x000003E1表示字符串数量为993个。
00000000 位置 0x0C ~0x0F,共4字节,表示字符串样式数量,值为0x00000000表示字符串样式数量为0个。
00 01 00 00 位置 0x10 ~ 0x13字符串的编码,共4字节,可取值包括0x000(UTF-16),0x001(字符串经过排序)、0X100(UTF-8)和他们的组合值。值为0x00000100表示字符串采用utf-8编码。
A00F 00 00 0x14 ~ 0x17共4字节,值为0x00000FA0表示字符串内容相对于RES_STRING_POOL头部的偏移为4000。实际的文件偏移地址为0x00000FA0+0x0C=0x0FAC。
如下图可以看出的RES_STRING_POOL头部位置为0x0C:
如下是第一个字符串的位置:
000000000 x18 ~ 0x1B共4字节,值为0表示字符串样式内容相对于RES_STRING_POOL头部的偏移为0。
除了上面的字符串头部,该部分还定义了每个字符串相对于字符串物理地址的偏移量。并且,每个字符串的定义的语义如下:
typedef struct (int _len) {
ubyte strcount;
ubyte bytecount;
char _string[_len-2];
} stringPool;
3. Package Header
默认情况下一个resource开发包只有一个package. 但是随着业务的复杂化,可以将相同的底层技术通过组件化的方式拆分。那么每个组件都是单独的module , 所有的业务组件被“app壳工程”依赖,组成一个完整的APP;
如上所述,每个module都有独立的 res. 这样完整的APP就可能会出现多个package.这里我们针对单package进行分析。
如下为Package Header结构图:
该图主要存储两种类型的字符串typeStrings和keyStrings
typeStrings资源类型字符串存储类型资源的类型字符串一般有animator、anim、color、drawable、layout、menu、raw、string和xml。
keyStrings(资源项名称字符串)一般储存资源文件XML内容的键名的名称字符串,以helloworld的strings.xml为例,键名“app_name”,”hello_world”, “action_settings”做为资源项名称字符串存储在该区域:
<resource>
<string name="app_name">helloworld</string>
<string name="hello_world">Hello world!</string>
<string name="app_settings">Settings</string>
</resource>
接下来看看其结构的语义定义:
typedef struct {
RESCHUNK_HEADER header;
uint32 id; // 包的ID,一般用户包的值Package Id为0X7F,系统资源包的Package Id为0X01。
wchar_t name[128]; // 包名
uint typeStrings; // 类型字符串资源池相对头部的偏移
uint lastPublicType; // 类型字符串资源池的元素个数
uint keyStrings; // 资源项名称字符串相对头部的偏移
uint lastPublicKey; // 资源项名称字符串资源池的元素个数
}RESTABLE_PACKAGE;
00 02 1C 01 B8 03 01 00 为ResTable_package的头ResChunk_header。
7F 00 0000 值 0x0000007f 为包的ID 127
63 00… 位置 0x91bc ~ 0x92bb为包名 com.example.helloworld
1C 0100 00 位置 0x9bc ~ 0x92bf 包的typeStrings(类型字符串资源池相对头部的偏移284)
实际物理地址计算:头部位置0x91B0+偏移0x11C = 0x92CC
0C 000000 typeStrings**(类型字符串资源池)的字符串个数为0x00000c (12)个**
C8 01 00 00 keyStrings(资源项名称字符串相对头部的偏移456)
实际物理地址计算:头部位置0x91B0+偏移0x1C8 = 0x9378
E0 01 00 00 keyStrings(资源项名称字符串资源池)的字符串元素个数。
ResTable_package包后是typeString和keyStrings的内容。
上面仅仅定义了两种类型的字符串存在的大体位置,接下来对每个具体的字符串存储细节进行拆分:
typeString内容如下:
- stringCount : 字符串数量为12
- flags : 字符串编码UTF-8
- stringStart : 字符串内容相对当前头部的偏移量76
- stringOffset : 字符串偏移量数组
- struct stringPool currentString : 存储的字符串内容,如上面截图中我们可以看到attr字符串
keyStrings内容如下:
- stringCount : 字符串数量为480
- flags : 字符串编码UTF-8
- stringStart : 字符串内容相对当前头部的偏移量1948
- stringOffset : 字符串偏移量数组
- struct stringPool currentString : 存储的字符串内容,如上面截图中我们可以看到windowActionBar字符串
4. Type Spec
Type Spec主要将整个系统将资源(animator、anim、color、drawable、layout、menu、raw、string和xml等)归为一个个的类型。并且分配了独立的ID.
其语义定义代码如下:
typedef struct {
RESCHUNK_HEADER header;
ubyte id; // 标识资源的Type ID,Type ID是指资源的类型ID。资源的类型有animator、anim、color、drawable、layout、menu、raw、string和xml等等若干种,每一种都会被赋予一个ID。
ubyte res0; // 0
uint16 res1; // 0
uint32 entryCount; // 名称相同的资源项的总个数
}RESTABLE_TYPESPEC;
整个resource包由多个Type Spec组成,如下图:
查看第一个TypeSpec(RESTABLE_TYPESPEC resTable_TypeSpec[0]),结构图如下:
02 02 10 00 C8 01 00 00 为ResTable_typeSpec的头ResChunk_header。
01 : id,是指资源的类型ID,对照的typeString的字符串值,我们可以知道该ID的指是attr
00 0000 6 EentryCount,共有0X6E个相同的名称类型ID。
ResTable_typeSpec后面紧跟着的是一个大小为entryCount的uint32_t数组文件位置 0xd628~0xd7df 为entry数组。
5.Config list
同一类型的资源共享同一个type spec。比如R.attr.windowActionBar和 R.attr.windowActionBarOverlay。他们的RESTABLE_TYPESPEC id是相同的。
RES_TABLE_TYPE_TYPE描述如下:
typedef struct {
RESCHUNK_HEADER header;
ubyte id;
ubyte res0;
uint16 res1;
uint32 entryCount; // Number of uint32_t entry indices that follow
uint32 entriesStart;// Offset from header where ResTable_entry data starts
RESTABLE_CONFIG resTable_config;
uint entryOffset[resTable_Type.entryCount];
}RESTABLE_TYPE;
- id对应type spec 的id.表示描述某个类型的资源。
- entry Count 表示该类型的资源个数。对应type spec表的 entryCount.
- entriesStart 指定这些具体资源的起始位置,它给出的是偏移量,需要通过获取当前块的首地址,再经过计算才能确定位置。
- resTable_config是对资源的国际化等等属性描述。这里不做过多了解。
- entryOffset[] 当我们获取整体资源的起始位置后,该数组可以相对的获取每个资源的物理地址。
6. 元素描述
每个元素的描述由restable_entry和res_value一起表示。并且针对不同的资源采用了2种表示方式,下图对元素做了一个简单的讲解: