Android APK 签名欺骗(PMS Hook)与防篡改对抗方案分析

2 阅读3分钟

一、 问题链条与现象还原

在 Android 应用或游戏二次打包(破解、修改、换皮)的过程中,破解者为了绕过应用内部的本地签名校验机制,往往利用逆向工具(如 MT 管理器NP 管理器ARMPro 等)提供的 “去除签名校验” 功能进行自动化处理。

由此产生的问题链条与假象如下:

[二次打包修改 APK]
       │
       ▼
物理真实签名变更为破解者证书 (如 testkey / 弱密钥 d09e6e... / e89b15...)
       │
       ▼
应用启动阶段注入去签 Hook 逻辑 (Java / Native 双层拦截)
       │
       ▼
┌────────────────────────────────────────────────────────┐
│ 欺骗方式 A: 动态代理拦截 ActivityThread.sPackageManager  │
│ 欺骗方式 B: 反射替换 PackageInfo.CREATOR 序列化构造器     │
│ 欺骗方式 C: xhook 拦截 libc open() 将物理 APK 偷换为原版  │
└────────────────────────────────────────────────────────┘
       │
       ▼
SDK / App 获取签名,返回被冻结硬编码的原版合法签名 (86d195...)
       │
       ▼
App 内计算的 MD5 / SHA1 依然显示为原版合法值
(表象:换包换签“没生效”;实质:应用处于深度欺骗环境下,传统校验全线阵亡)

二、 欺骗原理深度剖析(从初级到高级)

通过对大量实战篡改样本(如 2026-8-26.apkInsideTest_kill.apk)反编译分析,我们从注入的 classes.dex.so 库中提取出其底层开源核心:https://github.com/L-JINBIN/ApkSignatureKillerEx(即 MT 管理器“去除签名校验 2.0 ~ 4.0”的底层技术)。

攻击者主要通过以下三代手段逐步升级欺骗:

1. 初级:PMS 动态代理拦截(killPM 基础版)

  • 原理:在 Application.attachBaseContext 阶段利用 Java 反射,将 ActivityThread 中的 sPackageManager 替换成 Java 动态代理对象(Proxy)。
  • 表现:当应用调用 context.getPackageManager().getPackageInfo(..., GET_SIGNATURES) 时,代理对象直接阻断系统 IPC 远程调用,强行返回预置的原版假签名。

2. 中级:PackageInfo.CREATOR 序列化劫持(killPM 增强版)

  • 原理:为了避开对 sPackageManager 的代理检测,攻击工具直接通过反射偷换了 Android 框架底层的 PackageInfo.CREATOR

    // 伪代码:直接篡改系统 Parcelable 反序列化构造器
    findField(PackageInfo.class, "CREATOR").set(null, new Parcelable.Creator<PackageInfo>() {
        @Override
        public PackageInfo createFromParcel(Parcel parcel) {
            PackageInfo info = originalCreator.createFromParcel(parcel);
            if (info.packageName.equals(targetPkg)) {
                info.signatures[0] = fakeSignature; // 强行换回原版证书
            }
            return info;
        }
    });
    
  • 表现:系统底层 IPC 调用依然正常执行,但在应用层将 Parcel 字节流反序列化为 Java 对象的那一瞬间,证书被匿名内部类(如 bin.mt.signature.KillerApplication$1)恶意篡改。

3. 高级:Native 层 IO 重定向(killOpen 终极版)

  • 原理:针对很多开发者尝试“绕开 PackageManager、直接读取物理 APK(base.apk)文件提取证书”的做法,攻击工具实现了物理层偷梁换柱:

    1. 工具在 APK 的 assets/SignatureKiller/origin.apk 内藏匿一个原版未修改的完整 APK
    2. 应用启动时,自动将原版 APK 释放到沙箱私有目录:/data/data/<包名>/origin.apk
    3. 加载 libSignatureKiller.so,利用 xhook 框架 Hook 了底层 C 运行时库(libc)的文件打开函数:open()open64()openat()fopen()
    4. 当 Java 层(如 JarFileZipFileRandomAccessFile)试图打开真实物理路径 /data/app/.../base.apk 时,被 Native 钩子强行将路径重定向到沙箱里的 origin.apk
  • 表现:开发者自以为穿透到了物理文件,底层读取的其实是攻击者预留在沙箱里的原版假包。


三、 为什么微信、QQ 等第三方登录能天然免疫?

许多开发者常问:“为什么微信/QQ 登录能准确识别篡改并拦截,而游戏自己的本地代码却会被骗?”

场景谁在执行签名校验?运行在哪个进程?破解者的 Hook 能否影响它?结果
应用/游戏本地校验App 自身 SDK 代码应用自身进程(因为破解代码就在同一个进程内存里)签名被劫持,返回假签名
微信 / QQ 登录微信 App / QQ App微信 / QQ 独立进程绝对不能(Linux 跨进程沙箱内存隔离)微信拿到真实签名,直接拦截
  • 底层鉴权链路

    1. 游戏拉起微信登录时,通过 Binder 跨进程向微信发起请求。
    2. Linux 内核机制向微信提供不可伪造的调用方真实身份(Binder.getCallingUid())。
    3. 微信在其干净、未被 Hook 的独立进程中向系统 PMS 查询该 UID 的签名。
    4. 微信拿到的永远是系统底层真实登记的安装包证书(testkey / 破解者证书),与开放平台后台登记的合法证书比对不符,立即弹窗拦截。

四、 降维对抗与破局方案(ApkSignUtil 生产级实战)

针对 ApkSignatureKillerEx 和 MT 管理器的攻击矩阵,我们在 SDK 中构建了多维穿透与反制闭环

               ┌── 针对 killPM (代理 Hook) ────► 1. 反射检测 Proxy.isProxyClass
               │
对抗防御矩阵 ────┼── 针对 killPM (CREATOR 劫持) ─► 2. 校验 PackageInfo.CREATOR 类名包名
               │
               ├── 针对 killOpen (libc 重定向) ─► 3. 路径等价变形绕过 strcmp + 沙箱假包侦测
               │
               └── 针对 V1/V2/V3 多种签名模式 ──► 4. 纯 Java 物理签名块二进制穿透解析

1. 穿透 libc IO 重定向:利用 strcmp 破绽进行【路径等价变形】

  • 破绽发现:逆向分析 libSignatureKiller.so 的二进制符号表,发现其重定向比对函数使用的是标准的 strcmp(精确字符串比对) 。它监控的目标路径是绝对路径 /data/app/.../base.apk

  • 反制破局:在 Java 读取物理文件时,对路径实施等价规范化变形(在最后一个文件名之前插入 /./):

    // 原始路径: /data/app/~~xxx/com.pkg-xxx/base.apk
    // 变形路径: /data/app/~~xxx/com.pkg-xxx/./base.apk
    int lastSlash = apkPath.lastIndexOf('/');
    String bypassPath = apkPath.substring(0, lastSlash) + "/./" + apkPath.substring(lastSlash + 1);
    File apkFile = new File(bypassPath);
    
  • 效果

    • libSignatureKiller.sostrcmp("/..././base.apk", "/.../base.apk") != 0,字符串比对不匹配,Hook 钩子直接落空,重定向失效!
    • 对 Linux 底层内核:/./ 指向的完全是同一个文件物理节点(inode),直接打开了真正的被篡改物理包,成功提取真实物理证书!

2. 双重特征识别:识破 CREATOR 劫持与沙箱假包

isPackageManagerHooked(Context) 中建立多重防线:

  • CREATOR 命名空间检测

    // 原生系统类必为 android.content.pm.PackageInfo$1
    // 若被篡改,类名会变成 bin.mt.signature.KillerApplication$1 等外部包名
    if (PackageInfo.CREATOR != null && !PackageInfo.CREATOR.getClass().getName().startsWith("android.content.pm.")) {
        return true; // 100% 抓获 CREATOR 劫持
    }
    
  • 沙箱伪造假包检测

    // 正常 App 绝不会在私有目录下存放 origin.apk
    File originApk = new File(context.getApplicationInfo().dataDir, "origin.apk");
    if (originApk.exists()) {
        return true; // 100% 抓获 killOpen 释放的原版假包
    }
    
  • 动态代理与特征常量检测

    • 检测 ActivityThread.sPackageManagerpm.mPM 是否为 Proxy.isProxyClass
    • 检测 System.getProperty("mt.signature.killer.path1") 等特有属性。

3. 物理签名全版本兼容提取(V1 + V2/V3 Signing Block)

不调用任何受 Android 限制的私有类(如 ApkSignatureSchemeV2Verifier),纯通过标准 Java IO 流式解析:

  • 优先 V1 提取:流式读取 AndroidManifest.xml 触发 JarVerifier 填充公钥。

  • 兜底 V2/V3 物理块解析

    1. 通过尾部 EOCD 定位 Central Directory Offset
    2. 向前读取 16 字节 Magic APK Sig Block 42
    3. 按照 ID 0x7109871a (V2) 与 0xf05368c0 (V3) 定位 signer 序列,直接提取证书公钥 DER 字节并计算 MD5。
    4. 兼容所有 Android 版本(Android 4.4 ~ Android 15)。

五、 实战样本验证对照表

以实际被二次打包去签名的样本(InsideTest_kill.apk)为例,实测效果如下:

校验维度系统原生 API (被欺骗)本方案 ApkSignUtil (穿透对抗)
证书 SubjectCN=rn, OU=rn, O=rn, C=CN (原版伪造)E=android@android.com, CN=Android, C=US (真实testkey)
证书 MD5447...... (假)e89.... (真实重签MD5)
证书 SHA197:bf:f6:2a:d3:04:66... (假)61:ed:37:7e:85:d3:86... (真实重签SHA1)
Hook 状态识别认为环境正常 (false)精准报警 (isHooked = true)
CREATOR 识别未知识别为 bin.mt.signature.KillerApplication$1

六、 业务落地与防御协同体系

层级对抗手段业务建议行为收益与破解代价
客户端 P0物理路径变形直读 + 多维 Hook 识别接入 ApkSignUtil 替代原有 PackageManager 签名读取方法彻底破解现存的 killPMkillOpen,直接取得真实重打包签名。
风控层 P1静默风控上报real_md5is_tampered 随 SDK 初始化/登录埋点上报不影响普通玩家体验,后台一眼掌握篡改包分发量,便于黑产排查与封号。
强管控 P2本地直接阻断 / 降级isPackageManagerHooked() == true 时弹窗阻断或限制付费功能防止单机游戏内购被一键破解或资源被盗用。
终极防线 P3服务端登录白名单校验登录时携带真实签名 Hash,由服务端私钥验签比对形成类似微信/QQ登录的安全闭环,客户端无论如何单点修改均无法绕过。