前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Android逆向:resource.arsc文件解析(Config List)

Android逆向:resource.arsc文件解析(Config List)

作者头像
BennuCTech
发布2021-12-10 14:52:11
2.4K0
发布2021-12-10 14:52:11
举报
文章被收录于专栏:BennuCTechBennuCTech

resource.arsc是APK打包过程中生成一个重要的文件,主要存储了整个应用哦中的资源索引。但是这个文件是一个二进制文件,并不可读,所以本文就通过解析它的二进制内容来读懂这个文件。

Resource.arsc结果

我们先来看resource.arsc的整体结构,如图:

这张图的原型是网上的一张神图,但是由于神图年代久远,结构表现的不够清晰,而且比较旧了,缺少了一些新的东西,所以我根据神图自己又重新整理了一张架构图,其中新的东西是指Dynamic package reference。

整体结构可以看到是由Header、全局字符串池和package列表组成。所以这里面package列表就是主要内容,它是由一个个package结构组成的。

在package结构下可以看到同样可以大致分为Header、字符串池(包括资源类型字符串池和资源名称字符串池两个)和Type Spec-Config List列表。

Type Spec(类型规范数据块)和Config List是资源索引表中最重要的部分。这一部分也是同一个资源ID在不同配置下,找到不同资源文件的关键。

该部分的整体结构以资源类型Type分段,每段的数据结构相似,都是以ResTable_typeSpec开头,后面紧跟着一个spec数组,和Config List。

Config List就是我们今天研究的关键,这里面包含一些列的RES_TABLE_TYPE_TYPE结构的数据。

RES_TABLE_TYPE_TYPE

Config List包含若干段数据结构,每段结构都是一个ResTable_type之后紧跟着ResTable_entry偏移数组和若干ResTable_entry。

示例如下:

00000000 00000000 01024C00 500B0000 08000000 8D000000 80020000 38000000

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000 00000000 00000000 10000000 20000000

30000000 40000000 50000000 60000000 70000000 80000000 90000000 A0000000

B0000000 C0000000 D0000000 E0000000 F0000000 00010000 10010000 20010000

...

50080000 60080000 70080000 80080000 90080000 A0080000 B0080000 C0080000

08000000 5F020000 08000005 01300000 08000000 60020000 08000005 01380000

08000000 61020000 08000005 01280000 08000000 62020000 08000005 01100000

...

08000000 86020000 08000005 01240000 08000000 87020000 08000001 2E00087F

注意ResTable_entry有两种类型:bag和非bag。示例中仅仅是非bag类型,bag类型后面会细说。

下面我们根据示例来一点点讲解ResTable_type的结构。

RES_TABLE_TYPE

首先是ResTable_type,结构如下:

代码语言:javascript
复制
struct ResTable_type  
 {  
     struct ResChunk_header header;  

     enum {  
         NO_ENTRY = 0xFFFFFFFF  
     };  
     //标识资源的Type ID  
     uint8_t id;  
     //保留,始终为0  
     uint8_t res0;  
     //保留,始终为0  
     uint16_t res1;  
     //等于本类型的资源项个数,指名称相同的资源项的个数。
     uint32_t entryCount;  
     //等于资源项数据块相对头部的偏移值。
     uint32_t entriesStart;  
     //指向一个ResTable_config,用来描述配置信息,地区,语言,分辨率等  
     ResTable_config config;  
 };

其中ResChunk_header的结构如下:

代码语言:javascript
复制
struct ResChunk_header  
 {  
     enum   
     {  
         RES_NULL_TYPE               = 0x0000,  
         RES_STRING_POOL_TYPE        = 0x0001,  
         RES_TABLE_TYPE              = 0x0002,  
         RES_XML_TYPE                = 0x0003,  
         RES_XML_FIRST_CHUNK_TYPE    = 0x0100,  
         RES_XML_START_NAMESPACE_TYPE= 0x0100,  
         RES_XML_END_NAMESPACE_TYPE  = 0x0101,  
         RES_XML_START_ELEMENT_TYPE  = 0x0102,  
         RES_XML_END_ELEMENT_TYPE    = 0x0103,  
         RES_XML_CDATA_TYPE          = 0x0104,  
         RES_XML_LAST_CHUNK_TYPE     = 0x017f,  
         RES_XML_RESOURCE_MAP_TYPE   = 0x0180,  
         RES_TABLE_PACKAGE_TYPE      = 0x0200,  
         RES_TABLE_TYPE_TYPE         = 0x0201,  
         RES_TABLE_TYPE_SPEC_TYPE    = 0x0202  
     };  
     //当前这个chunk的类型  
     uint16_t type;  
     //当前这个chunk的头部大小  
     uint16_t headerSize;  
     //当前这个chunk的大小  
     uint32_t size;  
 };

ResTable_type就是上面橙色的部分,如下:

01024C00 500B0000 08000000 8D000000 80020000 38000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

我们一点一点来解读一下

01024C00 500B0000 是header,其中type是0x201(字序),即RES_TABLE_TYPE_TYPE;头部大小是0x4c,也就是76个;整个chunk大小是0xb50

08000000 是id+res0+res1,所以资源的TypeId是0x08(在示例中是dimen类型的资源,所以是非bag,后面会讲)

8D000000 是entryCount,即资源个数为141(注意字序)

80020000 是entriesStart,即资源数据块相对于头部的偏移量,即偏移0x280(字序)。例子中ResTable_type的大小为76byte;资源个数为141个,所以后面偏移数组有141个,每个偏移量占4byte,则是564byte。这样到资源数据部分正好有640byte,即0x280。偏移数组后面会讲

剩下的就是config了,其中38000000是config的数量,即56个,加上38000000也正好有56byte

ResTable_entry偏移数组

在ResTable_type紧跟着是ResTable_entry偏移数组,即绿色部分,每4byte为一个偏移量(相对于资源数据块起始位置的偏移,本例子即头部偏移0x280后的位置),一共有entryCount个即141个偏移量。

这块没必要要详细解释。但是我们注意到例子中这部分偏移量是16byte递增的,说明后面的资源数据块每块是16byte,这个后面我们可以验证。

ResTable_entry(非bag)

“ResTable_entry偏移数组”后面就是一系列资源数据块(ResTable_entry),即蓝色部分

数据块分两种,bag和非bag。bag就是有一系列属性的,比如attr、style等;非bag就是单个的,比如color、dimen等。

数据块是由ResTable_entry和一个或多个Res_value组成。

非bag的数据块是由ResTable_entry和一个Res_value组成;bag数据块则由ResTable_map_entry(继承自ResTable_entry)和一个或多个Res_value组成。

这里先看非bag数据块,示例中就是非bag数据块。数据结构如下:

代码语言:javascript
复制
struct ResTable_entry  
 {  
     //表示资源项头部大小。
     uint16_t size;  

     enum {  
         //如果flags此位为1,则ResTable_entry后跟随ResTable_map数组,为0则跟随一个Res_value。
         FLAG_COMPLEX = 0x0001,  
         //如果此位为1,这个一个被引用的资源项  
         FLAG_PUBLIC = 0x0002  
     };  
     //资源项标志位  
     uint16_t flags;  
     //资源项名称在资源项名称字符串资源池的索引  
     struct ResStringPool_ref key;  
 };

通过上面我们知道示例中每个数据块都是16byte,我们拿其中一个来举例:

08000000 87020000 08000001 2E00087F

前8byte就是ResTable_entry,后8byte是Res_value(后面再讲),一点点细说

0800 是ResTable_entry大小,即8byte

0000 是flag,0x01(字序)表示是bag,0x00则是非bag

87020000 是该资源项对应的字符串资源的索引

Res_value

ResTable_entry后面跟着就是Res_value,它的结构如下:

代码语言:javascript
复制
struct Res_value  
 {  
     //Res_value头部大小  
     uint16_t size;  
     //保留,始终为0  
     uint8_t res0;  

     enum {  
         TYPE_NULL = 0x00,  
         TYPE_REFERENCE = 0x01,  
         TYPE_ATTRIBUTE = 0x02,  
         TYPE_STRING = 0x03,  
         TYPE_FLOAT = 0x04,  
         TYPE_DIMENSION = 0x05,  
         TYPE_FRACTION = 0x06,  
         TYPE_FIRST_INT = 0x10,  
         TYPE_INT_DEC = 0x10,  
         TYPE_INT_HEX = 0x11,  
         TYPE_INT_BOOLEAN = 0x12,  
         TYPE_FIRST_COLOR_INT = 0x1c,  
         TYPE_INT_COLOR_ARGB8 = 0x1c,  
         TYPE_INT_COLOR_ARGB8 = 0x1c,  
         TYPE_INT_COLOR_RGB8 = 0x1d,  
         TYPE_INT_COLOR_ARGB4 = 0x1e,  
         TYPE_INT_COLOR_RGB4 = 0x1f,  
         TYPE_LAST_COLOR_INT = 0x1f,  
         TYPE_LAST_INT = 0x1f  
     };  
     //数据的类型,可以从上面的枚举类型中获取  
     uint8_t dataType;  

     //数据对应的索引  
     uint32_t data;  
 };

还用上面的例子来说,如下:

08000000 87020000 08000001 2E00087F

前8byte就是ResTable_entry(前面说过了),后8byte就是Res_value,一点点细说

  • 0800 是Res_value头部大小,非bag就是Res_value的大小,即8byte
  • 00 是res0,固定值
  • 01 是dataType,01表示是引用,即后面的data是一个resId
  • 2E00087F 是数据索引,例子中是一个resId,即0x7F08002E,上面知道type08是dimen,所以这是一个dimen资源,并不是具体值。

最后说一下为什么不是具体值,这个资源的源码类似:

代码语言:javascript
复制
<dimen name="dimen1">@dimen/dimen2</dimen>

所以我们得到的是dimen2的索引,还需要通过这个索引再次在resource.arsc查询dimen2的值,直到查询到具体的值。

从上面的type中可以看到,当dataType是05的时候,后面就会是一个具体值了。

bag类型的RES_TABLE_TYPE_TYPE

上面的例子是非bag,下面我们说说bag的例子,示例如下:

00000000 00000000 01024C00 90420000 09000000 7B010000 38060000 38000000

00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000

00000000 00000000 00000000 00000000 00000000 00000000 34000000 5C000000

6C000000 7C000000 98000000 A8000000 B8000000 C8000000 D8000000 E8000000

...

4C3A0000 8C3A0000 B43A0000 D03A0000 343B0000 743B0000 903B0000 F43B0000

10000100 EA020000 C700097F 03000000 F300017F 08000005 01180000 F400017F

08000005 01030000 F700017F 08000005 01120000 10000100 EB020000 D200097D

02000000 3601017F 08000010 ...

与非bag相同的部分我们就简单说一下,重点讲不同的部分。

首先、ResTable_type即橙色部分:

  • 01024C00 90420000 是header,type同样是0x201
  • 0900000 是id+res0+res1,所以资源的TypeId是0x09(在示例中是style类型的资源,所以是bag)
  • 7B010000 有0x17b个ResTable_entry
  • 38060000 资源数据块相对与头部偏移0x638。头部大小为76;偏移数组0x17b个,每个4byte。加起来正好0x638
  • 后面是config,其中38000000是config的数量,即56个,加上38000000也正好有56byte

其次、是ResTable_entry偏移数组,即绿色部分,一共0x17b个,每个占用4byte,不细说了。

下面进入资源数据块ResTable_entry,因为是bag,所以数据块是ResTable_map_entry(继承自ResTable_entry),结构如下:

代码语言:javascript
复制
struct ResTable_map_entry : public ResTable_entry  
 {  
     //指向父ResTable_map_entry的资源ID,如果没有父ResTable_map_entry,则等于0。
     ResTable_ref parent;  
     //等于后面ResTable_map的数量  
     uint32_t count;  
 };

我们拿第一条举例:

10000100 EA020000 C700097F 03000000 F300017F 08000005 01180000 F400017F

08000005 01030000 F700017F 08000005 01120000

前16byte就是ResTable_map_entry,后面则是ResTable_map(后面再讲),一点点细说。

因为继承,所以首先还是ResTable_entry的结构

  • 1000 是ResTable_entry大小,即0x10(16)byte
  • 0100 是flag,0x01(字序)表示是bag,0x00则是非bag
  • EA020000 是该资源项对应的字符串资源的索引
  • C700097F 就是parent,因为例子是style,所以是这style的parent的resId,即0x7F0900C7
  • 03000000 是ResTable_map的数量,即0x03(字序)

下面就是ResTable_map,结构如下:

代码语言:javascript
复制
struct ResTable_map  
 {  
     //bag资源项ID  
     ResTable_ref name;  
     //bag资源项值  
     Res_value value;  
 };

例子中有三个,我们拿第一个举例:

F300017F 08000005 01180000

其中

  • F300017F 是bag的资源项ID,即0x7F0100F3,后面细说
  • 08000005 就是Res_value的type,根据上面可知是TYPE_DIMENSION
  • 01180000 就是Res_value的数据值(01代表dp。后面的是数值,考虑字序是0x18,即24,所以应该是24dp)

这样bag就解析完了,我们来看看这个数据块到底是什么:

代码语言:javascript
复制
<style name="Base.Widget.AppCompat.DrawerArrowToggle" parent="Base.Widget.AppCompat.DrawerArrowToggle.Common">
    <item name="barLength">18dp</item>
    <item name="gapBetweenBars">3dp</item>
    <item name="drawableSize">24dp</item>
</style>

所以parent是Base.Widget.AppCompat.DrawerArrowToggle.Common,在R.java中可以看到

代码语言:javascript
复制
public static final int Base_Widget_AppCompat_DrawerArrowToggle_Common=0x7f0900c7;

与我们解析是一样的。

这个style含有三个item,这与上面分析的也一样。

其中一个item是drawableSize,在在R.java中可以看到

代码语言:javascript
复制
public static final int drawableSize=0x7d0100f3;

和上面的bag的资源项id也对应上了。

而且drawableSize的值是18dp,与上面猜测的值相符。

8、总结

这样我们就啃下了resource.arsc中最复杂也是最核心的部分,一点点分析下来就觉得清晰了很多,当然还有一些小细节没有聊到,不过这不影响对整体结构的认知,如果有兴趣可以自己去研究一下。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-10-28,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BennuCTech 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档