第六板块:Android 安全与权限体系 | 第二十篇:应用签名、权限机制与 PackageManagerService 的安全校验

37 阅读7分钟

第六板块:Android 安全与权限体系 | 第二十篇:应用签名、权限机制与 PackageManagerService 的安全校验

所属板块:第六板块 — Android 安全与权限体系

前置知识:第十九篇中的 SELinux 强制访问控制、Linux DAC 权限、应用沙箱机制、Binder IPC

本篇定位:这是 Android 应用生态的信任锚点。如果说 SELinux 是系统内部的防弹衣,那么 应用签名 就是应用之间的身份证与防伪码。本篇将彻底拆解 APK 签名方案(v1/v2/v3/v4)的密码学原理证书链(Certificate Chain)的信任模型PackageManagerService (PMS) 的安装校验流水线权限的动态授予与 UID 映射SharedUserId 的信任边界。我们将深入 JarSignerAPK Signature Scheme v2PMS 的 Installer 服务,揭示 Android 如何从数学上保证应用来源的真实性和完整性。


1. 核心结论先行(Thesis Statement)

Android 的应用安全是一个基于公钥基础设施(PKI)的信任链

  • 应用签名的本质数字指纹。开发者使用私钥对 APK 进行签名,系统使用公钥验证 APK 是否被篡改,以及是否来自同一开发者。
  • PMS 的本质系统信任的守门人。它在应用安装(Install)和升级(Update)时,执行严格的四重校验证书校验完整性校验权限兼容性校验SELinux 上下文分配
  • 权限的本质能力的映射。系统将 AndroidManifest.xml 中声明的权限,映射为 Linux 的 GID(Group ID)。当应用请求权限时,PMS 将其加入对应的 GID,内核通过 DAC 控制访问。
  • SharedUserId 的本质打破沙箱的协议。它允许两个应用运行在同一个 UID 下,共享数据和进程空间,但这要求两者必须有相同的签名证书

2. APK 签名方案演进史

2.1 签名方案的对比

Android 支持多种签名方案,新方案是为了解决旧方案的安全和性能问题。

方案引入版本签名位置完整性保护学术定义
v1 (Jar Signature)API 1META-INF/MANIFEST.MF仅文件内容基于 Java Jar 签名,只对文件内容做摘要,不保护 ZIP 元数据。易被篡改(ZipBomb)。
v2 (Full-file Sign)API 24 (N)**APK 签名块 (ECDSA/PKI)整个 APK 文件在 APK 中央插入一个签名块。任何对 APK 的修改都会破坏签名。推荐方案
v3 (Key Rotation)API 28 (P)**APK 签名块整个 APK 文件支持密钥轮换。允许开发者在签名块中证明新旧密钥的关联性,解决密钥丢失问题。
v4 (Streaming Sign)API 30 (R)**.idsig 文件仅 APK 内容支持增量安装。签名存储在单独的文件中,方便流式传输。

2.2 v2 签名方案的深度解析

v2 是目前最主流的方案,它改变了签名的结构。

学术定义

  • APK 签名分块(APK Signing Block):位于 ZIP 中央目录之前,文件内容之后。包含签名数据(证书、公钥、算法)。
  • 完整性覆盖:v2 对 APK 的 所有字节(除了签名块本身)计算摘要。这包括了 ZIP 的 Local File Header、Central Directory 等元数据。
graph LR
    subgraph APK ["APK 文件结构"]
        direction LR
        A["Contents of ZIP entries (文件内容)"]
        B["APK Signing Block (v2/v3 签名)"]
        C["ZIP Central Directory (目录)"]
        D["ZIP End of Central Directory (结尾)"]
    end

    A --> B
    B --> C
    C --> D

校验流程

  1. PMS 读取 ZIP End of Central Directory。
  2. 定位 APK Signing Block。
  3. 提取签名证书和公钥。
  4. 对整个 APK 文件(除签名块外)计算摘要。
  5. 使用公钥解密签名,比对摘要。

3. 证书与密钥体系

3.1 密钥对与证书

组件学术定义作用
私钥 (Private Key)RSA/ECDSA 密钥开发者持有,绝对保密。用于对 APK 进行签名。
公钥 (Public Key)私钥对应的公开部分包含在证书中。系统用它验证签名。
证书 (Certificate)X.509 标准包含公钥、颁发者、有效期、所有者信息。

3.2 证书指纹(Fingerprint)

证书指纹是证书的哈希值,用于唯一标识一个证书。

学术定义

  • SHA-256: 目前的标准。例如 SHA256: AB:12:CD:34...
  • MD5/SHA1: 已废弃,存在碰撞风险。

PMS 的信任逻辑

  • 安装时:PMS 计算证书指纹,存入 /data/system/packages.xml
  • 升级时:PMS 比对旧证书指纹和新证书指纹。
    • 相同:允许升级(同一开发者)。
    • 不同:拒绝升级(可能被篡改或冒充)。

4. PackageManagerService (PMS) 的安装校验流水线

4.1 安装流程全景图

sequenceDiagram
    participant User as 用户/应用商店
    participant PMS as PackageManagerService
    participant Installer as Installer Service
    participant Verifier as PackageVerifier
    participant SELinux as SELinux Policy

    User->>PMS: installPackage()
    PMS->>Verifier: 验证 APK 签名
    Verifier->>Verifier: 1. 证书有效性
    Verifier->>Verifier: 2. 完整性校验 (v2/v3)
    Verifier-->>PMS: 验证通过/失败
    alt 验证失败
        PMS-->>User: 安装失败 (INSTALL_FAILED_INVALID_APK)
    else 验证通过
        PMS->>PMS: 解析 Manifest
        PMS->>PMS: 检查权限声明
        PMS->>PMS: 分配 UID 和 GID
        PMS->>SELinux: 分配安全上下文 (seapp_contexts)
        SELinux-->>PMS: 上下文确认
        PMS->>Installer: 复制 APK 到 /data/app
        Installer->>Installer: 解压原生库 (.so)
        Installer->>Installer: 优化 Dex (dex2oat)
        PMS->>PMS: 更新 packages.xml
        PMS-->>User: 安装成功
    end

4.2 关键校验源码解析

1. 签名验证 (PackageParser.java)

// frameworks/base/core/java/android/content/pm/PackageParser.java
private static void collectCertificates(Package pkg, File apkFile, int flags) {
    // 1. 尝试 v2/v3 签名
    ApkSignatureSchemeV2Verifier.verify(apkFile);
    // 2. 如果失败,尝试 v1 签名
    JarVerifier.verify(apkFile);
}

2. 权限与 UID 映射 (PackageManagerService.java)

// 分配 UID
int uid = mSettings.getUidForSharedUser(pkg.mSharedUserId);
if (uid < 0) {
    uid = mSettings.newUserId(pkg);
}
pkg.applicationInfo.uid = uid;

// 映射权限到 GID
for (String perm : pkg.requestedPermissions) {
    if (perm.equals("android.permission.INTERNET")) {
        // 将应用加入 inet GID
        pkg.applicationInfo.gids = appendGid(pkg.applicationInfo.gids, GID_INET);
    }
}

5. 权限机制深度解析

5.1 权限的分类与等级

权限等级声明方式授予时机学术定义
Normalnormal安装时自动授予低风险,不影响隐私(如 INTERNET)。
Dangerousdangerous运行时请求高风险,涉及隐私(如 READ_CONTACTS)。
Signaturesignature安装时自动授予仅当请求者和声明者签名相同时才授予。
Systemsystem安装时自动授予仅系统应用(UID < 10000)可获得。

5.2 Runtime Permission 的动态授予

Android 6.0 (M) 引入了运行时权限。

学术定义

  • 权限组(Permission Group):将相关权限分组(如 CONTACTS 组包含读/写联系人)。用户授权一个组,组内所有权限生效。
  • 动态决策:应用调用 requestPermissions(),系统弹窗,用户选择。PMS 记录用户的选择到 /data/system/users/0/runtime-permissions.xml

5.3 SharedUserId 的信任边界

android:sharedUserId="android.uid.system"

学术定义

  • 打破沙箱:两个应用共享同一个 UID,意味着它们可以互相访问私有数据,甚至可以运行在同一个进程中。
  • 签名约束:只有签名与系统证书相同的应用才能使用 android.uid.system。这是 OEM 厂商预装应用的特权。
  • 风险:一旦应用获得 SharedUserId,它就脱离了普通应用的沙箱限制。

6. 应用更新的安全逻辑

6.1 覆盖安装(Cover Installation)

覆盖安装必须满足以下条件:

  1. 包名相同(Package Name)。
  2. 签名证书相同(Certificate Digest)。
  3. SharedUserId 兼容(如果声明了 SharedUserId,必须一致)。

6.2 降级攻击防御

学术定义

  • Version Code 校验:PMS 检查新 APK 的 versionCode 必须大于或等于旧 APK。
  • 防降级:如果攻击者试图用旧版本覆盖新版本(修复漏洞),PMS 会拒绝,除非使用 -d 参数(调试模式)。

7. 关键数据结构与存储

7.1 packages.xml

这是 PMS 的数据库,记录了所有已安装应用的信息。

<package name="com.example.app" codePath="/data/app/com.example.app-1" nativeLibraryPath="/data/app/com.example.app-1/lib" primaryCpuAbi="arm64-v8a" publicFlags="805306896" privateFlags="0" ft="186a0" it="186a0" ut="186a0" version="101" userId="10001">
    <sigs count="1">
        <cert index="1" key="308203..." />
    </sigs>
    <perms>
        <item name="android.permission.INTERNET" granted="true" flags="0" />
        <item name="android.permission.READ_CONTACTS" granted="false" flags="0" />
    </perms>
</package>

7.2 packages.list

便于人类阅读的列表,用于快速查找。

com.example.app 10001 /data/app/com.example.app-1 platform 0 1

8. 常见安全漏洞与防御

漏洞学术定义Android 防御
Janus 漏洞在 APK 前拼接 DEX 文件,利用 Zip 解析差异执行恶意代码。v2 签名保护整个 APK,无法拼接。
Master Key 漏洞构造同名文件,利用 Zip 解析漏洞欺骗签名验证。v2 签名不依赖文件名,基于文件内容。
权限提升声明 protectionLevel="signature" 的权限,诱导系统应用声明,然后调用。PMS 检查权限定义者和请求者的签名是否一致。

9. 本篇总结(Knowledge Closure)

关键点纯学术定义
应用签名的本质基于 PKI 的数字指纹,保证完整性和来源真实性。
PMS 的角色系统信任守门人,执行证书、完整性、权限、上下文的四重校验。
权限映射将 Manifest 权限映射为 Linux GID,通过 DAC 控制访问。
SharedUserId打破沙箱的协议,基于相同签名的 UID 共享。
v2 签名全文件完整性保护,防御 Zip 解析漏洞。

10. 第六板块结语

至此,第六板块:Android 安全与权限体系 已全部完结。

我们从 SELinux 的强制访问控制 出发,深入 内核 LSM 钩子,探索 安全上下文与 TE 策略,最终抵达 应用签名的 PKI 信任链PMS 的安装校验

我们揭示了 Android 安全的最高准则:“永不信任,始终验证”

下一篇预告第七板块:Android 存储体系与文件系统 | 第二十一篇:Vold 与 FUSE 存储架构