Skip to content

L-JINBIN/ApkDataMultiplexing

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

10 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

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 文件后体积会暴涨,在分析了测试文件后才发现了这个思路。

About

APK数据复用优化

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages