Android APK渠道信息写入实现和读取

背景

《APK签名机制说明》中已经了解Android的签名和校验机制,此篇基于此前提,来说明一下如何绕过Android的签名校验机制,以实现在APK中添加一些自定义的信息。

针对Signature Scheme v1渠道信息的写入

由于使用Signature Scheme v1签名的APK,仅仅只会校验META-INF文件夹以外的文件的是否被修改过,那么针对此情况,我们完全可以将我们需要携带的信息,添加到META-INFO文件夹下,只要我们不修改其中与签名有关的文件信息和其中被校验的文件,那么都可以校验通过的。
此处,使用Python脚本addEmptyFileToMETA.py来将文件写入到APK的META-INFO文件夹下,其代码实现如下:

1
2
3
4
5
6
7
8
9
from sys import argv
import zipfile

script, apkFile, emptyFile, channelName = argv

zipped = zipfile.ZipFile(apkFile, 'a', zipfile.ZIP_DEFLATED)
empty_channel_file = "META-INF/{channel}".format(chanel=channelName)
zipped.write(emptyFile, empty_channel_file)
zipped.close()

下面尝试将一张二维码图片写入到META-INFO文件夹下,并校验APK是否能够正常覆盖安装,以校验是否破坏了APK的签名:

如上可知,虽然使用jarsigner命令检测时,会多了一个警告信息,但是在Android系统上,却能够正常的覆盖安装原有的app的,所以针对使用Signature Scheme v1签名的APK,可以通过在META-INFO文件夹下添加文件,以实现在APK中携带一下自定义信息。

针对Signature Scheme v1渠道信息的读取

至于对应渠道信息的获取,只需要从java.util.zip.ZipFile中获取出对应java.util.zip.ZipEntry的输入流并读取即可:

1
2
3
4
5
6
7
8
9
ZipFile file = new File(context.getApplicationInfo().sourceDir);
Enumeration<?> entries = file.entries();
while (entries.hasMoreElements()) {
ZipEntry entry = ((ZipEntry) entries.nextElement());
if (TextUtils.equals(entry.getName(), channelFileName)) {
InputStream is = file.getInputStream(entry);
...
}
}

针对Signature Scheme v2渠道信息的写入

由于使用Signature Scheme v2签名的APK,会对APK除Signing Block之外的数据都进行摘要验证,所以没办法像v1将渠道信息写入META-INFO文件夹来实现。但这并不代表着无法在APK内写入额外的信息,通过查看APK签名校验时获取签名信息的逻辑可以发现,系统通过遍历Signing Block中的所有键值对,并获取id为0x7109871a的value,而对于id非0x7109871a的键值对,则直接进行忽略(最好将自定义的键值对插在id为0x7109871a键值对之后,不然使用apksigner进行校验apk的时候,会有warning提示;另外尽量避免使用0x7109871a作为id,即使要用,也一定要放在原有0x7109871aid的后面):

针对此情况,完全可以构建一个自定义的键值对,用于保存渠道或其他信息,然后插入到APK的Signing Block中,从而避免系统校验签名后插入的内容:

由于在Signing Block中插入了额外的数据,会导致ZIP文件格式错乱,因此,必须要修改End of Central Directory块中Central Directory在文件中的偏移量,在原有偏移量的基础上加上插入额外数据的大小即可。因为在校验签名的时候,系统会现将该字段还原为没有Signing Block时的偏移量再进行计算摘要数据,所以此处修改该字段的数据,并不会破坏原有的签名信息。
由于代码比较长,所以就不在此贴出对应代码,具体代码可参考:ApkV2ChannelTools

针对Signature Scheme v2渠道信息的读取

与系统读取APK签名信息的流程一样,整个算法流程就是根据ZIP文件格式解析流程,先找到End of Central Directory块的起始位置,然后查找对应的Central Directory位置,从而解析出对应的Signing Block信息(其中有个需要注意的是,APK不支持使用ZIP64的格式,所以在获取Signing Block的时候如果遇到ZIP64标示块的话,会支持抛出异常)。
由于代码比较长,所以就不在此贴出对应代码,具体代码可参考:ApkV2ChannelTools