Files

951 lines
32 KiB
PHP
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?
$apktool_jar = 'D:\Desktop\apktool\apktool_2.11.1.jar';//apktool包的路径
$apk_file[0] = 'D:\Desktop\apktool\app-release.apk';//1号apk,也就是注入弹窗壳apk
$apk_file[1] = 'D:\Desktop\apktool\original.apk';//2号apk,也就是被注入的正常APK
$keystore = 'D:/Desktop/apktool/my-release-key.keystore';//证书文件
$alias = 'myalias';
$storepass = '123456';
$keypass = '123456';
$apksigner_path = 'C:\Users\Administrator\AppData\Local\Android\Sdk\build-tools\35.0.0/apksigner.bat'; //签名工具,这里我定义的是我的Androidstudio的路径 或仅用 'apksigner' 如果加到环境变量了的话这里就可以填null
print_r("开始反编译\n");
$decompile = decompile_apks($apktool_jar, $apk_file);//反编译
//print_r($decompile);exit;
$de_apk1 = $decompile[0][2];
$de_apk2 = $decompile[1][2];
//这里是基类劫持
$result = get_application_inheritance_chain($de_apk2);//2号APK的基类读取
if($result['depth'] > 1){
print_r("该应用基类层级过多,可能已经被注入过了\n");
}
//print_r($result);//exit;
$smali = merge_smali_directories($de_apk1, $de_apk2);//smali融合,将1号APK的smali复制到2号apk中
print_r("正在进行基类替换\n");
$result = replace_application_super($result['file'],'com.example.shell.HookApplication;');//基类替换,将2号文件的application父类替换为1号文件中写好的application类
//print_r($result);//exit;
$result = ensure_application_name($de_apk2, 'com.example.shell.HookApplication');//基类劫持的话,需要检查是否存在全局application,没有的话就需要添加
//print_r($result);
$result = merge_activities_to_application_only($de_apk1, $de_apk2);//AndroidManifest融合
$result = rebuild_apk($apktool_jar,$de_apk2);//回编译
$output_apk = $result[2];//拿到回编译之后的apk路径
//$result = sign_apk($keystore, $alias, $storepass, $keypass, $output_apk, null, $de_apk2, $apksigner_path);//签名,并删除反编译目录
$result = sign_apk($keystore, $alias, $storepass, $keypass, $output_apk, null, null, $apksigner_path);//签名
//print_r($result);
//delete_dir($de_apk1);//删除壳目录
unlink($result[2].".idsig");
//================================这里是启动入口修改注入的用法,兼容性并不是很好================================
/*
//这里是启动入口替换
$dirs = ['D:\Desktop\apktool\shell', 'D:\Desktop\apktool\qupai'];
$result = parse_apk_manifests($dirs);//找启动入口类名
print_r("启动入口{$result[1][1]}\n");
//exit;
$result = replace_smali_string('D:\Desktop\apktool\shell\smali', '[OpenClassName]', $result[1][1]);//融合之前进行smali字符串替换
print_r("smali替换结果\n");
print_r($result);
print_r("\n");
$result = merge_android_manifests('D:\Desktop\apktool\shell', 'D:\Desktop\apktool\qupai');//AndroidManifest融合
$result = merge_smali_directories('D:\Desktop\apktool\shell', 'D:\Desktop\apktool\qupai');//smali融合 */
/* $list = [
'/res/drawable/icon.png',
'/res/layout/main.xml'
];
$result = copy_res_files($de_apk1, $de_apk2, $list);//资源融合,将1号APK里的资源复制到2号APK里
print_r($result); */
//================================下方方法代码勿动================================
function merge_activities_to_application_only($source_dir, $target_dir) {
$src_manifest = rtrim($source_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
$dst_manifest = rtrim($target_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
if (!file_exists($src_manifest) || !file_exists($dst_manifest)) {
echo "Manifest 文件不存在\n";
return false;
}
// 加载源和目标 manifest
$src_doc = new DOMDocument();
$src_doc->preserveWhiteSpace = false;
$src_doc->formatOutput = true;
$src_doc->load($src_manifest);
$dst_doc = new DOMDocument();
$dst_doc->preserveWhiteSpace = false;
$dst_doc->formatOutput = true;
$dst_doc->load($dst_manifest);
// 获取 <manifest> 根节点
$dst_manifest_node = $dst_doc->getElementsByTagName("manifest")->item(0);
$src_manifest_node = $src_doc->getElementsByTagName("manifest")->item(0);
if (!$dst_manifest_node || !$src_manifest_node) {
echo "未找到 <manifest> 根节点\n";
return false;
}
// 合并 <uses-permission>(去重)
$dst_perms = [];
foreach ($dst_doc->getElementsByTagName("uses-permission") as $perm) {
$name = $perm->getAttribute("android:name");
if ($name) {
$dst_perms[$name] = true;
}
}
foreach ($src_doc->getElementsByTagName("uses-permission") as $perm) {
$name = $perm->getAttribute("android:name");
if ($name && !isset($dst_perms[$name])) {
$comment = $dst_doc->createComment(" 此 uses-permission 来自合并插入 ");
$dst_manifest_node->appendChild($comment);
$dst_manifest_node->appendChild($dst_doc->importNode($perm, true));
$dst_perms[$name] = true;
}
}
// 合并 <permission>(去重)
$dst_defined_perms = [];
foreach ($dst_doc->getElementsByTagName("permission") as $perm) {
$name = $perm->getAttribute("android:name");
if ($name) {
$dst_defined_perms[$name] = true;
}
}
/* foreach ($src_doc->getElementsByTagName("permission") as $perm) {
$name = $perm->getAttribute("android:name");
if ($name && !isset($dst_defined_perms[$name])) {
$comment = $dst_doc->createComment(" 此 permission 来自合并插入 ");
$dst_manifest_node->appendChild($comment);
$dst_manifest_node->appendChild($dst_doc->importNode($perm, true));
$dst_defined_perms[$name] = true;
}
} */
// 获取 <application> 节点
$dst_app = $dst_doc->getElementsByTagName("application")->item(0);
$src_app = $src_doc->getElementsByTagName("application")->item(0);
if (!$dst_app || !$src_app) {
echo "未找到 <application> 标签\n";
return false;
}
// 合并 <activity> 和 <activity-alias>(去掉 intent-filter
foreach (['activity', 'activity-alias'] as $tag) {
foreach ($src_app->getElementsByTagName($tag) as $node) {
$imported = $dst_doc->importNode($node, true);
// 移除 intent-filter
$filters = $imported->getElementsByTagName("intent-filter");
while ($filters->length > 0) {
$imported->removeChild($filters->item(0));
}
$comment = $dst_doc->createComment(" 此 $tag 来自合并插入 ");
$dst_app->appendChild($comment);
$dst_app->appendChild($imported);
}
}
// 保存结果
$dst_doc->save($dst_manifest);
echo "合并完成:uses-permission、permission、activity 均已插入并带注释\n";
return true;
}
function delete_dir($dir) {
if (!is_dir($dir)) return;
$items = scandir($dir);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$path = $dir . DIRECTORY_SEPARATOR . $item;
if (is_dir($path)) {
delete_dir($path);
} else {
unlink($path);
}
}
rmdir($dir);
}
function copy_res_files($src_dir, $dst_dir, $file_list) {
$copied_files = [];
$count = 0;
foreach ($file_list as $relative_path) {
// 标准化路径,去掉开头斜杠
$relative_path = ltrim($relative_path, '/\\');
$src_file = rtrim($src_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $relative_path;
$dst_file = rtrim($dst_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . $relative_path;
if (!is_file($src_file)) {
continue; // 源文件不存在,跳过
}
if (file_exists($dst_file)) {
continue; // 目标已存在,跳过
}
// 创建目标文件夹
$dst_folder = dirname($dst_file);
if (!is_dir($dst_folder)) {
mkdir($dst_folder, 0777, true);
}
if (copy($src_file, $dst_file)) {
$copied_files[] = str_replace('\\', '/', $relative_path);
$count++;
}
}
return [
'count' => $count,
'files' => $copied_files
];
}
function ensure_application_name($apk_dir, $class_name) {
$manifest_path = rtrim($apk_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
if (!file_exists($manifest_path)) {
return [false, "Manifest 文件不存在:$manifest_path"];
}
// 加载并解析 XML
libxml_use_internal_errors(true); // 屏蔽格式警告
$xml = new DOMDocument();
$xml->preserveWhiteSpace = false;
$xml->formatOutput = true;
$xml->load($manifest_path);
$xpath = new DOMXPath($xml);
$xpath->registerNamespace("android", "http://schemas.android.com/apk/res/android");
// 获取 <application> 标签
$applications = $xml->getElementsByTagName("application");
if ($applications->length === 0) {
return [false, "未找到 <application> 标签"];
}
$application = $applications->item(0);
// 查找 android:name 属性
$name_attr = null;
foreach ($application->attributes as $attr) {
if ($attr->nodeName === "android:name" || $attr->name === "android:name") {
$name_attr = $attr;
break;
}
}
// 是否需要修改
$need_update = false;
if ($name_attr === null) {
// 属性不存在,则添加
$application->setAttribute("android:name", $class_name);
$need_update = true;
} elseif (trim($name_attr->value) === "") {
// 属性为空,设置新值
$name_attr->value = $class_name;
$need_update = true;
}
// 保存文件
if ($need_update) {
$xml->save($manifest_path);
return [true, "已设置 android:name 为:$class_name"];
} else {
return [true, "已有有效 android:name,无需修改"];
}
}
//Application基类替换
function replace_application_super($target_file_path, $new_super_class_name, $apk_root_dir = null) {
if (!is_file($target_file_path) || pathinfo($target_file_path, PATHINFO_EXTENSION) !== 'smali') {
return [false, "不是有效的 smali 文件", null, null];
}
// 将 Java 类名(com.xxx.HookApplication;)转为 smali 路径
$class_name = rtrim($new_super_class_name, ';');
$class_path = str_replace('.', '/', $class_name) . '.smali';
// 获取 apk 根目录(根据 smali 文件路径推算或传入)
if (!$apk_root_dir) {
$apk_root_dir = explode(DIRECTORY_SEPARATOR, $target_file_path);
while (count($apk_root_dir)) {
$path = implode(DIRECTORY_SEPARATOR, $apk_root_dir);
if (is_dir($path) && preg_match('/smali(_classes\d+)?$/', basename($path))) {
array_pop($apk_root_dir); // 去掉 smali_classesX
break;
}
array_pop($apk_root_dir);
}
$apk_root_dir = implode(DIRECTORY_SEPARATOR, $apk_root_dir);
}
// 遍历 smali 目录查找新的父类文件
$smali_dirs = [];
foreach (scandir($apk_root_dir) as $entry) {
if (preg_match('/^smali(_classes\d+)?$/', $entry) && is_dir($apk_root_dir . DIRECTORY_SEPARATOR . $entry)) {
$smali_dirs[] = $apk_root_dir . DIRECTORY_SEPARATOR . $entry;
}
}
$hook_file_found = false;
$hook_file_path = null;
$hook_is_app = false;
foreach ($smali_dirs as $dir) {
$full_path = $dir . DIRECTORY_SEPARATOR . $class_path;
if (file_exists($full_path)) {
$hook_file_found = true;
$hook_file_path = $full_path;
$lines = file($full_path);
foreach ($lines as $line) {
if (preg_match('/^\.super\s+(L[^;]+;)/', trim($line), $match)) {
if ($match[1] === 'Landroid/app/Application;') {
$hook_is_app = true;
}
break;
}
}
break;
}
}
if (!$hook_file_found) {
return [false, "指定的新父类类文件不存在", null, null];
}
if (!$hook_is_app) {
return [false, "新父类不是 Application 子类,不允许替换", null, null];
}
// 开始替换目标文件中的 .super
$lines = file($target_file_path);
$modified = false;
$original_super = null;
$new_super_smali = 'L' . str_replace('.', '/', $class_name) . ';';
foreach ($lines as $index => $line) {
if (preg_match('/^\.super\s+(L[^;]+;)/', trim($line), $match)) {
$original_super = $match[1];
if ($original_super !== $new_super_smali) {
$lines[$index] = ".super $new_super_smali\n";
$modified = true;
}
break;
}
}
if ($modified) {
file_put_contents($target_file_path, implode('', $lines));
return [true, "替换成功", $original_super, $new_super_smali];
} else {
return [false, "无需修改,.super 已是目标类", $original_super, $new_super_smali];
}
}
//基类读取
function get_application_inheritance_chain($apk_dir) {
$manifest_path = rtrim($apk_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
if (!file_exists($manifest_path)) {
return ['error' => "Manifest 文件不存在:$manifest_path"];
}
// 读取 AndroidManifest 内容
$content = file_get_contents($manifest_path);
// 提取 application 的 android:name
if (!preg_match('/<application[^>]*android:name="([^"]+)"/', $content, $match)) {
return ['error' => "未找到 application 的 android:name 属性"];
}
$class_name = $match[1];
// 处理相对类名(.MyApp)拼接包名
if (substr($class_name, 0, 1) === '.') {
if (preg_match('/<manifest[^>]*package="([^"]+)"/', $content, $pkg_match)) {
$class_name = $pkg_match[1] . $class_name;
}
}
// 构造链路
$chain = build_class_chain($apk_dir, $class_name);
// 获取最终基类名称
$final = get_last_node($chain);
$depth = get_chain_depth($chain);
return [
'final_super' => $final['super'],
'class' => $final['class'] ?? $class_name,
'file' => $final['file'] ?? null,
'depth' => $depth,
'chain' => $chain
];
}
//基类链路查找
function build_class_chain($apk_dir, $class_name) {
$class_path = str_replace('.', '/', ltrim($class_name, '.')) . '.smali';
$smali_dirs = [];
foreach (scandir($apk_dir) as $entry) {
if (preg_match('/^smali(_classes\d+)?$/', $entry) && is_dir($apk_dir . DIRECTORY_SEPARATOR . $entry)) {
$smali_dirs[] = $apk_dir . DIRECTORY_SEPARATOR . $entry;
}
}
foreach ($smali_dirs as $smali_dir) {
$full_path = $smali_dir . DIRECTORY_SEPARATOR . $class_path;
if (file_exists($full_path)) {
$lines = file($full_path);
foreach ($lines as $line) {
if (preg_match('/^\.super\s+(L[^;]+;)/', trim($line), $super_match)) {
$super_smali = $super_match[1];
// 到达终点,这一步其实可以用数组将别的注入器的类也填进来,直接将别人的注入替换为自己的注入,比如云注入 Lcom/sadfxg/fasg/App
if ($super_smali === 'Landroid/app/Application;') {
return [
'class' => $class_name,
'super' => $super_smali,
'file' => $full_path,
'extends' => null
];
}
// 向上递归
$super_java = str_replace('/', '.', substr($super_smali, 1, -1));
return [
'class' => $class_name,
'super' => $super_smali,
'file' => $full_path,
'extends' => build_class_chain($apk_dir, $super_java)
];
}
}
return [
'class' => $class_name,
'super' => null,
'file' => $full_path,
'extends' => null,
'error' => '.super 未找到'
];
}
}
return [
'class' => $class_name,
'super' => null,
'file' => null,
'extends' => null,
'error' => '类文件未找到'
];
}
function get_last_node($chain) {
while ($chain && isset($chain['extends']) && $chain['extends']) {
$chain = $chain['extends'];
}
return $chain;
}
function get_chain_depth($chain) {
$depth = 0;
while ($chain) {
$depth++;
$chain = $chain['extends'] ?? null;
}
return $depth;
}
//smali文件融合
function replace_smali_string($dir, $search, $replace, $case_sensitive = true) {
$result = [];
if (!is_dir($dir)) {
echo "无效的目录:$dir\n";
return $result;
}
$iterator = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($dir));
foreach ($iterator as $file) {
if (!$file->isFile() || pathinfo($file, PATHINFO_EXTENSION) !== 'smali') {
continue;
}
$file_path = $file->getPathname();
$lines = file($file_path); // 逐行读取
$modified = false;
$new_lines = [];
foreach ($lines as $index => $line) {
$original_line = $line;
if ($case_sensitive) {
if (strpos($line, $search) !== false) {
$new_line = str_replace($search, $replace, $line);
if ($new_line !== $line) {
$result[] = [
'file' => $file_path,
'line' => $index + 1,
'original' => rtrim($line),
'modified' => rtrim($new_line)
];
$line = $new_line;
$modified = true;
}
}
} else {
if (preg_match('/' . preg_quote($search, '/') . '/i', $line)) {
$new_line = preg_replace('/' . preg_quote($search, '/') . '/i', $replace, $line);
if ($new_line !== $line) {
$result[] = [
'file' => $file_path,
'line' => $index + 1,
'original' => rtrim($line),
'modified' => rtrim($new_line)
];
$line = $new_line;
$modified = true;
}
}
}
$new_lines[] = $line;
}
if ($modified) {
file_put_contents($file_path, implode('', $new_lines));
}
}
return $result;
}
//签名APK
function sign_apk($keystore, $alias, $storepass, $keypass, $unsigned_apk, $signed_apk = null, $output_folder = null, $apksigner_path = 'apksigner') {
if (!file_exists($unsigned_apk)) {
$msg = "未找到待签名 APK 文件:$unsigned_apk";
echo "$msg\n";
return [false, $msg, null, null];
}
if (!file_exists($keystore)) {
$msg = "签名文件不存在:$keystore";
echo "$msg\n";
return [false, $msg, null, null];
}
// 自动生成签名后 APK 名称
if (empty($signed_apk)) {
$signed_apk = preg_replace('/\.apk$/', '.signed.apk', $unsigned_apk);
}
// 构造签名命令
$cmd = "\"$apksigner_path\" sign --ks \"$keystore\" --ks-key-alias $alias --ks-pass pass:$storepass --key-pass pass:$keypass --out \"$signed_apk\" \"$unsigned_apk\"";
echo "执行签名命令:$cmd\n";
// 执行命令
$output = shell_exec($cmd);
echo "签名输出:\n$output\n";
// 判断签名结果
if (file_exists($signed_apk)) {
$msg = " 签名完成:$signed_apk";
echo "$msg\n";
// 删除未签名 APK
unlink($unsigned_apk);
echo "已删除未签名文件:$unsigned_apk\n";
// 提示删除反编译目录(不自动执行)
if (!empty($output_folder)) {
delete_dir($output_folder);
//echo "请手动清理反编译目录:$output_folder\n";
}
return [true, $msg, $signed_apk, $output];
} else {
$msg = " 签名失败,未生成文件。";
echo "$msg\n";
return [false, $msg, null, $output];
}
}
//回编译
function rebuild_apk($apktool_path, $decode_folder, $output_apk = null) {
if (!file_exists($apktool_path)) {
$msg = "找不到 apktool 工具:$apktool_path";
echo "$msg\n";
return [false, $msg, null, null];
}
if (!is_dir($decode_folder)) {
$msg = "反编译目录不存在:$decode_folder";
echo "$msg\n";
return [false, $msg, null, null];
}
// 如果未指定输出 APK 路径,则自动生成
if (empty($output_apk)) {
$parent_dir = dirname($decode_folder);
$folder_name = basename($decode_folder);
$output_apk = $parent_dir . DIRECTORY_SEPARATOR . $folder_name . '.build.apk';
}
// 构造打包命令
$cmd = "java -jar \"$apktool_path\" b \"$decode_folder\" -o \"$output_apk\"";
//echo "执行打包命令:$cmd\n";
echo "开始回编译打包\n";
// 执行命令
$output = shell_exec($cmd);
//echo "回编译输出:\n$output\n";
// 检查输出文件
if (file_exists($output_apk)) {
$msg = "APK 回编译成功,输出文件:$output_apk";
echo "$msg\n";
return [true, $msg, $output_apk, $output];
} else {
$msg = "回编译失败,未生成 APK 文件。";
echo "$msg\n";
return [false, $msg, $output_apk, $output];
}
}
function merge_smali_directories($source_dir, $target_dir) {
// 1. 获取源目录中所有 smali 和 smali_* 目录
$smali_dirs = [];
foreach (scandir($source_dir) as $entry) {
if (preg_match('/^smali(_classes\d+)?$/', $entry) && is_dir($source_dir . DIRECTORY_SEPARATOR . $entry)) {
$smali_dirs[] = $entry;
}
}
// 2. 获取目标目录已有的最大 smali_classesN 序号
$existing = [];
foreach (scandir($target_dir) as $entry) {
if (preg_match('/^smali(_classes(\d+))?$/', $entry, $m) && is_dir($target_dir . DIRECTORY_SEPARATOR . $entry)) {
$existing[] = isset($m[2]) ? intval($m[2]) : 1; // smali 视为 classes1
}
}
$max_index = empty($existing) ? 0 : max($existing);
// 3. 依次复制,每个递增命名
foreach ($smali_dirs as $dir_name) {
$src_path = $source_dir . DIRECTORY_SEPARATOR . $dir_name;
$new_index = ++$max_index;
$dst_name = $new_index === 1 ? 'smali' : 'smali_classes' . $new_index;
$dst_path = $target_dir . DIRECTORY_SEPARATOR . $dst_name;
// 递归复制目录
recursive_copy($src_path, $dst_path);
echo "已复制 $dir_name $dst_name\n";
}
echo "smali 融合完成。\n";
return true;
}
// 工具函数:递归复制目录内容
function recursive_copy($src, $dst) {
if (!is_dir($src)) return;
if (!file_exists($dst)) mkdir($dst, 0777, true);
$items = scandir($src);
foreach ($items as $item) {
if ($item === '.' || $item === '..') continue;
$src_item = $src . DIRECTORY_SEPARATOR . $item;
$dst_item = $dst . DIRECTORY_SEPARATOR . $item;
if (is_dir($src_item)) {
recursive_copy($src_item, $dst_item);
} else {
copy($src_item, $dst_item);
}
}
}
//AndroidManifest融合
function merge_android_manifests($source_dir, $target_dir, $intent = false) {
$src_manifest = rtrim($source_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
$dst_manifest = rtrim($target_dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
if (!file_exists($src_manifest) || !file_exists($dst_manifest)) {
echo "Manifest 文件不存在\n";
return false;
}
$src_xml = file_get_contents($src_manifest);
$dst_xml = file_get_contents($dst_manifest);
// 1. 提取 <uses-permission>
preg_match_all('/<uses-permission[^>]+\/>/', $src_xml, $src_permissions);
preg_match_all('/<uses-permission[^>]+\/>/', $dst_xml, $dst_permissions);
$src_permissions = array_unique($src_permissions[0]);
$dst_permissions_text = implode("\n", $dst_permissions[0]);
// 2. 提取 <permission>
preg_match_all('/<permission[^>]+\/>/', $src_xml, $src_custom_permissions);
preg_match_all('/<permission[^>]+\/>/', $dst_xml, $dst_custom_permissions);
$src_custom_permissions = array_unique($src_custom_permissions[0]);
$dst_custom_permissions_text = implode("\n", $dst_custom_permissions[0]);
// 3. 提取 <activity> 和 <activity-alias>
preg_match_all('/<activity\b[^>]*>.*?<\/activity>/is', $src_xml, $src_activities);
preg_match_all('/<activity-alias\b[^>]*>.*?<\/activity-alias>/is', $src_xml, $src_aliases);
$src_activities = $src_activities[0];
$src_aliases = $src_aliases[0];
$all_activities = array_merge($src_activities, $src_aliases);
// 4. 处理 Activity,仅保留一个入口
$entry_found = false;
$processed_activities = [];
foreach ($all_activities as $block) {
if (!$entry_found && preg_match('/<intent-filter>.*?MAIN.*?LAUNCHER.*?<\/intent-filter>/is', $block)) {
$processed_activities[] = "<!-- activity 来自插入 -->\n" . $block;
$entry_found = true;
} else {
$block_no_entry = preg_replace('/<intent-filter>.*?<\/intent-filter>/is', '', $block);
$processed_activities[] = "<!-- activity 来自插入 -->\n" . $block_no_entry;
}
}
// 5. 注释原 manifest 中的 intent-filter 启动项
if($intent){
$dst_xml = preg_replace_callback(
'/(<(activity|activity-alias)\b[^>]*>)(.*?<intent-filter>.*?<\/intent-filter>)(.*?<\/\2>)/is',
function ($matches) {
if (preg_match('/android.intent.action.MAIN/', $matches[3]) &&
preg_match('/android.intent.category.LAUNCHER/', $matches[3])) {
$commented = "<!-- 此处为原启动入口,intent-filter 已被注释 -->\n";
$commented .= preg_replace('/(<intent-filter>.*?<\/intent-filter>)/is', '<!-- $1 -->', $matches[3]);
return $matches[1] . "\n" . $commented . "\n" . $matches[4];
}
return $matches[0];
},
$dst_xml
);
}
// 6. 插入 <permission>(自定义权限),避免重复
foreach ($src_custom_permissions as $perm_def) {
if (strpos($dst_custom_permissions_text, $perm_def) === false) {
$insert = " <!-- 此自定义权限来自插入 -->\n $perm_def";
//$dst_xml = preg_replace('/(<manifest[^>]*>)/', "$1\n$insert", $dst_xml);//插入后会导致无法安装
}
}
// 7. 插入 <uses-permission>(跳过重复)
foreach ($src_permissions as $perm) {
if (strpos($dst_permissions_text, $perm) === false) {
$insert = " <!-- 此权限来自插入 -->\n $perm";
$dst_xml = preg_replace('/(<manifest[^>]*>)/', "$1\n$insert", $dst_xml);
}
}
// 8. 插入 <activity> 和 <activity-alias> 到 <application> 中
$insert_block = implode("\n ", $processed_activities);
$dst_xml = preg_replace_callback(
'/<application[^>]*>/',
function ($matches) use ($insert_block) {
return $matches[0] . "\n " . $insert_block;
},
$dst_xml,
1
);
// 9. 保存结果
file_put_contents($dst_manifest, $dst_xml);
echo "合并完成:权限、自定义权限、Activity 合并,并保留注释信息:$dst_manifest\n";
return true;
}
//找启动入口类名
function parse_apk_manifests($dirs) {
$results = [];
foreach ($dirs as $dir) {
$manifest_path = rtrim($dir, DIRECTORY_SEPARATOR) . DIRECTORY_SEPARATOR . 'AndroidManifest.xml';
if (!file_exists($manifest_path)) {
$results[] = [false, null, null, "Manifest 文件不存在:$manifest_path"];
continue;
}
// 加载 XML
$xml = new DOMDocument();
libxml_use_internal_errors(true); // 忽略格式警告
$xml->load($manifest_path);
$xpath = new DOMXPath($xml);
$launcher_activity = null;
$source = null;
// 查找所有 <activity>
$activities = $xpath->query('//activity');
foreach ($activities as $activity) {
$intent_filters = $activity->getElementsByTagName('intent-filter');
foreach ($intent_filters as $filter) {
$has_main = false;
$has_launcher = false;
foreach ($filter->getElementsByTagName('action') as $action) {
if ($action->getAttribute('android:name') === 'android.intent.action.MAIN') {
$has_main = true;
}
}
foreach ($filter->getElementsByTagName('category') as $category) {
if ($category->getAttribute('android:name') === 'android.intent.category.LAUNCHER') {
$has_launcher = true;
}
}
if ($has_main && $has_launcher) {
$launcher_activity = $activity->getAttribute('android:name');
$source = 'activity';
break 2;
}
}
}
// 如果未找到,再查找 <activity-alias>
if (!$launcher_activity) {
$aliases = $xpath->query('//activity-alias');
foreach ($aliases as $alias) {
$intent_filters = $alias->getElementsByTagName('intent-filter');
foreach ($intent_filters as $filter) {
$has_main = false;
$has_launcher = false;
foreach ($filter->getElementsByTagName('action') as $action) {
if ($action->getAttribute('android:name') === 'android.intent.action.MAIN') {
$has_main = true;
}
}
foreach ($filter->getElementsByTagName('category') as $category) {
if ($category->getAttribute('android:name') === 'android.intent.category.LAUNCHER') {
$has_launcher = true;
}
}
if ($has_main && $has_launcher) {
$launcher_activity = $alias->getAttribute('android:targetActivity');
$source = 'activity-alias';
break 2;
}
}
}
}
if ($launcher_activity !== null) {
$results[] = [true, $launcher_activity, $source, null];
} else {
$results[] = [false, null, null, "未找到启动 Activity$manifest_path"];
}
}
return $results;
}
// 方法:反编译 APK
function decompile_apks($apktool_jar, $apk_files, $output_base_dir = null) {
$results = [];
// 检查所有 APK 文件是否存在
foreach ($apk_files as $apk_file) {
if (!file_exists($apk_file)) {
return [[false, "APK 文件不存在:$apk_file", null, null]];
}
}
// 遍历每个 APK 执行反编译
foreach ($apk_files as $apk_file) {
// 生成默认输出目录
$apk_dir = dirname($apk_file);
$apk_name = pathinfo($apk_file, PATHINFO_FILENAME);
$output_dir = ($output_base_dir ?? $apk_dir) . DIRECTORY_SEPARATOR . $apk_name;
// 构造反编译命令
$cmd = "java -jar \"$apktool_jar\" d \"$apk_file\" -o \"$output_dir\" -f";
// 执行命令
$output = shell_exec($cmd);
// 判断是否成功(通过输出目录是否存在判断)
if (is_dir($output_dir)) {
$results[] = [true, "反编译成功", $output_dir, $output];
} else {
$results[] = [false, "反编译失败", $output_dir, $output];
}
}
return $results;
}