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

● CVE-2024-32896 · CVSS 7.8 · Haute
Recherche · FlawOpen

CVE-2024-32896 : la réinitialisation d'usine d'Android pouvait être interrompue avant la destruction des clés

CVE-2024-32896 (CVSS 7.8, exploitée dans des attaques limitées et ciblées) : la réinitialisation d'usine se contentait de redémarrer en mode recovery et laissait les clés de chiffrement en place jusqu'à l'effacement, si bien que quiconque tenait le téléphone pouvait interrompre le redémarrage et garder les données récupérables. Le correctif supprime toutes les clés du Keystore avant le redémarrage.

💡 Explication en Langage Simple (ELI5)

La règle d'une entreprise pour un ordinateur portable perdu est de lui envoyer l'ordre « broyer ». L'ordinateur se rend alors à la salle du broyeur, où tous les fichiers sont détruits. Mais celui qui tient l'ordinateur peut bloquer le couloir en chemin : les fichiers n'arrivent nulle part et restent intacts. Le correctif : avant de partir, l'ordinateur brûle les seules clés de son armoire verrouillée. Même si le trajet est interrompu, plus personne ne pourra jamais ouvrir l'armoire.

Concepts Clés et Termes

Réinitialisation d'usine (--wipe_data)
Effacement demandé par l'utilisateur, une app d'administration de l'appareil ou de MDM, ou un service d'effacement à distance. RecoverySystemService.rebootRecoveryWithCommand() écrit la commande --wipe_data pour recovery puis redémarre.
Bloc de contrôle du bootloader (BCB)
Petite partition (misc) où Android laisse des instructions au bootloader et à recovery ; écrite par setupOrClearBcb().
FBE et mot de passe synthétique
Chiffrement par fichier. Les clés des données utilisateur dérivent d'un mot de passe synthétique dont les blobs de protection sont liés à des clés KeyMint : détruire ces clés rend les données indéchiffrables.
KeyMint / Keystore
Service de clés adossé au matériel. AndroidKeyStoreMaintenance.deleteAllKeys() demande à chaque appareil KeyMint de supprimer toutes ses clés.

Analyse de Cause Racine

rebootRecoveryWithCommand() dans RecoverySystemService.java traitait --wipe_data en écrivant la commande dans le bloc de contrôle du bootloader puis en redémarrant, en comptant sur recovery pour effacer /data ensuite. Tant que cet effacement n'était pas terminé, les clés KeyMint protégeant le mot de passe synthétique et les clés de chiffrement DE et de métadonnées restaient intactes. Interrompre le redémarrage, ou empêcher l'effacement de s'exécuter, laissait en place les données chiffrées et leurs clés : une erreur de logique dans l'ordre des opérations. Le correctif (AOSP 8b7b2c66, bug 324321147) appelle deleteSecrets(), qui exécute AndroidKeyStoreMaintenance.deleteAllKeys(), avant le redémarrage. C'est la moitié côté framework Android du correctif de firmware Pixel suivi sous CVE-2024-29748.

Déroulement de l'Attaque Étape par Étape

Étape 1

Un effacement est demandé

Le propriétaire, une app d'administration de l'appareil ou de MDM, ou un service d'effacement à distance demande la réinitialisation d'usine, qui aboutit à rebootRecoveryWithCommand("--wipe_data ...").

Étape 2

Redémarrage sans rien détruire

setupOrClearBcb() enregistre la commande et pm.reboot(REBOOT_RECOVERY) redémarre le téléphone. À ce stade, les clés de chiffrement sont intactes.

Étape 3

L'effacement est interrompu

Une personne ayant l'appareil en main arrête le redémarrage avant recovery, par exemple en maintenant Volume bas pour tomber dans le bootloader, et l'effacement ne s'exécute jamais.

Étape 4

Les données survivent à la réinitialisation

Les données utilisateur chiffrées et les clés qui les protègent restent sur l'appareil, qui peut encore être attaqué plus tard avec des outils forensiques au lieu d'être vierge.

Code Source : Vulnérable vs Sécurisé

IMPLÉMENTATION VULNÉRABLE
// 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);
    }
}
PATCH SÉCURISÉ ET ROBUSTE
// 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);
    }
}

Liste de Contrôle de Sécurité pour l'Ingénierie

Sources