中文站

新一代AAB框架下的安全加固之道

在 Google I/O 大会上,Google 向 Android 引入了新 APP 动态化框架 Android APP Bundle (AAB),被看作是对 Android 未来发展具有颠覆性的动态化解决方案。本文从 AAB动态化框架的优势出发,解析如何在 AAB 格式下的实现优质加固,助力移动应用在国内外市场间自由切换,以更小应用提供优质体验。


图 | AAB 格式的架构图

01 AAB应用的瘦身术

相比于 APK,Google 主推的 APP 打包格式 AAB 在很多方面都更为优越,主要体现为如下几点:

1.1. 动态分发

一个 APK 中往往包含各国的语言资源、ABI、屏幕密度等资源。然而,对于单个用户来说,往往只需要这些资源中的部分。

目前,国内的开发者将所有资源统一放在单个 APK 中,这样就会导致 APK 特别庞大,而AAB在压缩APK体积方面具有优势。

而为了缩小体积,部分开发者会有意缩减 APK 中的 ABI 目录。例如,将 arm64-v8a 的 SO 从 APK 中去除,只留下 armeabi-v7a 的 SO。但这种做法使得64位 CPU 的手机无法发挥出其64位的运算优势,降低程序运行速度。

Split APKs 是 Android 5.0 开始提供的多 APK 构建机制,借助 Split APKs 可以将一个 APK 基于 ABI、屏幕密度和 CPU 架构拆分成多个 APK ,这样可以有效减少单个 APK 体积。当用户下载应用程序安装包时,Google Play 会自动识别用户的语言和 CPU 架构,自动将对应平台 SO 和资源的 APK 下发给用户。

1.2. 动态功能模块

在 Android Studio 中新增了一个模块:动态功能模块。通过该模块开发出的功能可在用户需要时再进行下载,类似于目前在国内被广泛使用的热更新机制,只不过热更新大多是用来修复功能性 BUG 的,而动态功能模块更倾向于形成一个独立功能。

用户在安装 APK 时,只需要下载一个包含 APP 主要功能的 APK ,而其他附加功能可在用户需要时进行动态下载安装。这样就进一步减小了 APK 的体积,为用户改善了 APP 安装使用体验。

02 AAB加固解决方案

AAB 格式在给 Android APP 带来便利的同时,也给移动安全领域带来了新的挑战:AAB 格式的安装包,在组织结构和文件内容方面都与 APK 格式有较大差异,传统的 APP 加壳技术无法直接应用在 APP Bundle 模式生成的数据包之上。

易盾通过对 AAB 格式进行深入研究,针对新的 AAB 格式对现有加固进行了一系统调整和改进,从而能够支持新的 AAB 格式。

那么,现在的加固方案要兼容 AAB 格式面临着哪些难点,易盾又是如何改进的呢?

2.1. 防二次打包

我们知道,正常的 APK 在上传应用市场时都是已经提前进行了签名,当破解者从应用市场上将 APK 下载下来,经过重打包之后,必然需要修改 APK 的签名,而一般的防二次打包功能都会对 APK 的签名进行验证,若 APK 的签名与原始的签名不一致,则 APP 会直接退出。如下所示:


而加固中的防二次打包方案,都是在加固时预先读取 APK 中的签名内容,使用加固前的 APK 签名特征作为校验标准,在 APP 运行时对 APK 的签名特征进行校验,若运行时的 APK 签名特征与加固前的 APK 签名特征不一致,则 APK 会直接闪退。

而使用AAB格式时,Google Play 会要求用户上传签名证书文件,由 Google Play 在下发 APK 的时候再进行签名。换而言之,AAB格式是无法进行签名的,尝试使用 apksigner 对 APK 进行签名,输出如下所示:


可以看到,apksigner 会尝试去寻找根目录下的 AndroidManifest.xml,但因为该文件是 AAB 格式,导致 apksigner 无法正确解析。

这随之导致,加固厂商在加固时无法正确获取到 APK 的原始签名,也就无法正确实现防二次打包。那么,易盾打造的加固解决方案是如何解决这个问题的呢?

众所周知,android 使用的第一代签名工具,是 jarsigner,而不是 apksigner,而 jarsigner 是JAVA SDK中自带的用于对 JAR 包进行签名的工具。既然 jarsigner 可以对 JAR 和 APK 进行签名,那么 jarsigner 是否也可以对 AAB 进行签名呢?

答案是肯定的。我们尝试使用 jarsigner 对 AAB 文件进行签名,签名后的 AAB 文件结构如下所示:


事实上,jarsigner 是一个对 ZIP 格式的压缩包进行签名的工具,而无论是 JAR、APK 还是 AAB,本质上都是 ZIP 格式。因此,只要用户在加固前使用 jarsigner 对 AAB 进行签名,加固厂商就能正确获取到 APK 的签名特征。

2.2. 资源混淆

资源混淆常用于减小 APK 的体积和保护资源。

某些 APP 可能会将 APP 中的一些关键数据放在 res 文件中,而破解者通过代码中资源的 ID 可以轻松找到其对应的资源文件,从而达到在不修改代码的情况下轻松修改 APK 的一些重要逻辑的效果。而通过资源混淆,APK 中的 res 资源名会被修改为毫无意义的名称,破解者再难以轻松地找到代码对应的资源文件。

同时,通过对资源的文件名进行混淆,将长文件名混淆为短文件名,从而实现 APK 的"瘦身"效果。根据每个 APK 的具体情况,每个 APK 在经过资源混淆之后可缩小 1 至 N MB 的体积。

在 APK 中,所有的资源都记录在资源索引文件 resource.arsc 中,如下所示:


但是在 AAB 中,资源索引文件的格式和名称也发生了变化:


2.3. AndroidManifest.xml修改

虽然 VMP 方案和 JAVA2C 方案已经出现很多年了,但是因为性能原因,目前市面上的主流的加固方案还是 DEX 加固,VMP 方案和 JAVA2C 仅用来做核心类的保护。而 DEX 加固的基本原理就是通过修改应用的入口 Application 接管应用的启动逻辑,在执行 DEX 的动态加载等一系列操作之后,将程序逻辑还给 APP 的原始入口 Application,如下所示:


图 | 经易盾加固之后的入口 Application

一些粗心大意的开发者甚至会将 APP 的 debuggable 开关设为 "true",从而使得 APK 能够被轻易地调试。易盾在加固的过程会自动识别出这种情况,将对应的开关改为 "false"。

上述提到的数据都记录在 AndroiManifest.xml 文件中,而经过打包出来的 APK,其 AndroidManifest.xml 是经过编译之后的二进制格式,其中还涉及到一些和 APK 中的资源相关的数据,并不像开发阶段的明文那么容易修改,编译后的 AndroidManifest.xml 内容如下所示:

可以看到,相比于开发阶段的 AndroidManifest.xml,编译后 AndroidManifest.xml 多出了很多不可见字符,这往往对应着一些数据结构。要对其进行修改,就需要对 AndroiManifest.xml 有深入的了解。

而 AAB 中的 AndroiManifest.xml 相比于 APK 中的 AndroiManifest.xml 又有了一定程度的变化,但原始的修改方案并不能直接拿来复用。

上述 AndroiManifest.xml 的变化 Google 并没有在文档中记录下来。易盾在原有修改方案基础上,又深入研究了 Android Bundle Tool 的打包源码,提取出了 AAB 中的 AndroiManifest.xml 的文件格式,从而实现了 AAB 格式中 AndroiManifest.xml 文件的修改。

2.4. DEX和SO的加固

事实上,在 AAB 格式中,DEX 文件和 SO 文件的内容并没有被修改,只不过其在 APK 中的目录组织结构发生了变化。如下所示:


对这部分变化的兼容方式较为简单,我们只需要将相对应的文件抽离出来,重新按 APK 的目录结构组织成一个伪 APK。在加固之后,我们再将加固处理后的文件还原到 AAB 中,即可在不修改原有加固方案的前提下完美适配 AAB 格式。

03 总结

AAB 格式相比于 APK 格式,在 DEX 格式和 SO 格式上并没有多少变化,这也使得对于 DEX 和 SO 的保护方案能够较方便地迁移过来。

而其中较困难的地方在于,各种配置文件和资源文件的格式发生了较大的变化,导致原始的方案直接无法使用了。

易盾从用户的角度出发,考虑到了广大出海应用的加固需求,积极对 AAB 格式进行适配,使原有的加固方案能够完整地迁移到 AAB 格式上,助力应用安全地轻松适应各种改变。