resource.arsc二进制内容解析 之 Dynamic package reference

BennuCTech 已于 2022-07-15 17:11:01 修改

目录

1、加载Theme出错

2、aapt中的特殊处理

3、RES_TABLE_LIBRARY_TYPE

4、dynamicRefTable位置

5、验证dynamicRefTable

6、总结


 

1、加载Theme出错

这是一篇补充文章,在做动态替换resId的过程中,我发现bag类型的ResTable_entry在使用过程中存在问题。比如style,其parent解析一直有问题,日志如下:

经过一些粗暴的尝试,发现解决不了问题,看来需要祭出绝招了——看源码。

因为是与theme有关,所以我们追踪theme是怎么加载出来的,过程不说了,最后追踪到AssetManager.applyThemeStyle(),这个函数则是一个native函数,这就需要去底层代码看,源码:

android_frameworks_base/android_util_AssetManager.cpp at master · hushnymous/android_frameworks_base · GitHub

在源码里可以看到引用了ResourceType,这个源码:

https://github.com/aosp-mirror/platform_frameworks_base/blob/07735797a235ed98d182d0a40c8bdce4d92f9f0a/libs/androidfw/ResourceTypes.cpp

在ResourceType.cpp源码中通过搜索上面日志里的内容,我们找到了如下代码:

可以看到关键函数是lookupResourceId,如果这个函数返回的不是NO_ERROR就会报错。那么来看这个函数的源码:

先从resId中获取packageId,然后经过有三个if语句,我们一个个来看。

第一个,如果packageId是APP_PACKAGE_ID且不是lib,return NO_ERROR,这里APP_PACKAGE_ID是一个常量0x7F,因为我们动态替换成了0x7D,所以不执行这里。

第二个,如果packageId是0 或 packageId是APP_PACKAGE_ID且是lib,return NO_ERROR,这里重新组合了resId,将原packageId替换成了mAssignedPackageId,mAssignedPackageId即是resource.arsc文件中Package头部中的那个PackageId,我们已经动态替换成了0x7D。但是由于不满足if条件,所以这里也不执行。

第三个,从mLookupTable中获取一个translatedId,如果translatedId是0,则return UNKNOWN_ERROR;否则重新组合了resId,将原packageId替换成了translatedId。

 

当我们执行到这里,获取的translatedId一定是0,所以才会报出上面的错误。

这样我们找到原因所在,即mLookupTable中缺少translatedId的数据。

那么如何才能让数据正确?由于mLookupTable使用地方很多,而且还存储这其他数据,所以调查起来有些困难。

2、aapt中的特殊处理

这样就又走进死胡同了?我想到另外一个方案,即直接修改aapt源码来修改resId,那么那个方案既然可以,我们是不是可以看看差别在哪里?

那么方案很简单,修改代码也不多,没有看到什么特殊处理,那么一定是aapt源码中有一些处理。在aapt源码中搜索0x7F

(为什么要搜索0x7F,因为aapt一定是判断resId的packageId是不是0x7F,如果不是有一些特殊处理)

可以找到如下代码:

https://github.com/aosp-mirror/platform_frameworks_base/blob/master/tools/aapt/ResourceTable.cpp

可以看到当packageId不是0x7F,会新建一个Package结构存入libraryPackages,那么这个libraryPackages有啥用? 我们继续往下看发现flattenLibraryTable函数使用了它,这个函数源码如下:

可以看到向resource.arsc文件中写入了一个RES_TABLE_LIBRARY_TYPE类型的数据结构。

这个RES_TABLE_LIBRARY_TYPE在网上的resource.arsc解析相关文章中几乎没有被提到,而且网上的那张神图中也看不到这个结构。

 

3、RES_TABLE_LIBRARY_TYPE

那么它是什么?我们来看看找到的一些蛛丝马迹:

chunktypenote
Table headerRES_TABLE_TYPE = 0x002 
Resource stringsRES_STRING_POOL_TYPE = 0x001e.g. 'Hello World!'
Package headerRES_TABLE_PACKAGE_TYPE = 0x200Rewrite entry: package id
Type stringsRES_STRING_POOL_TYPE = 0x001e.g. 'attr', 'layout', etc.
Key stringsRES_STRING_POOL_TYPE = 0x001e.g. 'activity_main', etc.
DynamicRefTableRES_TABLE_LIBRARY_TYPE = 0x0203Insert entry for Android 5.0+
Type specRES_TABLE_TYPE_SPEC_TYPE = 0x0202 
Type infoRES_TABLE_TYPE_TYPE = 0x0201Rewrite entry: resource entry value

In Android 5.0+ needs to set the dynamicRefTable for lookup bag parent.

5.0以上需要对二进制文件加入“资源包id映射”数据段,以使得能正确查找到主题等bag的parent。

 

根据上面的我们大概知道这个“资源包id映射”是5.0之后才加入的,所以网上那么比较陈旧的文章中没有提及。它的作用就是正确的查找bag类型Entry数据的parent。

(至于什么是bag类型,请阅读《resource.arsc二进制内容解析 之 RES_TABLE_TYPE_TYPE》)

这样我们就知道了,如果resId使用默认的0x7F,则不存在问题;如果不使用默认的0x7F,而且还缺少dynamicRefTable这个映射,则查找parent会出问题。 同时,当用aapt编译资源时,如果resId的packageId不是0x7F,才会添加dynamicRefTable。

由于我们是在resource.arsc生产后才去修改,所以aapt编译时并未加入dynamicRefTable,这就是问题所在,我们需要手动加入它。

那么我们就需要知道它的结构,如下:

 

首先header包括两部分,一个是ResChunk_header结构体,一个是包含的映射个数。 ResChunk_header结构体如下:

这个结构体就不细说了,其中type就是RES_TABLE_LIBRARY_TYPE 紧接着header,就是映射表了,每一个映射都是一个ResTable_lib_entry结构,一共有count个

映射结构也很简单,packageId+packageName,但是注意packageId占固定4byte大小,而packageName则占固定256byte大小(2byte一个字符,一共128个字符),当然一般packageName都不会这么长,多余的用0补充。

4、dynamicRefTable位置

上面我们知道了dynamicRefTable的结构,那么它在文件中处于什么位置呢?又与哪些数据有联系? 根据网上的神图我重新做了一张更完整更准确的结构图,补充了dynamicRefTable,如下:

img

 

这样看到比较清晰了,dynamicRefTable是整个Package结构中的一部分,与Package中的其他结构是并列的,所以不会影响。但是加入dynamicRefTable会影响Package头部中的块大小,以及文件header中的文件大小。

那么如果下面还有另外一个Package数据块,则会有影响么?

答案是不会,因为与位置相关的数据,比如偏移量,一般都是相对于该数据块头部的。

5、验证dynamicRefTable

这样dynamicRefTable的结构就分析完了。那么我们如何知道resource.arsc文件中是否存在这种结构?

我们可以使用aapt反编译一下打包好的apk

./aapt d resources /Users/.../app-debug.apk

会得到如下数据:

Package Groups (1)

Package Group 0 id=0x6d packageCount=1 name=com.example.bennu.testapp

DynamicRefTable entryCount=1:

0x6d -> com.example.bennu.testapp

 

Package 0 id=0x6d name=com.example.bennu.testapp

type 1 configCount=2 entryCount=12

spec resource 0x6d020000 com.example.bennu.testapp:drawable/arrow: flags=0x00000000

spec resource 0x6d020001 com.example.bennu.testapp:drawable/fio: flags=0x00000000

spec resource 0x6d020002 com.example.bennu.testapp:drawable/fit: flags=0x00000000

spec resource 0x6d020003 com.example.bennu.testapp:drawable/fith: flags=0x00000000

spec resource 0x6d020004 com.example.bennu.testapp:drawable/fo: flags=0x00000000

spec resource 0x6d020005 com.example.bennu.testapp:drawable/ft: flags=0x00000000

spec resource 0x6d020006 com.example.bennu.testapp:drawable/fth: flags=0x00000000

spec resource 0x6d020007 com.example.bennu.testapp:drawable/test_bg_java: flags=0x00000000

spec resource 0x6d020008 com.example.bennu.testapp:drawable/test_bg_xml: flags=0x00000000

spec resource 0x6d020009 com.example.bennu.testapp:drawable/xo: flags=0x00000000

spec resource 0x6d02000a com.example.bennu.testapp:drawable/xt: flags=0x00000000

spec resource 0x6d02000b com.example.bennu.testapp:drawable/xth: flags=0x00000000

config (default):

resource 0x6d020007 com.example.bennu.testapp:drawable/test_bg_java: t=0x03 d=0x00000000 (s=0x0008 r=0x00)

resource 0x6d020008 com.example.bennu.testapp:drawable/test_bg_xml: t=0x03 d=0x00000001 (s=0x0008 r=0x00)

config xhdpi-v4:

resource 0x6d020000 com.example.bennu.testapp:drawable/arrow: t=0x03 d=0x00000003 (s=0x0008 r=0x00)

resource 0x6d020001 com.example.bennu.testapp:drawable/fio: t=0x03 d=0x00000004 (s=0x0008 r=0x00)

resource 0x6d020002 com.example.bennu.testapp:drawable/fit: t=0x03 d=0x00000005 (s=0x0008 r=0x00)

resource 0x6d020003 com.example.bennu.testapp:drawable/fith: t=0x03 d=0x00000006 (s=0x0008 r=0x00)

resource 0x6d020004 com.example.bennu.testapp:drawable/fo: t=0x03 d=0x00000007 (s=0x0008 r=0x00)

resource 0x6d020005 com.example.bennu.testapp:drawable/ft: t=0x03 d=0x00000008 (s=0x0008 r=0x00)

resource 0x6d020006 com.example.bennu.testapp:drawable/fth: t=0x03 d=0x00000009 (s=0x0008 r=0x00)

resource 0x6d020009 com.example.bennu.testapp:drawable/xo: t=0x03 d=0x0000000a (s=0x0008 r=0x00)

resource 0x6d02000a com.example.bennu.testapp:drawable/xt: t=0x03 d=0x0000000b (s=0x0008 r=0x00)

resource 0x6d02000b com.example.bennu.testapp:drawable/xth: t=0x03 d=0x0000000c (s=0x0008 r=0x00)

可以看到有dynamicRefTable的相关数据,如果没有则表示不存在这类数据。

6、总结

dynamicRefTable的存在很重要,影响了bag类数据的parent部分。但是有一个前提,即资源索引不使用默认的0x7F。所以在正常编译时我们不需要特别去注意它,但是如果我们手动干预或后期修改了资源索引,就不得不考虑它了。