resource.arsc二进制内容解析 之 Dynamic package reference
目錄
1、加載Theme出錯
2、aapt中的特殊處理
3、RES_TABLE_LIBRARY_TYPE
4、dynamicRefTable位置
5、驗證dynamicRefTable
6、總結
1、加載Theme出錯
這是一篇補充文章,在做動態替換resId的過程中,我發現bag類型的ResTable_entry在使用過程中存在問題。比如style,其parent解析一直有問題,日志如下:
W/ResourceType: Failed resolving bag parent id 0x7d090062W/ResourceType: Attempt to retrieve bag 0x7d090114 which is invalid or in a cycle.經過一些粗暴的嘗試,發現解決不了問題,看來需要祭出絕招了——看源碼。 因為是與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源碼中通過搜索上面日志里的內容,我們找到了如下代碼: ssize_t ResTable::getBagLocked(uint32_t resID, const bag_entry** outBag, uint32_t* outTypeSpecFlags) const {...status_t err = grp->dynamicRefTable.lookupResourceId(&resolvedParent);if (err != NO_ERROR) {ALOGE("Failed resolving bag parent id 0x%08x", parent);return UNKNOWN_ERROR;}... } 可以看到關鍵函數是lookupResourceId,如果這個函數返回的不是NO_ERROR就會報錯。那么來看這個函數的源碼: status_t DynamicRefTable::lookupResourceId(uint32_t* resId) const {uint32_t res = *resId;size_t packageId = Res_GETPACKAGE(res) + 1;if (packageId == APP_PACKAGE_ID && !mAppAsLib) {// No lookup needs to be done, app package IDs are absolute.return NO_ERROR;}if (packageId == 0 || (packageId == APP_PACKAGE_ID && mAppAsLib)) {*resId = (0xFFFFFF & (*resId)) | (((uint32_t) mAssignedPackageId) << 24);return NO_ERROR;}// Do a proper lookup.uint8_t translatedId = mLookupTable[packageId];if (translatedId == 0) {ALOGW("DynamicRefTable(0x%02x): No mapping for build-time package ID 0x%02x.",(uint8_t)mAssignedPackageId, (uint8_t)packageId);for (size_t i = 0; i < 256; i++) {if (mLookupTable[i] != 0) {ALOGW("e[0x%02x] -> 0x%02x", (uint8_t)i, mLookupTable[i]);}}return UNKNOWN_ERROR;}*resId = (res & 0x00ffffff) | (((uint32_t) translatedId) << 24);return 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
status_t ResourceTable::flatten(Bundle* bundle, const sp<const ResourceFilter>& filter,const sp<AaptFile>& dest,const bool isBase) {...// The libraries this table references.Vector<sp<Package> > libraryPackages;const ResTable& table = mAssets->getIncludedResources();const size_t basePackageCount = table.getBasePackageCount();for (size_t i = 0; i < basePackageCount; i++) {size_t packageId = table.getBasePackageId(i);String16 packageName(table.getBasePackageName(i));if (packageId > 0x01 && packageId != 0x7f &&packageName != String16("android")) {libraryPackages.add(sp<Package>(new Package(packageName, packageId)));}}...if (isBase) {status_t err = flattenLibraryTable(data, libraryPackages);if (err != NO_ERROR) {fprintf(stderr, "ERROR: failed to write library table\n");return err;}}...} 可以看到當packageId不是0x7F,會新建一個Package結構存入libraryPackages,那么這個libraryPackages有啥用?我們繼續往下看發現flattenLibraryTable函數使用了它,這個函數源碼如下: status_t ResourceTable::flattenLibraryTable(const sp<AaptFile>& dest, const Vector<sp<Package> >& libs) {// Write out the library table if necessaryif (libs.size() > 0) {if (kIsDebug) {fprintf(stderr, "Writing library reference table\n");}const size_t libStart = dest->getSize();const size_t count = libs.size();ResTable_lib_header* libHeader = (ResTable_lib_header*) dest->editDataInRange(libStart, sizeof(ResTable_lib_header));memset(libHeader, 0, sizeof(*libHeader));libHeader->header.type = htods(RES_TABLE_LIBRARY_TYPE);libHeader->header.headerSize = htods(sizeof(*libHeader));libHeader->header.size = htodl(sizeof(*libHeader) + (sizeof(ResTable_lib_entry) * count));libHeader->count = htodl(count);// Write the library entriesfor (size_t i = 0; i < count; i++) {const size_t entryStart = dest->getSize();sp<Package> libPackage = libs[i];if (kIsDebug) {fprintf(stderr, " Entry %s -> 0x%02x\n",String8(libPackage->getName()).string(),(uint8_t)libPackage->getAssignedId());}ResTable_lib_entry* entry = (ResTable_lib_entry*) dest->editDataInRange(entryStart, sizeof(ResTable_lib_entry));memset(entry, 0, sizeof(*entry));entry->packageId = htodl(libPackage->getAssignedId());strcpy16_htod(entry->packageName, libPackage->getName().string());}}return NO_ERROR; } 可以看到向resource.arsc文件中寫入了一個RES_TABLE_LIBRARY_TYPE類型的數據結構。 這個RES_TABLE_LIBRARY_TYPE在網上的resource.arsc解析相關文章中幾乎沒有被提到,而且網上的那張神圖中也看不到這個結構。
3、RES_TABLE_LIBRARY_TYPE
那么它是什么?我們來看看找到的一些蛛絲馬跡:| Table header | RES_TABLE_TYPE = 0x002 | |
| Resource strings | RES_STRING_POOL_TYPE = 0x001 | e.g. 'Hello World!' |
| Package header | RES_TABLE_PACKAGE_TYPE = 0x200 | Rewrite entry: package id |
| Type strings | RES_STRING_POOL_TYPE = 0x001 | e.g. 'attr', 'layout', etc. |
| Key strings | RES_STRING_POOL_TYPE = 0x001 | e.g. 'activity_main', etc. |
| DynamicRefTable | RES_TABLE_LIBRARY_TYPE = 0x0203 | Insert entry for Android 5.0+ |
| Type spec | RES_TABLE_TYPE_SPEC_TYPE = 0x0202 | |
| Type info | RES_TABLE_TYPE_TYPE = 0x0201 | Rewrite 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,這就是問題所在,我們需要手動加入它。
那么我們就需要知道它的結構,如下:
struct ResTable_lib_header {struct ResChunk_header header;// The number of shared libraries linked in this resource table.uint32_t count; }; 首先header包括兩部分,一個是ResChunk_header結構體,一個是包含的映射個數。ResChunk_header結構體如下: struct ResChunk_header { //當前這個chunk的類型 uint16_t type; //當前這個chunk的頭部大小 uint16_t headerSize; //當前這個chunk的大小 uint32_t size; };
這個結構體就不細說了,其中type就是RES_TABLE_LIBRARY_TYPE
緊接著header,就是映射表了,每一個映射都是一個ResTable_lib_entry結構,一共有count個 struct ResTable_lib_entry {// The package-id this shared library was assigned at build time.// We use a uint32 to keep the structure aligned on a uint32 boundary.uint32_t packageId;// The package name of the shared library. \0 terminated.char16_t packageName[128]; }; 映射結構也很簡單,packageId+packageName,但是注意packageId占固定4byte大小,而packageName則占固定256byte大小(2byte一個字符,一共128個字符),當然一般packageName都不會這么長,多余的用0補充。
4、dynamicRefTable位置
上面我們知道了dynamicRefTable的結構,那么它在文件中處于什么位置呢?又與哪些數據有聯系?
根據網上的神圖我重新做了一張更完整更準確的結構圖,補充了dynamicRefTable,如下:
這樣看到比較清晰了,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。所以在正常編譯時我們不需要特別去注意它,但是如果我們手動干預或后期修改了資源索引,就不得不考慮它了。
總結
以上是生活随笔為你收集整理的resource.arsc二进制内容解析 之 Dynamic package reference的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: gradle编译打包过程 之 Proce
- 下一篇: Android逆向:二进制xml文件解析