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() записывает команду --wipe_data для recovery и перезагружает устройство.
Блок управления загрузчиком (BCB)
Небольшой раздел (misc), где Android оставляет инструкции для загрузчика и recovery; его записывает setupOrClearBcb().
FBE и синтетический пароль
Пофайловое шифрование. Ключи пользовательских данных выводятся из синтетического пароля, защитные блобы которого привязаны к ключам KeyMint; уничтожение этих ключей делает данные нерасшифровываемыми.
KeyMint / Keystore
Аппаратно защищённая служба ключей. AndroidKeyStoreMaintenance.deleteAllKeys() просит каждое устройство KeyMint удалить все свои ключи.

Анализ первопричины

rebootRecoveryWithCommand() в RecoverySystemService.java обрабатывала --wipe_data так: записывала команду в блок управления загрузчиком и перезагружала устройство, полагаясь на то, что recovery затем сотрёт /data. Пока стирание не завершалось, ключи KeyMint, защищающие синтетический пароль и ключи шифрования DE и метаданных, оставались целыми. Прерванная перезагрузка или несработавшее стирание оставляли на месте и зашифрованные данные, и их ключи — логическая ошибка в порядке действий. Исправление (AOSP 8b7b2c66, ошибка 324321147) вызывает перед перезагрузкой deleteSecrets(), выполняющую AndroidKeyStoreMaintenance.deleteAllKeys(). Это часть Android framework в исправлении прошивки Pixel, учтённом как CVE-2024-29748.

Пошаговый сценарий атаки

Шаг 1

Запрошено стирание

Владелец, приложение администратора устройства или MDM либо сервис удалённого стирания запрашивает сброс, который заканчивается вызовом rebootRecoveryWithCommand("--wipe_data ...").

Шаг 2

Перезагрузка без уничтожения

setupOrClearBcb() сохраняет команду, а pm.reboot(REBOOT_RECOVERY) перезагружает телефон. Ключи шифрования на этом этапе не тронуты.

Шаг 3

Стирание прервано

Человек, физически владеющий устройством, останавливает перезагрузку до recovery — например, удерживая «Громкость вниз», чтобы попасть в загрузчик, — и стирание так и не запускается.

Шаг 4

Данные переживают сброс

Зашифрованные пользовательские данные и защищающие их ключи остаются на устройстве, и его позже всё ещё можно атаковать криминалистическими инструментами, а не получить чистым.

Исходный код: Уязвимый 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);
    }
}

Чек-лист по защите системы для инженеров

Источники