flawopen.com/Teardowns/cve-2024-32896-android-factory-reset-wipe-bypass
CVE-2024-32896:Android の初期化は鍵が破棄される前に中断できた
CVE-2024-32896(CVSS 7.8、限定的な標的型攻撃で悪用):初期化は recovery へ再起動するだけで、消去が実行されるまで暗号鍵はそのまま残っていました。そのため端末を手にした者は再起動を中断し、データを復元可能な状態に保てました。修正では再起動の前に Keystore の鍵をすべて削除します。
ある会社では、紛失したノートPCには「シュレッダー」命令を送る決まりです。ノートPCはシュレッダー室へ歩いて行き、そこで全ファイルが処分されます。ところが、ノートPCを押さえている人が途中の廊下をふさげば、ファイルはどこにも届かず無傷のままです。修正策は、出発する前にノートPCが鍵付き書類棚のたった一つの鍵を燃やしてしまうことです。途中で止められても、もう誰もその棚を開けられません。
主要な概念と専門用語
初期化(--wipe_data)- ユーザー、デバイス管理アプリや MDM アプリ、リモートワイプサービスが要求する消去。
RecoverySystemServiceが recovery 向けに.rebootRecoveryWithCommand()--wipe_dataコマンドを書き込んで再起動します。 ブートローダー制御ブロック(BCB)- Android がブートローダーと recovery への指示を残す小さなパーティション(
misc)。setupOrClearBcb()が書き込みます。 FBE と合成パスワード- ファイルベース暗号化。ユーザーデータの鍵は合成パスワードから導出され、その保護用データは KeyMint の鍵に結び付いています。その鍵を破棄すればデータは復号できません。
KeyMint / Keystore- ハードウェアに裏付けられた鍵サービス。
AndroidKeyStoreMaintenanceはすべての KeyMint デバイスに全鍵の削除を指示します。.deleteAllKeys()
根本原因の分析 (Root Cause)
RecoverySystemService.java の rebootRecoveryWithCommand() は、--wipe_data をブートローダー制御ブロックに書き込んで再起動するだけで、その後 recovery が /data を消去することに頼っていました。消去が終わるまで、合成パスワードを保護する KeyMint の鍵や、DE・メタデータ暗号化用の鍵は無傷でした。再起動を中断したり消去を実行させなかったりすれば、暗号化データも鍵もそのまま残ります。処理の順序に関する論理エラーです。修正(AOSP 8b7b2c66、バグ 324321147)は再起動前に deleteSecrets() を呼び、AndroidKeyStoreMaintenance を実行します。これは .deleteAllKeys()CVE-2024-29748 として追跡される Pixel ファームウェア修正の、Android フレームワーク側の半分です。
ステップ・バイ・ステップの攻撃フロー
消去が要求される
所有者、デバイス管理アプリや MDM アプリ、リモートワイプサービスが初期化を要求し、最終的に rebootRecoveryWithCommand("--wipe_data ...") が呼ばれます。
何も破棄せず再起動
setupOrClearBcb() がコマンドを保存し、pm.reboot(REBOOT_RECOVERY) が端末を再起動します。この時点で暗号鍵には手が付けられていません。
消去が中断される
端末を物理的に手にした者が、たとえば音量ダウンを押し続けてブートローダーに入るなどして recovery の手前で再起動を止め、消去は一度も実行されません。
データが初期化を生き延びる
暗号化されたユーザーデータとそれを守る鍵が端末に残るため、消去済みになる代わりに、後からフォレンジックツールで攻撃できる状態のままになります。
ソースコード比較:脆弱 vs 堅牢化
// 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 の運用では、端末が完了を報告するかクリーンな状態で再登録されたときだけ消去完了とみなし、消去済みの端末が古い状態のまま現れたら警告する。
- ✓機密データを扱う端末には強力な画面ロックの認証情報を必須にし、消去に失敗して残った暗号化データを総当たりで破られないようにする。