我们知道在 Android 的打包过程中,有一个步骤是压缩,也是为了减少 apk 包的大小,其中在压缩的过程中,很大一部分就是对资源的压缩,除了系统的压缩方案之外,我们今天讲另外两种压缩方案:和
微信的方案是通过修改 aapt 在处理资源文件相关的源码达到资源文件的替换;而后者指通过直接修改 resources.arsc 文件达到资源文件混淆的目的。相比之下,微信的方案更加优秀。
微信中的资源混淆工具主要为了混淆资源 ID 长度 (例如将 res/drawable/welcome.png 混淆为 r/s/a.png),同时利用 7z 深度压缩,大大减少了安装包体积,同时也增加了逼格,提升了反破解难度。
具体源码与使用方法详细在 github 中:
资源混淆简单来说希望实现将 res/drawable/icon,png 变成 res/drawable/a.png, 或我们甚至可以将文件路径也同时混淆,改成 r/s/a.png。形如:
- Proguard -> Resource Proguard
- R.string.name -> R.string.a
- res/drawable/icon -> res/drawable/ar/s/a
要实现上面的效果,我们可以想到以下几种方案:
我们知道,resources.arsc 一共有五种 chunk 类型,分别为 TYPETABLE,TYPEPACKAGE,TYPE_STRING ,TYPETYPE,TYPECONFIG。
说明:
table,是整个 reousces table 的开始,它的 chunksize 即是整个文件的大小。
package,指的是一个 package 的开始,其实在 resources,arsc 是可以有多个 package 的。 而 packageID 即是资源 resID 的最高八位 ,一般来说系统 android 的是 1(0x01),普通的例如 com.tencent.mm 会是 127(0x7f),剩下的是从 2 开始起步。当然这个我们在 aapt 也是可以指定的 (1-127 即八位的合法空间, 一些混合编译就是改这个 packageID)。
string, 代表 stringblock,我们一共有三种类型的 stringblock。分别是 table stringblock,typename stringblock, specsname stringblock。
type,这里讲的是 typename stringblock 里面我们用到的各种 type(用到多少种类型的 type, 就有多少个 type chunk),例如 attr, drawable, layout, id, color, anim 等,Type ID 是紧跟着 Package ID。
config, 即是 Android 用来描述资源维度,例如横竖屏,屏幕密度,语言等。对于每一种 type,它定义了多少种 config,它后面就紧跟着多少个 config chunk, 例如我们定义了 drawable-mdpi,drawable-hdpi, 那后面就会有两个 config。
entry,尽管没有 entry 这个 chunk, 但是每个 config 里面都会有很多的 entry,例如 drawable-mdpi 中有 icon1.png,icon2.png 两个 drawable, 那在 mdpi 这个 config 中就存在两个 entry。
比如微信在压缩前后:
具体实现方案如图:
然后我们在与 7z 的极限压缩结合,同时我们也可以强制压缩类似 resources.arsc、png、jpg 等 Android 默认不会打包压缩的文件。最后把修改后的 resources.arsc 重打包即可。
通过上面的分析,我们来看一下微信压缩的完整流程:
在 Android 系统中,每一个应用程序一般都会配置很多资源,用来适配不同密度、大小和方向的屏幕,以及适配不同的国家、地区和语言等等。这些资源是在应用程序运行时自动根据设备的当前配置信息进行适配的。这也就是说,给定一个相同的资源 ID,在不同的设备配置之下,查找到的可能是不同的资源。
这个查找过程对应用程序来说,是完全透明的,这个过程主要是靠 Android 资源管理框架来完成的,而 Android 资源管理框架实际是由 AssetManager 和 Resources 两个类来实现的。其中,Resources 类可以根据 ID 来查找资源,而 AssetManager 类根据文件名来查找资源。事实上,如果一个资源 ID 对应的是一个文件,那么 Resources 类是先根据 ID 来找到资源文件名称,然后再将该文件名称交给 AssetManager 类来打开对应的文件的。
基本流程如下图:
通过上图我们可以看到 Resources 是通过 resources.arsc 把 Resource 的 ID 转化成资源文件的名称,然后交由 AssetManager 来加载的。
而 Resources.arsc 这个文件是存放在 APK 包中的,他是由 AAPT 工具在打包过程中生成的,他本身是一个资源的索引表,里面维护者资源 ID、Name、Path 或者 Value 的对应关系,AssetManager 通过这个索引表,就可以通过资源的 ID 找到这个资源对应的文件或者数据。
AAPT 是 Android Asset Packaging Tool 的缩写,它存放在 SDK 的 tools / 目录下,AAPT 的功能很强大,可以通过它查看查看、创建、更新压缩文件 (如 .zip 文件,.jar 文件, .apk 文件), 它也可以把资源编译为二进制文件,并生成 resources.arsc, AAPT 这个工具在 APK 打包过程中起到了非常重要作用,在打包过程中使用 AAPT 对 APK 中用到的资源进行打包,这里不对 AAPT 这个工具做过多的讨论,只看一下 AAPT 这个工具在打包过程中起到的作用,下图是 AAPT 打包的流程:
AAPT 这个工具在打包过程中主要做了下列工作:
我们知道在系统的 Proguard 中,对 APK 中资源文件名使用简短无意义名称进行替换,给破解者制造困难,从而做到资源的相对安全。通过阅读 AAPT 编译资源的代码,我们发现修改 AAPT 在处理资源文件相关的源码是能够做到资源文件名的替换,下面是 Resource.cpp 中 makeFileResources() 的修改的代码片段:
- static status_t makeFileResources(Bundle* bundle, const sp& assets,
- ResourceTable* table,
- const sp& set,
- const char* resType)
- {
- String8 type8(resType);
- String16 type16(resType);
- bool hasErrors = false;
- ResourceDirIterator it(set, String8(resType));
- ssize_t res;
- while ((res=it.next()) == NO_ERROR) {
- if (bundle->getVerbose()) {
- printf(" (new resource id %s from %s)\n",
- it.getBaseName().string(), it.getFile()->getPrintableSource().string());
- }
- String16 baseName(it.getBaseName());
- const char16_t* str = baseName.string();
- const char16_t* const end = str + baseName.size();
- while (str < end) {
- if (!((*str >= 'a' && *str <= 'z')
- || (*str >= '0' && *str <= '9')
- || *str == '_' || *str == '.')) {
- fprintf(stderr, "%s: Invalid file name: must contain only [a-z0-9_.]\n",
- it.getPath().string());
- hasErrors = true;
- }
- str++;
- }
- String8 resPath = it.getPath();
- resPath.convertToResPath();
- String8 obfuscationName;
- String8 obfuscationPath = getObfuscationName(resPath, obfuscationName);
- table->addEntry(SourcePos(it.getPath(), 0), String16(assets->getPackage()),
- type16,
- baseName, // String16(obfuscationName),
- String16(obfuscationPath), // resPath
- NULL,
- &it.getParams());
- assets->addResource(it.getLeafName(), obfuscationPath/*resPath*/, it.getFile(), type8);
- }
- return hasErrors ? UNKNOWN_ERROR : NO_ERROR;
- }
上述代码是在 ResourceTable 和 Assets 中添加资源文件时, 对资源文件名称进行修改,这就能够做到资源文件名称的替换,这样通过使用修改过的 AAPT 编译资源并进行打包,从而达到保护资源的目的。
微信的方案是通过修改 aapt 在处理资源文件相关的源码达到资源文件的替换;而美团主要通过直接修改 resources.arsc 文件达到资源文件混淆的目的。微信从 aapt 的原理上着手,而美团只是在已有的方案上优化,相比之下,微信的混淆更彻底。
来源: