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записывает команду.rebootRecoveryWithCommand()--wipe_dataдля recovery и перезагружает устройство. Блок управления загрузчиком (BCB)- Небольшой раздел (
misc), где Android оставляет инструкции для загрузчика и recovery; его записываетsetupOrClearBcb(). FBE и синтетический пароль- Пофайловое шифрование. Ключи пользовательских данных выводятся из синтетического пароля, защитные блобы которого привязаны к ключам KeyMint; уничтожение этих ключей делает данные нерасшифровываемыми.
KeyMint / Keystore- Аппаратно защищённая служба ключей.
AndroidKeyStoreMaintenanceпросит каждое устройство KeyMint удалить все свои ключи..deleteAllKeys()
Анализ первопричины
rebootRecoveryWithCommand() в RecoverySystemService.java обрабатывала --wipe_data так: записывала команду в блок управления загрузчиком и перезагружала устройство, полагаясь на то, что recovery затем сотрёт /data. Пока стирание не завершалось, ключи KeyMint, защищающие синтетический пароль и ключи шифрования DE и метаданных, оставались целыми. Прерванная перезагрузка или несработавшее стирание оставляли на месте и зашифрованные данные, и их ключи — логическая ошибка в порядке действий. Исправление (AOSP 8b7b2c66, ошибка 324321147) вызывает перед перезагрузкой deleteSecrets(), выполняющую AndroidKeyStoreMaintenance. Это часть Android framework в исправлении прошивки Pixel, учтённом как .deleteAllKeys()CVE-2024-29748.
Пошаговый сценарий атаки
Запрошено стирание
Владелец, приложение администратора устройства или 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 или новее) — они включают AOSP 8b7b2c66.
- ✓При любом разрушающем стирании сначала уничтожайте ключевой материал (crypto-shredding) и лишь затем запускайте медленное стирание, чтобы прерванное стирание не оставляло ничего полезного.
- ✓Проверяйте сценарии стирания с прерыванием — отключение питания, принудительный вход в загрузчик, пропущенный шаг recovery — и убеждайтесь, что после этого данные уже нельзя расшифровать.
- ✓В регламентах MDM считайте стирание завершённым, только когда устройство его подтвердит или заново зарегистрируется чистым, и поднимайте тревогу, если стёртое устройство появляется со старым состоянием.
- ✓На устройствах с чувствительными данными требуйте надёжные учётные данные блокировки экрана, чтобы зашифрованные данные, пережившие неудачное стирание, нельзя было подобрать перебором.