flawopen.com/Teardowns/cve-2024-32896-android-factory-reset-wipe-bypass

● CVE-2024-32896 · CVSS 7.8 · 高危
安全研究 · FlawOpen

CVE-2024-32896:Android 恢复出厂设置可在密钥销毁前被打断

CVE-2024-32896(CVSS 7.8,已被用于有限的定向攻击):恢复出厂设置只是重启进入 recovery,在擦除真正执行前加密密钥都原封不动,因此手里拿着手机的人可以打断重启,让数据保持可恢复。修复会在重启前删除所有 Keystore 密钥。

💡 通俗易懂的原理解析 (ELI5)

某公司对丢失笔记本电脑的规定是:给它发一条“粉碎”指令。笔记本随后走向碎纸间,所有文件都会在那里销毁。但拿着笔记本的人可以在半路堵住走廊,文件哪儿也去不了,完好无损。修复办法是:出发之前,笔记本先把它那只上锁文件柜仅有的钥匙烧掉。就算半路被拦住,也再没人能打开那个柜子。

核心概念与专有名词

恢复出厂设置(--wipe_data)
由用户、设备管理员或 MDM 应用、或远程擦除服务发起的擦除。RecoverySystemService.rebootRecoveryWithCommand() 为 recovery 写入 --wipe_data 命令后重启。
引导加载程序控制块(BCB)
一个小分区(misc),Android 在其中给引导加载程序和 recovery 留下指令,由 setupOrClearBcb() 写入。
FBE 与合成密码
基于文件的加密。用户数据密钥由合成密码派生,而合成密码的保护数据块又绑定到 KeyMint 密钥;销毁这些密钥,数据就无法解密。
KeyMint / Keystore
由硬件支撑的密钥服务。AndroidKeyStoreMaintenance.deleteAllKeys() 会要求每个 KeyMint 设备删除其全部密钥。

根本原因剖析 (Root Cause)

RecoverySystemService.java 中的 rebootRecoveryWithCommand() 处理 --wipe_data 的方式是把命令写入引导加载程序控制块然后重启,指望 recovery 随后擦除 /data。在擦除完成之前,保护合成密码的 KeyMint 密钥以及 DE 和元数据加密密钥都完好无损。打断重启或阻止擦除运行,就会让加密数据及其密钥都留在设备上——这是操作顺序上的逻辑错误。修复(AOSP 8b7b2c66,bug 324321147)在重启前调用 deleteSecrets(),执行 AndroidKeyStoreMaintenance.deleteAllKeys()。它是编号为 CVE-2024-29748 的 Pixel 固件修复在 Android 框架层的另一半。

攻击执行流程分解

步骤 1

发起擦除

机主、设备管理员或 MDM 应用、或远程擦除服务请求恢复出厂设置,最终调用 rebootRecoveryWithCommand("--wipe_data ...")。

步骤 2

重启,但什么都没销毁

setupOrClearBcb() 保存命令,pm.reboot(REBOOT_RECOVERY) 重启手机。此时加密密钥完全没有被动过。

步骤 3

擦除被打断

实际拿着设备的人在进入 recovery 之前阻止重启,例如按住音量减键进入引导加载程序,擦除从未执行。

步骤 4

数据熬过了重置

加密的用户数据和保护它的密钥都还在设备上,之后仍可用取证工具攻击,而不是已被清空。

源代码对比:漏洞与安全实现

存在漏洞的实现
// services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
    if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");
    synchronized (sRequestLock) {
        if (!setupOrClearBcb(true, command)) {
            return;
        }

        // BUG: for "--wipe_data" nothing is destroyed yet. The keys that
        // protect the user's encrypted data survive until recovery runs the
        // wipe. If the reboot is interrupted or the wipe is skipped, the data
        // is still recoverable.
        PowerManager pm = mInjector.getPowerManager();
        pm.reboot(PowerManager.REBOOT_RECOVERY);
    }
}
加固后的安全修复
// services/core/java/com/android/server/recoverysystem/RecoverySystemService.java
static final String RECOVERY_WIPE_DATA_COMMAND = "--wipe_data";

@Override // Binder call
public void rebootRecoveryWithCommand(String command) {
    if (DEBUG) Slog.d(TAG, "rebootRecoveryWithCommand: [" + command + "]");

    boolean isForcedWipe = command != null && command.contains(RECOVERY_WIPE_DATA_COMMAND);
    synchronized (sRequestLock) {
        if (!setupOrClearBcb(true, command)) {
            return;
        }

        // FIX: destroy the keys first. Deleting every KeyMint key (including
        // the synthetic-password protector keys and the keys protecting DE and
        // metadata encryption keys) makes FBE data unrecoverable even if the
        // wipe in recovery is interrupted or skipped.
        if (isForcedWipe) {
            deleteSecrets();
        }

        PowerManager pm = mInjector.getPowerManager();
        pm.reboot(PowerManager.REBOOT_RECOVERY);
    }
}

private static void deleteSecrets() {
    Slogf.w(TAG, "deleteSecrets");
    try {
        AndroidKeyStoreMaintenance.deleteAllKeys();
    } catch (android.security.KeyStoreException e) {
        Log.wtf(TAG, "Failed to delete all keys from keystore.", e);
    }
}

工程与系统安全加固清单

参考来源