一款 APK 动态修改工具


动态修改 APK?什么时候需要用到呢?是的,为一些细分渠道打 flag 时会需要用到,为一些特殊场景下载安装 APK 后做定向跳转以延续之前的场景时会需要…等等这里就不赘述。

相关 Github地址 点这里。

去年六月写的这个工具,在公司电脑闲置了挺久,最近项目用到就重拾起来,跟大家分享分享,也算个备忘吧。
版权声明:本文为 frendy 原创文章,可以随意转载,但请务必在明确位置注明出处。


用法

1. 写入数据:

在服务器端调用 ApkModifier.jar,往指定的 apk 包写入 data.txt 里的数据,最终文件生成在 apks 文件夹下:

java -jar ApkModifier.jar test.apk data.txt

image

2. 读取数据:

在 App 端导入 iimedia_apk_modifier_1.0.0.jar,在需要获取数据的地方通过以下方法获取:

String data_insert = ApkModifier.getData(mContext);

原理

该工具通过修改 ZIP 文件末尾的注释字段来实现,参考了打包工具 PackerNg

1. 文件格式:

APK 文件本质是一个带签名信息的 ZIP 文件,想了解具体结构可以点这里 The structure of a PKZip file

这里我们重点看一下 End of central directory record,如下图示,包含 Comment Len 和 Zip File Comment 两个字段,前者表示注释内容的长度,后者是可选的注释内容,正确修改这一部分不会对 ZIP 文件造成破坏。这里我们就利用这个字段来添加一些自定义的数据。

image

2. 写入数据:

起初踩了不少坑,后来参考 PackerNg,我们在注释的最后加入几个 MAGIC 字节,这样从文件的最后开始,读取很少的几个字节就可以定位到自定义数据,避免读取整个文件,提高了性能。

// {@see java.util.zip.ZipOutputStream.writeEND}
byte[] data = comment.getBytes(UTF_8);
final RandomAccessFile raf = new RandomAccessFile(file, "rw");
raf.seek(file.length() - SHORT_LENGTH);
// write zip comment length
// (content field length + length field length + magic field length)
writeShort(data.length + SHORT_LENGTH + MAGIC.length, raf);
// write content
writeBytes(data, raf);
// write content length
writeShort(data.length, raf);
// write magic bytes
writeBytes(MAGIC, raf);
raf.close();
3. 读取数据:

如上所述,读取的时候,先定位到 MAGIC 的位置,根据 Comment Len 读取自定义数据。

raf = new RandomAccessFile(file, "r");
long index = raf.length();
byte[] buffer = new byte[MAGIC.length];
index -= MAGIC.length;
// read magic bytes
raf.seek(index);
raf.readFully(buffer);
// if magic bytes matched
if (isMagicMatched(buffer)) {
	index -= SHORT_LENGTH;
	raf.seek(index);
	// read content length field
	int length = readShort(raf);
	if (length > 0) {
		index -= length;
		raf.seek(index);
		// read content bytes
		byte[] bytesComment = new byte[length];
		raf.readFully(bytesComment);
		return new String(bytesComment, UTF_8);
	} else {
		throw new IOException("zip comment content not found");
	}
} else {
	throw new IOException("zip comment magic bytes not found");
}


欢迎来撩

frendy

It will shine for us...