Skip to content

签名与完整性检查绕过 ​

1. 入口信号 ​

text
repack 后安装成功但启动闪退
logcat: signature mismatch / integrity failed / invalid channel
jadx: getPackageInfo(..., GET_SIGNATURES|GET_SIGNING_CERTIFICATES)
jadx: MessageDigest.getInstance("SHA-256")
native: open/read classes.dex 或 lib*.so 后计算 hash
网络: 启动请求携带 sign_hash / cert_md5 / channel

先定位检查点,再选择 Java hook、smali patch、native patch 或签名参数重算。

2. 静态定位 ​

powershell
jadx -d exports/android/app-jadx samples/app.apk
rg -n "GET_SIGNATURES|GET_SIGNING_CERTIFICATES|SigningInfo|getInstallerPackageName|MessageDigest|SHA-256|MD5|integrity|signature" exports/android/app-jadx
apktool d samples/app.apk -o exports/android/app-apktool
rg -n "signature|digest|hash|classes.dex|base.apk|sourceDir" exports/android/app-apktool
strings exports/android/app-apktool/lib/arm64-v8a/*.so | rg -i "signature|sha256|md5|classes.dex|base.apk|integrity"

3. Java 层打点 ​

javascript
Java.perform(function () {
  const PM = Java.use("android.app.ApplicationPackageManager");
  PM.getPackageInfo.overload("java.lang.String", "int").implementation = function (pkg, flags) {
    const r = this.getPackageInfo(pkg, flags);
    if ((flags & 0x40) || (flags & 0x08000000)) {
      console.log("[sig] getPackageInfo pkg=" + pkg + " flags=0x" + flags.toString(16));
    }
    return r;
  };

  const MD = Java.use("java.security.MessageDigest");
  MD.getInstance.overload("java.lang.String").implementation = function (alg) {
    console.log("[digest] getInstance " + alg);
    return this.getInstance(alg);
  };
});

成功标志:能看到检查发生在哪个类/线程,结合堆栈定位调用者。

javascript
function printStack(tag) {
  const Exception = Java.use("java.lang.Exception");
  const Log = Java.use("android.util.Log");
  console.log(tag + "\n" + Log.getStackTraceString(Exception.$new()));
}

4. Native 层文件 hash 打点 ​

javascript
["open", "openat", "read", "fopen"].forEach(function (name) {
  const p = Module.findExportByName(null, name);
  if (!p) return;
  Interceptor.attach(p, {
    onEnter(args) {
      const path = name === "openat" ? args[1].readCString() : args[0].readCString();
      if (path && (path.indexOf("base.apk") >= 0 || path.indexOf("classes.dex") >= 0 || path.indexOf(".so") >= 0)) {
        console.log("[file] " + name + " " + path);
      }
    }
  });
});

如果后续进入 SHA256_Update / MD5_Update,继续 hook 输入长度和调用者地址:

javascript
["SHA256_Update", "MD5_Update"].forEach(function (name) {
  const p = Module.findExportByName(null, name);
  if (!p) return;
  Interceptor.attach(p, {
    onEnter(args) {
      console.log("[hash] " + name + " len=" + args[2] + " caller=" + this.returnAddress);
    }
  });
});

5. 路径分叉 ​

检查类型打点动作下一跳
APK 签名证书hook getPackageInfo / SigningInfo返回原证书摘要或 patch 比较分支
classes.dex hashhook MessageDigest / native hash重新计算白名单或 patch 结果
SO hashhook open/read + SHA256_UpdateGhidra 定位校验函数
Installer 来源hook getInstallerPackageName返回期望 installer
Play Integrity定位 nonce/request token网络验证或响应处理路径

6. 攻击链 / 工作流 ​

text
repack 后闪退 / 功能锁定
  → logcat 找 signature/hash/integrity 关键字
  → hook getPackageInfo / MessageDigest / open(base.apk)
  → 结合堆栈定位 Java 类或 native VA
  → smali patch、native patch 或返回原始摘要
  → reinstall 后复验启动、目标功能和网络请求

7. Smali patch 模式 ​

smali
# 原始:if-eqz v0, :fail
# 修改:强制进入 pass 分支
const/4 v0, 0x1
if-nez v0, :pass

记录 patch:

text
file: smali_classes*/com/vendor/Guard.smali
method: checkSignature()Z
old: if-eqz v0, :cond_fail
new: const/4 v0, 0x1
effect: repack 后启动进入主界面

8. Evidence ​

项记录内容
检查点Java 类/方法或 native VA/RVA
原始值原证书 hash、原 dex hash、期望 channel
运行证据hook 日志、调用堆栈、文件访问路径
Patchsmali diff、SO offset、原始字节/新字节
复验install、launch、目标功能、网络响应

9. MCP 工具映射 ​

步骤MCP 工具用途
动态打点android_frida_run_script捕获签名/hash/installer 调用
crypto/hash 证据android_crypto_unpack_recipe生成 MessageDigest/native hash hook
SO 定位ghidra_headless_analyze找比较分支和返回值
知识路由kb_router按 signature、integrity、repack 查下一篇

GPL-3.0 · 仅供授权环境下的学习与防御性研究使用