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

● CVE-2024-32896 · CVSS 7.8 · Alta
Pesquisa · FlawOpen

CVE-2024-32896: a redefinição de fábrica do Android podia ser interrompida antes de as chaves serem destruídas

CVE-2024-32896 (CVSS 7.8, explorada em ataques limitados e direcionados): a redefinição de fábrica apenas reiniciava no modo recovery e deixava as chaves de criptografia intactas até o apagamento rodar, então quem estivesse com o celular podia interromper a reinicialização e manter os dados recuperáveis. A correção apaga todas as chaves do Keystore antes de reiniciar.

💡 Explicação em Linguagem Simples (ELI5)

A regra de uma empresa para notebook perdido é mandar a ordem "triturar". O notebook então segue até a sala do triturador, onde todos os arquivos são destruídos. Mas quem está segurando o notebook pode bloquear o corredor no caminho, e os arquivos não chegam a lugar nenhum e continuam intactos. A correção é que, antes de sair, o notebook queima as únicas chaves do seu arquivo trancado. Mesmo que o caminho seja interrompido, ninguém nunca mais consegue abrir o arquivo.

Conceitos Centrais e Termos

Redefinição de fábrica (--wipe_data)
Apagamento pedido pelo usuário, por um app de administrador do dispositivo ou MDM, ou por um serviço de apagamento remoto. RecoverySystemService.rebootRecoveryWithCommand() grava o comando --wipe_data para o recovery e reinicia.
Bloco de controle do bootloader (BCB)
Pequena partição (misc) onde o Android deixa instruções para o bootloader e o recovery, gravada por setupOrClearBcb().
FBE e a senha sintética
Criptografia baseada em arquivos. As chaves dos dados do usuário derivam de uma senha sintética cujos blobs protetores estão presos a chaves do KeyMint; destruir essas chaves torna os dados indecifráveis.
KeyMint / Keystore
Serviço de chaves com suporte de hardware. AndroidKeyStoreMaintenance.deleteAllKeys() pede a cada dispositivo KeyMint que apague todas as suas chaves.

Análise de Causa Raiz

rebootRecoveryWithCommand() em RecoverySystemService.java tratava --wipe_data gravando o comando no bloco de controle do bootloader e reiniciando, confiando que o recovery apagaria /data depois. Até esse apagamento terminar, as chaves do KeyMint que protegem a senha sintética e as chaves de criptografia DE e de metadados continuavam intactas. Interromper a reinicialização, ou impedir que o apagamento rodasse, deixava os dados criptografados e suas chaves no lugar: um erro de lógica na ordem das operações. A correção (AOSP 8b7b2c66, bug 324321147) chama deleteSecrets(), que executa AndroidKeyStoreMaintenance.deleteAllKeys(), antes da reinicialização. É a metade do framework Android da correção de firmware do Pixel registrada como CVE-2024-29748.

Fluxo de Ataque Passo a Paso

Passo 1

Um apagamento é solicitado

O dono, um app de administrador do dispositivo ou MDM, ou um serviço de apagamento remoto pede a redefinição de fábrica, que termina em rebootRecoveryWithCommand("--wipe_data ...").

Passo 2

Reinício sem destruir nada

setupOrClearBcb() guarda o comando e pm.reboot(REBOOT_RECOVERY) reinicia o celular. Nesse ponto as chaves de criptografia não foram tocadas.

Passo 3

O apagamento é interrompido

Alguém com posse física interrompe a reinicialização antes do recovery, por exemplo segurando o botão de diminuir volume para cair no bootloader, e o apagamento nunca roda.

Passo 4

Os dados sobrevivem à redefinição

Os dados criptografados do usuário e as chaves que os protegem continuam no aparelho, que ainda pode ser atacado depois com ferramentas forenses em vez de estar limpo.

Código-Fonte: Vulnerável vs. Seguro

IMPLEMENTAÇÃO VULNERÁVEL
// 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 SEGURO E ROBUSTO
// 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);
    }
}

Lista de Verificação de Segurança para Engenharia

Fontes