Низкоуровневая программно-техническая экспертиза iOS

Низкоуровневая программно-техническая экспертиза iOS

В данном посте мы опишем, как получить образ NAND и использовать FTL-метаданные для восстановления удаленных файлов на устройствах, использующих процессор A4.

Автор: jean

Механизмы шифрования и защиты данных файловой системы iOS теперь хорошо документированы и поддерживаются многими утилитами ПТЭ (программно-технической экспертизы или forensics). В качестве основной области хранения данных iOS-устройства используют NAND flash-память, но создание физических образов обычно имеет отношение к "dd image" логических разделов. Уровень трансляции Flash в iOS для текущих устройств имеет программную основу (реализован в iBoot и ядре), что означает, что CPU имеет прямой доступ к сырой NAND-памяти. В данном посте мы опишем, как получить образ NAND и использовать FTL-метаданные для восстановления удаленных файлов на устройствах, использующих процессор A4. Информация, представленная здесь, основана на серьезной работе по реверс-инжинирингу, выполненной командой iDroid/openiBoot.

Чтение NAND-памяти

Чип NAND-памяти состоит из множества страниц, сгрупированных в блоки. Каждая страница разделена на две части: область действительных данных и меньшую по размеру резервную область, содержащую информацию для исправления ошибок и, возможно, метаданные. Страницы можно считывать и заполнять данными, но перед внесением новых данных страница должна быть стерта. Это главное физическое ограничение NAND-хранилища. Операции стирания делаются на уровне блока: за раз должно быть стерто множество страниц. Кроме того, страницы могут поддерживать лишь ограниченное количество циклов стирания/записи до того, как появится слишком большое число ошибок, и страница станет непригодной.

iOS-устройства используют один или несколько идентичных чипов, адресуемых номером CE ("chip enable"). Фактические параметры геометрии (число CE, число блоков на CE, число страниц на блок, размер страниц и резервной области) зависят от модели устройства и общей емкости хранилища. Физический адрес составляется из CE-номера чипа и физического номера страницы (PPN) на этом чипе.

Из-за ограничений NAND в операционных системах распространены механизмы трансляции (FTL), которые позволяют использовать NAND-память как обычное блочное устройство, оптимизируя при этом ее производительность и срок службы. Главная цель FTL заключается в уменьшении операций стирания и распределения их по всем блокам. С точки зрения ПТЭ, интересным побочным эффектом здесь является то, что при перезаписи логического блока на уровне блочного устройства старые данные на физическом уровне обычно не стираются сразу. Поэтому при поиске удаленных данных работа с сырым образом NAND может быть очень полезна.

Можно прочитать сырые данные NAND с помощью openiBoot, но передача данных по USB на данный момент довольно медленна, что делает этот способ непрактичным для выгрузки содержимого всей Flash-памяти.

Начиная с iOS 3 на ram-дисках Apple iOS можно найти программу ioflashstoragetool. Данная утилита может производить множество низкоуровневых операций, касающихся flash-хранилища и может читать сырые NAND-страницы и резервные области (не производя никакого рода дешифрования). Данная функциональность предоставлялась сервисом ядра IOFlashControllerUserClient.

В iOS 5 большая часть функций, предоставлявшихся данным интерфейсом IOKit, была убрана. Чтобы создать дамп посредством этого интерфейса, мы можем загрузить ram-диск с помощью более старой, четвертой версии ядра iOS. Это отлично работает, однако в таком случае мы потеряем способность использовать имеющийся код ядра для атаки перебором более новых связок ключей iOS 5. Чтобы выгрузить NAND под пятой версией ядра iOS, мы заново реализовали часть функции IOFlashControllerUserClient::externalMethod, ответственной за функциональность чтения. Когда наш инструмент выгрузки запускается под пятой версией ядра iOS, он замещает данную функцию той, что обрабатывает селектор kIOFlashControllerReadPage.

Кроме того, мы можем установить флаг загрузки nand-disable-driver, чтобы предотвратить высокоуровневый доступ к NAND и гарантировать, что ее содержимое не изменится в ходе получения.

Уровень преобразования Flash в iOS

Уровень преобразования Flash (FTL), используемый в iOS, основан на Samsung Whimory FTL (WMR). Команда openiBoot проделала хорошую работу по его реверс-инжинирингу, так что мы можем понять используемые в нем структуры и механизмы. Код преобразования Whimory делится на два слоя: VFL и FTL.

Virtual Flash Layer (VFL) ответственен за переназначение bad-блоков и представление NAND, не содержащее ошибок, для FTL-слоя. VFL-слой знает физическую геометрию и транслирует виртуальные номера страниц, используемые FTL, в физические адреса (номер CE + номер физической страницы).

Слой FTL оперирует над VFL и предоставляет операционной системе интерфейс блочного устройства. Он транслирует номера логических страниц блочного устройства (LPN) в виртуальные номера страниц, занимается wear leveling и сборкой мусора в для блоков, содержащих устаревшие данные. На устройствах, поддерживающих аппаратное шифрование, все страницы, содержащие структуры данных, связанные с VFL и FTL, зашифрованы статическим ключом метаданных.

В различных версиях iOS использовались следующие варианты подсистем FTL:

  • iOS 1.x и 2.x : "legacy" FTL/VFL
  • iOS >= 3 : YaFTL / VSVFL
  • Начиная с iOS 4.1, некоторые из новых устройств оснащены PPN (Perfect Page New) NAND, которая использует особый PPNFTL.

PPN-устройства имеют свой собственный контроллер с прошивкой, которая может быть изменена через интерфейс IOFlashControllerUserClient, но большая часть работы FTL похоже все еще выполняется программно – с помощью YaFTL поверх нового PPNVFL.

На основании кода openiBoot мы написали минимальную read-only реализацию YaFTL/VSVFL на Питоне, чтобы получить возможность просматривать образы NAND как блочные устройства. В сочетании с реализацией HFS+ на Питоне она позволяет извлечение логических разделов для получения эквивалента dd-image. Далее нам нужно понять механизмы YaFTL, чтобы воспользоваться дополнительными данными, доступными в образе NAND.

YaFTL

YaFTL – это FTL отображения страниц, в котором логические страницы могут храниться в любом месте и в любом порядке на физическом носителе. Он довольно похож на DFTL: информация об отображении страниц (назыаемая индексными страницами) хранится во Flash-памяти и кэшируется в память при доступе. YaFTL делит виртуальное адресное пространство, представляемое слоем VFL, на суперблоки. Суперблок можно рассматривать как "строку" из физических NAND-блоков. Существует три типа суперблоков, в зависимости от типа хранимых в них страниц:
  • Контекстные страницы содержат всю информацию, необходимую для инициализации YaFTL, включая массив userTocPages, который указывает на актуальные индексные страницы. Он также хранит счетчики стирания для для wear-levelling.
  • Индексные страницы хранят указатели на страницы пользователя
  • Страницы пользователя содержат данные блочного устройства

Следующий рисунок обобщает процесс преобразования YaFTL:

В ходе нормальной работы в каждый момент лишь один суперблок каждого типа является "открытым": страницы записываются последовательно в манере log-block. Когда текущий суперблок заполнен, YaFTL находит пустой суперблок для продолжения процесса. старевшие пользовательские данные стираются только в процессе работы сборщика мусора.

Последние страницы индексного и пользовательского суперблоков используются для хранения BTOC (таблица содержимого блока). Для пользовательских блоков BTOC перечисляет логические номера страниц, хранимые в этих блоках. В индексном блоке BTOC хранит первые логические номера страниц, на которые указывает каждая из индексных страниц.

Восстановление FTL производится при загрузке, если FTL не был должным образом отмонтирован (после kernel panic или жесткой перезагрузки, например) и контекстная информация не была зафиксирована на flash-носителе. Функции восстановления FTL приходится исследовать все блоки (используя BTOC для ускорения процесса), чтобы восстановить корректный контекст.

Метаданные резервной области

iOS резервирует 12 байт каждой страницы в резервной области для хранения метаданных. Формат метаданных YaFTL описан в openiBoot:
  // Page types (as defined in the spare data "type" bitfield)
#define PAGETYPE_INDEX (0x4)
// Index block indicator #define PAGETYPE_LBN (0x10)
// User data #define PAGETYPE_FTL_CLEAN (0x20)
// FTL context (unmounted, clean) #define PAGETYPE_VFL (0x80)
// VFL context ... typedef struct { uint32_t lpn;
// Logical page number uint32_t usn;
// Update sequence number uint8_t field_8; uint8_t type;
// Page type uint16_t field_A; } __attribute__((packed)) SpareData;

Поле lpn позволяет FTL-коду проверять корректность преобразования при чтении страницы. Оно также используется в ходе процесса восстановления FTL, чтобы обнаруживать страницы в "открытых" суперблоках, которые не имеют BTOC.

В поле usn записывается глобальный порядковый номер обновления во время записи страницы. Этот номер увеличивается каждый раз при фиксации новой версии контекста FTL или когда суперблок заполнен и происходит открытие нового суперблока. Данное поле позволяет легко сортировать суперблоки по возрасту.

"Отбеливание" метаданных

Аппаратное шифрование применяется только к действительным данным страницы, т. е. не применяется к резервной области. На недавно появившихся устройствах с iOS 4 метаданные хранятся в резервной области и скремблируются в ходе процесса, называемого "отбеливанием метаданных". 12 байт структуры Sparedata подвергаются побитовому исключающему или (XOR) с псевдослучайными значениями, зависящими от физического номера страницы. Данный алгоритм можно найти в openiBoot
  static uint32_t h2fmi_hash_table[256]; ... void h2fmi_init() { ...
// This is a very simple PRNG with
// a preset seed. What are you // up to Apple? -- Ricky26
// Same as in 3GS NAND -- Bluerise uint32_t val = 0x50F4546A; for(i = 0; i < 256; i++)
{ val = (0x19660D * val) + 0x3C6EF35F; int j; for(j = 1; j < 763; j++) { val = (0x19660D * val) + 0x3C6EF35F; }
h2fmi_hash_table[i] = val; } ... } error_t h2fmi_read_single_page(...) { ... if
(h2fmi_data_whitening_enabled) { uint32_t i; for(i = 0; i < 3; i++) ((uint32_t*)_meta_ptr)[i] ^=
h2fmi_hash_table[(i + _page) % ARRAY_SIZE(h2fmi_hash_table)]; } ... }

Признак того, что отбеливание метаданных включено, находится в поле флагов специальной страницы NANDDRIVERSIGN.

Восстановление удаленных файлов

После того, как получен образ NAND и стала известна геометрия, мы можем начать поиск удаленных данных. Первый шаг – построить таблицу поиска, которая ссылается на все доступныые версии каждой логической страницы. Для этого можно использовать два метода:
  • Считать каждую резервную область в образе, чтобы найти страницы с типом PAGETYPE_LBN, и прочитать поле lpn (метод грубой силы)
  • Пробежать каждый непустой пользовательский суперблок, прочитать BTOC, где это возможно, или просканировать все страницы, если блок не полный

Когда таблица поиска построена, мы можем легко обратиться ко всем доступным версиям заданной логической страницы. Чтобы восстановить удаленные данные на разделе мы реализовали простой алгоритм, похожий на метод вырезания данных из журнала HFS:

  • Перечислить все идентификаторы файла на разделе данных в его текущем состоянии: мы используем обычную трансляцию FTL и EMF-ключ для расшифровки данных.
  • Получить местоположение текущего файла каталога и файла аттрибутов (диапазоны LBA)
  • Для каждого LBA, принадлежащего файлу каталога
    • Для каждой версии текущего LBA
      • Считать страницу для данной версии, расшифровать ее с помощью EMF-ключа
      • найти в странице записи файла каталога, файловые идентификаторы которых не присутствуют в текущем списке файловых идентификаторов (удаленные файлы)
  • Повторить тот же процесс с файлом атрибутов для нахождения ключей шифрования для ранее обнаруженных удаленных файлов (расширенные атрибуты cprotect)
  • для каждого найденного удаленного файла
    • Пробежать в цикле все возможные ключи шифрования и версии первого логического блока, пока расшифрованное содержимое не совпадет с магическим числом (обычная магия файловых заголовков). См. функцию isDecryptedCorrectly (которую можно улучшить).
    • если найдены ключ шифрования и USN первого блока, считать следующие блоки файлов, используя данный USN как ссылку. Другой метод – начать считывать страницы, начиная с первого найденного блока файлов, следуя "порядку записи" FTL: читать до конца суперблока, затем продолжить в следующем с более высоким значением USN, и так, пока не будут найдены все блоки файла.

Данный наивный алгоритм дает хорошие результаты на "статичных" файлах вроде изображений, где весь файл записывается один раз и более не обновляется. Для файлов вроде баз данных SQLite понадобится больше логики, чтобы восстановить состоятельные снимки последовательных версий. Для этого можно, например, обнаруживать акты записи в заголовок файла или отслеживать модификации в записях файла каталога (дата модификации файла).

Один из файлов, который было бы интересно восстановить – системное хранилище ключей. Если атакующий смог получить доступ к первой версии системного хранилища ключей (без установленного пароля, сразу после восстановления прошивки), он мог бы затем получить доступ ко всем ключам классов без необходимости в атаке пароля пользователя. Однако, эксплуатировать более старые версии хранилища ключей невозможно из-за второго слоя шифрования: полезная нагрузка systembag.kb шифруется ключом BAG1, который хранится в уничтожаемой области и принимает случайное значение каждый раз, когда на диск записывается новая версия файла (когда пользователь меняет свой пароль). Данный механизм, очевидно, был разработан для предотвращения подобных атак, как объяснено в выступлении "Securing application data" с Apple WWDC 2010 (Сессия 209).

Уязвимость стирания в iOS 3.x

Теперь, имея возможность читать сырое содержимое NAND, мы еще раз взглянем на механизмы iOS 3. В то время весь раздел данных (включая содержимое файлов) был зашифрован ключом EMF, который хранился (зашифрованным) в последнем логическом блоке раздела. Поскольку в это время не было уничтожаемой области, мы полагали, что данный последний логический блок управлялся FTL, как и остальная часть раздела. Путем получения NAND-образа сразу после стирания устройства под управлением iOS 3 становится возможно найти две версии данного логического блока: одну с новым EMF-ключом, сгенерированным в ходе стирания и одну, которая была перезаписана только на уровне блочного устройства. Таким образом, используя старый ключ и собрав старые страницы раздела данных (на основании USN операции стирания), возможно (частично) восстановить стертый раздел данных. Конечно, эта уязвимость исправлена в iOS 4 с помощью уничтожаемой области, которая позволяет ключам шифрования безопасно стираться.

Инструменты получения содержимого NAND и вырезания данных теперь доступны в репозитории iphone-dataprotection. Дополнительные подробности также доступны на вики. Наконец, большое спасибо Патрику Вилдту и команде openiBoot за их работу над iOS FTL, которая позволила нам создать эти инструменты.

Устали от того, что Интернет знает о вас все?

Присоединяйтесь к нам и станьте невидимыми!