PHP 解析与打包 Android V2 签名渠道包
参考文章 https://cloud.tencent.com/developer/article/1006237
本程序使用v2签名方案进行存放渠道信息,仿照美团打包工具walle制作,还做了apk包签名块缺失的情况支持(暂时不知道什么情况下会缺失一个签名块)
<?php
class AndroidSignApkToolV2
{
/**
* 读取apk安装包参数
* @param string $filePath
* @param bool $moreParams
* @return array
* @throws Exception
*/
public function readParam(string $filePath, bool $moreParams = false)
{
if (!file_exists($filePath)) {
throw new Exception(sprintf('需要分析的APK包 %s 不存在', $filePath));
}
$stream = fopen($filePath, 'r');
//跳到结尾 寻找中心目录偏移位置
fseek($stream, -6, SEEK_END);
//中心目录偏移位置
$centerDirectoryOffsetPosition = unpack('V*',fread($stream, 4))[1];
//检查是否存在魔法数
fseek($stream, $centerDirectoryOffsetPosition - 16);
if (fread($stream, 16) != 'APK Sig Block 42') {
throw new Exception('数据格式异常,不存在ApkSigBlock魔法数');
}
//获取V2签名块总大小
fseek($stream, ftell($stream) - 24);
$v2BlockSize = unpack('P*', fread($stream, 8))[1];
//获取v2签名块开始位置
$startPosition = $centerDirectoryOffsetPosition - 8 - $v2BlockSize + 0x00000010;
//获取v2签名 特殊标识ID 0x7109871a
fseek($stream, $startPosition);
if (bin2hex(fread($stream, 8)) != '1a87097117060000') {
throw new Exception('数据格式异常,不支持非V2签名进行操作');
}
//获取第一个块大小
fseek($stream, -16, SEEK_CUR);
$blockSize1 = unpack('P*', fread($stream, 8))[1];
//获取第二个块大小
fseek($stream, $blockSize1, SEEK_CUR);
$blockSize2 = unpack('P*', fread($stream, 8))[1];
//获取第三个块大小
fseek($stream, $blockSize2, SEEK_CUR);
$blockSize3 = unpack('P*', fread($stream, 8))[1];
//缺失第三个包 暂时不知道为什么
if ($blockSize3 == $v2BlockSize) {
$blockSize3 = 0;
$attachContentSize = $v2BlockSize;
} else {
//获取附加信息块大小
//检查是否缺包打出来的子包
if (fread($stream, 4) == 'wwwq') {
//是缺失包
fseek($stream, -4, SEEK_CUR);
$attachContentSize = $blockSize3;
$blockSize3 = 0;
} else {
//不是缺失包
fseek($stream, -4, SEEK_CUR);
fseek($stream, $blockSize3, SEEK_CUR);
$attachContentSize = unpack('P*', fread($stream, 8))[1];
}
}
$moreInfo = [
'centerDirPosition' => $centerDirectoryOffsetPosition,
'v2BlockSize' => $v2BlockSize,
'v2BlockStartPosition' => $startPosition,
'b1Size' => $blockSize1,
'b2Size' => $blockSize2,
'b3Size' => $blockSize3,
];
$attachContent = '';
do {
//没有进行添加附加信息块 因为总大小与v2块总大小一致
if ($attachContentSize == $v2BlockSize) {
break;
}
//获取附加块信息
$attachContent = fread($stream, $attachContentSize);
fclose($stream);
break;
} while (false);
//返回更多信息
if ($moreParams) {
return array_merge(['attackContent' => $attachContent], $moreInfo);
}
return ['attackContent' => $attachContent];
}
/**
* 写出APK安装包参数
* @param string $inputFile
* @param string $outputFile
* @param string $content
* @return void
* @throws Exception
*/
public function writeParam(string $inputFile, string $outputFile, string $content)
{
if (!file_exists($inputFile)) {
throw new Exception(sprintf('masterApk包 %s 不存在', $inputFile));
}
$contentLength = strlen($content);
if ($contentLength == 0) {
throw new Exception('写入参数内容不能为空');
}
//补全兼容缺失包
$content = 'wwwq' . $content;
$contentLength = strlen($content);
$masterApkPackageInfo = $this->readParam($inputFile, true);
if ($masterApkPackageInfo['attackContent'] != '') {
throw new Exception('masterApk包不能是已经修改的包的');
}
$masterApkStream = fopen($inputFile, 'r');
$subPackageApkStream = fopen($outputFile, 'w');
//第一步 复制第一段内容
fwrite($subPackageApkStream, fread($masterApkStream, $masterApkPackageInfo['v2BlockStartPosition'] - 16));
//第二步 写出需要添加附加文本长度大小 与 偏移写出块大小
fwrite($subPackageApkStream, pack('P*', $masterApkPackageInfo['v2BlockSize'] + $contentLength + 8));
fseek($masterApkStream, 8, SEEK_CUR);
fwrite($subPackageApkStream, fread($masterApkStream, 8 + $masterApkPackageInfo['b1Size']));
fwrite($subPackageApkStream, fread($masterApkStream, 8 + $masterApkPackageInfo['b2Size']));
if ($masterApkPackageInfo['b3Size'] != 0) {
fwrite($subPackageApkStream, fread($masterApkStream, 8 + $masterApkPackageInfo['b3Size']));
}
//第三步 写出附加文本大小 与 附加文本内容
fwrite($subPackageApkStream, pack('P*', $contentLength));
fwrite($subPackageApkStream, $content);
//写出v2签名块大小
fwrite($subPackageApkStream, pack('P*', $masterApkPackageInfo['v2BlockSize'] + $contentLength + 8));
// exit(var_dump(222));
//复制后续内容
$copyLength = filesize($inputFile) - ftell($masterApkStream) - 14;
fseek($masterApkStream, 8, SEEK_CUR);
fwrite($subPackageApkStream, fread($masterApkStream, $copyLength));
fwrite($subPackageApkStream, pack('V*', $masterApkPackageInfo['centerDirPosition'] + $contentLength + 8));
fwrite($subPackageApkStream, pack('v*', 0));
fclose($masterApkStream);
fclose($subPackageApkStream);
}
}
写入数据后,在android内怎么获取写入的数据
可以使用头条的渠道sdk进行获取,这玩意就是跟那个搭配使用的
你好大佬,这方法 大一点的包 安装失败你有碰到过吗
没试过。。。我这边测试的一半都是50~70m左右的安装包
谢谢,我在调试的时候用不了,请问怎么回事?
php Fatal error: Allowed memory size of 536870912 bytes exhausted (tried to allocate 8083517594057203592 bytes) 88行
你php运行内存超过了限制,你调大一些就好了