逆向编程-解析应用资源文件

了解应用资源文件的格式,有助于我们对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

操作步骤如下:

  1. Templates -> Open Template -> 选中ARSCTemplate.bt模板文件(该文件定义了srsc文件的chunk格式)
  2. 修改apk文件后缀为zip,解压文件,找到resources.arsc 。
  3. File -> open File -> 选中resources.arsc文件。
  4. 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;
  1. id对应type spec 的id.表示描述某个类型的资源。
  2. entry Count 表示该类型的资源个数。对应type spec表的 entryCount.
  3. entriesStart 指定这些具体资源的起始位置,它给出的是偏移量,需要通过获取当前块的首地址,再经过计算才能确定位置。
  4. resTable_config是对资源的国际化等等属性描述。这里不做过多了解。
  5. entryOffset[] 当我们获取整体资源的起始位置后,该数组可以相对的获取每个资源的物理地址。

6. 元素描述

每个元素的描述由restable_entryres_value一起表示。并且针对不同的资源采用了2种表示方式,下图对元素做了一个简单的讲解:

这里写图片描述

这里写图片描述

猜你喜欢

转载自blog.csdn.net/qq285016127/article/details/80073184