签名安全漏洞
Android 签名漏洞的共同教训是:验证器、ZIP 解析器、运行时加载器和权限授予者必须对同一份内容形成一致结论。本文不复刻历史漏洞利用代码,而是把 Master Key、FakeID、Janus 等攻击面映射到 Android 17 仍在运行的防御链:APK 签名方案选择、ZIP 条目解析、证书链/签名算法验证、完整性校验和 signer capability。
1. 攻击面分层
| 攻击面 | 历史问题 | 当前主要防线 |
|---|---|---|
| ZIP 重复条目 | Master Key | 严格 JAR/ZIP 解析、v2+ 全文件签名 |
| DEX 与 ZIP 双重解析 | Janus | v2/v3 全文件保护、最低签名方案 |
| 证书链信任错误 | FakeID | X.509/签名算法验证、v2/v3 signer 验证 |
| signer 身份替换 | 伪造更新 | SigningDetails.checkCapability、lineage |
| 密钥轮换滥用 | 旧 signer 能力过宽 | capability flags、回滚/权限分离 |
2. 当前统一验证入口
源码文件:frameworks/base/core/java/android/util/apk/ApkSignatureVerifier.java
public static ParseResult<SigningDetails> verify(
ParseInput input, String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion) {
return verifySignatures(
input, apkPath, minSignatureSchemeVersion,
true /* verifyFull */);
}
private static ParseResult<SigningDetailsWithDigests>
verifySignaturesInternal(
ParseInput input, String apkPath,
@SignatureSchemeVersion int minSignatureSchemeVersion,
boolean verifyFull) {
if (minSignatureSchemeVersion
> SignatureSchemeVersion.SIGNING_BLOCK_V4) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No signature found in package of version "
+ minSignatureSchemeVersion + " or newer");
}
try {
return verifyV4Signature(
input, apkPath, minSignatureSchemeVersion, verifyFull);
} catch (SignatureNotFoundException e) {
if (minSignatureSchemeVersion
>= SignatureSchemeVersion.SIGNING_BLOCK_V4) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v4 signature in package",
e);
}
}当前入口先尝试 v4;只有最低版本允许降级时才继续 v3、v2、v1。防御重点是“最低版本约束不能被降级路径绕过”:调用方要求 v3 时,只有 v2 的 APK 不会被接受。
3. Master Key
Master Key 类攻击利用 ZIP 允许重复文件名、而不同读取路径选择不同副本的问题。若签名验证看到合法副本、运行时加载到恶意副本,签名就失去了“代码未被替换”的意义。
Android 17 仍保留严格的 JAR/APK 读取路径;更重要的是 v2/v3 签名将签名保护范围扩展到 APK 的整体结构,攻击者对 ZIP 条目或目录布局的修改会破坏完整性摘要。
// frameworks/base/core/java/android/util/jar/StrictJarFile.java
// 通过严格的 APK/JAR 读取路径遍历签名相关条目
StrictJarFile jarFile = new StrictJarFile(
fileName,
verify,
signatureSchemeRollbackProtectionsEnforced);在 Android 17 中,不能把普通 ZIP 遍历结果当作签名验证结果;安装、解析和 dex 相关路径使用 framework 的严格读取与签名验证组合。排查重复条目时应同时检查签名方案和 APK 实际解析器,而不是只看 META-INF。
4. Janus
Janus 利用 v1 签名只覆盖 ZIP 条目摘要、而运行时可能从 APK 前缀解释 DEX 的差异。v2/v3 通过 APK Signing Block 对文件内容提供整体保护,Android 17 的 verifier 也允许调用方指定最低 scheme。
private static ParseResult<SigningDetailsWithDigests>
verifyV3AndBelowSignatures(
ParseInput input, String apkPath,
int minSignatureSchemeVersion, boolean verifyFull) {
try {
return verifyV3Signature(input, apkPath, verifyFull);
} catch (SignatureNotFoundException e) {
if (minSignatureSchemeVersion
>= SignatureSchemeVersion.SIGNING_BLOCK_V3) {
return input.error(
INSTALL_PARSE_FAILED_NO_CERTIFICATES,
"No APK Signature Scheme v3 signature in package",
e);
}
}
// 只有最低版本允许时才继续 v2/v1
return verifyV2OrV1(...);
}当前防线不是“所有 APK 都必须 v3”,而是由 target SDK/安装场景计算最低方案;一旦调用方要求 v2 或更高,v1-only 包会失败。安全审计应先确认最低 scheme 的来源,再判断是否存在降级空间。
5. FakeID
FakeID 攻击面在于把伪造的证书链当成可信身份,从而命中特权组件的证书判断。当前 v2/v3 verifier 不只提取 X.509 证书,还验证签名算法、签名值、内容摘要和 signer 结构;v3 的 Proof-of-Rotation 还要求每一级由前一级签名背书。
// frameworks/base/core/java/android/util/apk/ApkSignatureSchemeV2Verifier.java
VerifiedSigner signer = verifySigner(
signerBlock, signatureInfo.contentDigests);
verifyIntegrity(
signatureInfo, apk,
signer.contentDigests);verifySigner 验证 signer 内部签名与证书,verifyIntegrity 再根据 content digest 校验 APK 内容。证书主体名称或包名字符串不是信任根;PMS 后续使用的是验证成功后生成的 SigningDetails。
6. Lineage 防复用
源码文件:frameworks/base/core/java/android/content/pm/SigningDetails.java
public boolean checkCapability(
@NonNull SigningDetails oldDetails,
@CertCapabilities int flags) {
if (this == UNKNOWN || oldDetails == UNKNOWN) {
return false;
}
if (oldDetails.mSignatures.length > 1) {
return signaturesMatchExactly(oldDetails);
}
boolean matchFound = hasCertificate(
oldDetails.mSignatures[0], flags);
if (!matchFound) return false;
if (isV32Hybrid() || oldDetails.isV32Hybrid()) {
return checkV32HybridCapability(oldDetails, flags);
}
return true;
}历史 signer 只有被授予目标 capability 才能继续使用旧能力;多 signer 不能借单 signer lineage 放行;hybrid signer 还要防止只复用一把 key。这样可以把“证书出现在历史链中”和“证书仍被允许执行某动作”分开。
7. 能力分离
if (!parsed.checkCapability(
old, SigningDetails.CertCapabilities.INSTALLED_DATA)
&& !old.checkCapability(
parsed, SigningDetails.CertCapabilities.ROLLBACK)) {
throw new PrepareFailure(
INSTALL_FAILED_UPDATE_INCOMPATIBLE,
"New package has a different signature");
}更新使用 INSTALLED_DATA,回滚使用 ROLLBACK,签名权限使用 PERMISSION,shared UID 使用 SHARED_USER_ID。如果所有动作共享一个“签名相等”判断,密钥轮换就会把数据、权限、shared UID 和回滚信任一起扩大;能力位拆分正是当前防御的核心。
8. 当前防御
- 方案门槛:最低签名方案阻止 v1-only APK 在要求 v2/v3 的场景降级。
- 整体完整性:v2/v3 content digest 覆盖 APK 内容和结构,减少签名验证/加载器分歧。
- 证书数学验证:不以证书文本、subject 或包名作为信任依据。
- lineage 防环:历史 signer 重复或非法链会在 PoR/SigningDetails 阶段失败。
- 能力隔离:更新、回滚、权限、shared UID 使用不同 capability。
- 安装时再次比较:解析出
SigningDetails后仍要与已安装包、系统包和 shared UID 状态比对。
9. 漏洞排查方法
- 先确定攻击面是 ZIP 解析、签名块、证书链、lineage 还是运行时组件权限。
- 检查 APK 实际使用的最高签名方案和调用方要求的最低方案。
- 比较验证器生成的
SigningDetails,不要只读取META-INF文件名。 - 对更新攻击检查
INSTALLED_DATA/ROLLBACK,对权限提升检查PERMISSION。 - 对 shared UID 或 platform key 检查
SHARED_USER_ID和 platform signer 关系。 - 若涉及增量安装,另外检查 v4/idsig/fs-verity,不要把它与 APK signer 混为一谈。
10. 源码阅读路线
ApkSignatureVerifier:方案选择与最低版本。- v2/v3 verifier:签名块、内容摘要和证书链验证。
StrictJarFile:v1 ZIP/JAR 解析和历史重复条目防御。SigningDetails:lineage、capability 和 hybrid signer 约束。InstallPackageHelper/PackageManagerServiceUtils.verifySignatures:安装更新消费。AppIdPermissionPolicy:签名权限消费。
历史漏洞留下的关键经验是:签名验证必须保护“实际被解释和执行的内容”,证书链必须有密码学背书,密钥轮换必须按动作拆分能力。Android 17 的多层 verifier 和 capability 判断,正是这些防御原则在源码中的具体落点。
