flawopen.com/Teardowns/cve-2024-32896-android-factory-reset-wipe-bypass
CVE-2024-32896:Android 恢复出厂设置可在密钥销毁前被打断
CVE-2024-32896(CVSS 7.8,已被用于有限的定向攻击):恢复出厂设置只是重启进入 recovery,在擦除真正执行前加密密钥都原封不动,因此手里拿着手机的人可以打断重启,让数据保持可恢复。修复会在重启前删除所有 Keystore 密钥。
某公司对丢失笔记本电脑的规定是:给它发一条“粉碎”指令。笔记本随后走向碎纸间,所有文件都会在那里销毁。但拿着笔记本的人可以在半路堵住走廊,文件哪儿也去不了,完好无损。修复办法是:出发之前,笔记本先把它那只上锁文件柜仅有的钥匙烧掉。就算半路被拦住,也再没人能打开那个柜子。
核心概念与专有名词
恢复出厂设置(--wipe_data)- 由用户、设备管理员或 MDM 应用、或远程擦除服务发起的擦除。
RecoverySystemService为 recovery 写入.rebootRecoveryWithCommand()--wipe_data命令后重启。 引导加载程序控制块(BCB)- 一个小分区(
misc),Android 在其中给引导加载程序和 recovery 留下指令,由setupOrClearBcb()写入。 FBE 与合成密码- 基于文件的加密。用户数据密钥由合成密码派生,而合成密码的保护数据块又绑定到 KeyMint 密钥;销毁这些密钥,数据就无法解密。
KeyMint / Keystore- 由硬件支撑的密钥服务。
AndroidKeyStoreMaintenance会要求每个 KeyMint 设备删除其全部密钥。.deleteAllKeys()
根本原因剖析 (Root Cause)
RecoverySystemService.java 中的 rebootRecoveryWithCommand() 处理 --wipe_data 的方式是把命令写入引导加载程序控制块然后重启,指望 recovery 随后擦除 /data。在擦除完成之前,保护合成密码的 KeyMint 密钥以及 DE 和元数据加密密钥都完好无损。打断重启或阻止擦除运行,就会让加密数据及其密钥都留在设备上——这是操作顺序上的逻辑错误。修复(AOSP 8b7b2c66,bug 324321147)在重启前调用 deleteSecrets(),执行 AndroidKeyStoreMaintenance。它是编号为 .deleteAllKeys()CVE-2024-29748 的 Pixel 固件修复在 Android 框架层的另一半。
攻击执行流程分解
发起擦除
机主、设备管理员或 MDM 应用、或远程擦除服务请求恢复出厂设置,最终调用 rebootRecoveryWithCommand("--wipe_data ...")。
重启,但什么都没销毁
setupOrClearBcb() 保存命令,pm.reboot(REBOOT_RECOVERY) 重启手机。此时加密密钥完全没有被动过。
擦除被打断
实际拿着设备的人在进入 recovery 之前阻止重启,例如按住音量减键进入引导加载程序,擦除从未执行。
数据熬过了重置
加密的用户数据和保护它的密钥都还在设备上,之后仍可用取证工具攻击,而不是已被清空。
源代码对比:漏洞与安全实现
// 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);
}
}
工程与系统安全加固清单
- ✓让设备保持在 Android 安全补丁级别 2024-09-01 或更高(Pixel:2024 年 6 月更新或更高),其中包含 AOSP 8b7b2c66。
- ✓任何破坏性擦除都应先销毁密钥材料(加密粉碎),再开始耗时的擦除,这样即使擦除被打断也不会留下可用的数据。
- ✓在中断条件下测试擦除流程——断电、强制进入引导加载程序、跳过 recovery 步骤——并断言之后数据已无法解密。
- ✓在 MDM 流程中,只有设备确认完成或干净地重新注册后才视为擦除完成;若已擦除的设备带着旧状态再次出现则告警。
- ✓对存有敏感数据的设备要求强锁屏凭据,这样即使擦除失败,残留的加密数据也无法被暴力破解。