/ApkDataMultiplexing

APK数据复用优化

Primary LanguageJava

APK数据复用优化

如今越来越多的 APP 采用了 apk 文件完整性校验来检测是否被修改,原先 hook 签名数据的方式已无法通过该检测,对于该情况需要使用原包过签方式。

其原理是将原 apk 文件打包到过签包中,运行时将原 apk 文件释放到本地并进行 IO 重定向,实现对抗整包校验,但这也导致了过签包的体积是原体积的两倍以上。

过签包结构

  • 过签包
    • assets/base.apk (原包)
      • AndroidManifest.xml
      • classes.dex
      • resources.arsc
      • res/a
      • res/b
    • lib/arm64-v8a/libxxx.so(增)
    • AndroidManifest.xml(改)
    • classes.dex(改)
    • resources.arsc(改)
    • res/a
    • res/b

以上是某个过签包的文件结构,assets/base.apk 是原包,其体积占了整个过签包体积的近一半,如果我们对比这个过签包与原包的内容,会发现过签包

新增了:

  • assets/base.apk
  • lib/arm64-v8a/libxxx.so

修改了:

  • AndroidManifest.xml
  • classes.dex
  • resources.arsc

以下文件则完全一样:

  • res/a
  • res/b

如果这 2 个一样的文件有 10 MB,那么它们会在过签包中占据 20 MB 的空间,造成了空间浪费,而这个是可以优化的。

体积优化思路

我们知道 APK 文件使用的是 ZIP 格式,而 ZIP 文件末尾有一个**目录记录了所有的文件头和它们的数据偏移。

经过测试,安卓系统读取 apk 文件时,先读取**目录来获取所有文件信息,再根据数据偏移读取文件数据。

重点来了,文件数据是通过**目录的偏移去定位的,而过签包和原包都是 ZIP 结构,那么对于相同的文件,我们可以让过签包和原包的**目录数据偏移都指向同一个地址,删除另一个地址的数据段,于是就实现了体积优化。

具体如下:

优化前

  • 过签包
    • assets/base.apk
      • 数据段1 res/a
      • 数据段2 res/b
      • 文件信息 res/a 指向数据段1
      • 文件信息 res/b 指向数据段2
    • 数据段3 res/a
    • 数据段4 res/b
    • 文件信息 res/a 指向数据段3
    • 文件信息 res/b 指向数据段4

优化后

  • 过签包
    • assets/base.apk
      • 数据段1 res/a
      • 数据段2 res/b
      • 文件信息 res/a 指向数据段1
      • 文件信息 res/b 指向数据段2
    • 数据段3 res/a(删除)
    • 数据段4 res/b(删除)
    • 文件信息 res/a 改为指向数据段1
    • 文件信息 res/b 改为指向数据段2

因为原包的数据不能动,因此我们删除的是过签包中对应文件的数据段,并让过签包**目录对应文件的数据偏移指向原包对应的数据段。

这里有个非常重要的前提,原包必须以存储方式打包到过签包中,不能压缩!!!

使用方法

源码无任何依赖,方便移植与修改,可直接复制到你的项目,并调用以下方法。

DataMultiplexing.optimize("test.apk", "output.apk", "assets/base.apk", true);

V2 / V3 签名必须在优化之后进行,并且必须使用 V2V3SchemeSigner,否则优化会失效,关于签名的更多信息请看后面的说明。

V2V3SchemeSigner.sign(new File("output.apk"), new JksSignatureKey("test.jks", "123456", "123456", "123456"), true, true);

另外输出的文件已经过 ZipAlign,不建议进行其它处理,否则很可能导致失去优化效果。

局限性

  1. 根据上面的原理,优化后能减小多少体积取决于过签包与原包有多少完全相同的文件,最多不会超过 50%,这里的完全相同包括文件名相同、文件数据相同、压缩方式相同。

  2. 由于该优化要求原包必须以存储方式进行打包,相比以压缩方式打包会占用更多体积,不过原包已经是一个压缩包了,再次压缩效果有限,但极端情况下也可能因为这个导致优化后体积增大。

  3. 由于目前基本没有兼容该技术的工具,对优化包进行二次修改(如签名,添加或删除文件等)将大概率导致优化失效,体积会膨胀回去,需要再次进行优化。

打个广告,MT管理器已完美兼容该技术,修改此类 apk 不会导致优化失效。

关于签名

我们一般使用 apksig 对 apk 进行签名,然而经过测试,使用该库签名后,数据复用优化会失效,而数据复用优化又会破坏 V2 / V3 签名。

为此本项目提供了一个 V2V3SchemeSigner 来进行 V2 / V3 签名同时又不破坏数据复用优化。

在签名时需要遵守以下规则:

V1 签名

先用 apksig 进行 V1 签名,然后进行数据复用优化。

V2 / V3 签名

先进行数据复用优化,再使用 V2V3SchemeSigner 进行签名,不需要用到 apksig。

不需要兼容 Android 4.x 的话直接使用这个方案比较方便。

V1 + V2 / V3 签名

先用 apksig 进行 V1 + V2 / V3 签名,然后进行数据复用优化,最后使用 V2V3SchemeSigner 再次签名。

致谢

感谢 LSP 技术团队!该技术思路并非原创,起因是前阵子有网友跟我反馈,使用 MT 修改 LSPatch 生成的 apk 文件后体积会暴涨,在分析了测试文件后才发现了这个思路。