Light mode

Как зашифровать апельсинку, или eBoot на Orange Pi 5

15 минут
  • #HardwareSecurity

Я люблю вытаскивать прошивки из разных устройств. Часто это нужно для дальнейшего анализа, а порой бывает просто приятно положить очередной бинарик в коллекцию. Но однажды мне захотелось пойти от обратного — попробовать защитить прошивку одноплатника Orange Pi 5 от таких, как я любопытных глаз.

Сердце этой апельсинки — RK3588S — 8-ядерный процессор от RockChip. Именно он стал основным объектом моего исследования, поэтому большая часть результатов наверняка применима не только к Orange Pi 5, но и к другим устройствам на базе RK3588S и RK3588.

Secure Boot и Encrypted Boot

Хочешь сохранить данные в тайне — шифруй! В рамках одноплатника Linux позволяет создать контейнер TrueCrypt и даже зашифровать системный диск при помощи LUKS, но злоумышленник может заразить загрузчик и вытащить ключ шифрования. Для решения этой проблемы я решил обратиться к технологиям Secure Boot и Encrypted Boot.

Secure Boot помогает предотвратить запуск неавторизованной прошивки. В основе технологии лежит асимметричная криптография: перед загрузкой проверяется подпись прошивки, а публичный ключ или его хеш сравнивается с доверенным. Без Secure Boot мы не сможем убедиться в том, что злоумышленники не внесли в код изменений, удаляющих или изменяющих алгоритмы защиты. Однако технология не защитит прошивку от считывания: это как смотреть через стекло — потрогать нельзя, но все видно.

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

Технология Encrypted Boot встречается реже, чем Secure Boot, — зачастую хранить прошивку в секрете просто бессмысленно. Но защищать ее от изменений полезно почти всегда, поэтому, если ваша система не поддерживает Encrypted Boot, можно сделать доверенное хранилище ключей и с их помощью зашифровать часть прошивки. При правильной реализации такой подход не менее надежен.

Нам нужно выбрать место для хранения ключей Secure Boot и Encrypted Boot. Для этого отлично подходят фьюзы (они же OTP — One-Time Programmable) — набор ячеек памяти в чипе, информацию в которые можно записать только один раз. Обычно они работают по принципу плавкого предохранителя (от англ. fuse): если пережечь перемычку высоким током, восстановить ее практически невозможно. Отсюда следует еще одна интересная особенность фьюзов: одноразовость записи, скорее всего, обеспечивается на уровне битов, причем только в одном направлении. Если бит — единица, его нельзя вернуть обратно в ноль, а вот любой нулевой бит можно перевести в единицу. Естественно, возможны и другие реализации, но в RK3588 все устроено так.

Рисунок1.png
Рисунок 1. Фьюзы в RK3588

Внутри RK3588 находятся 32 килобита фьюзов. Невозможность их перезаписи гарантирует, что злоумышленник не отключит Secure Boot и не сможет изменить ключ.

Как запустить апельсинку

Как и любая достаточно сложная система, Orange Pi 5 не запускается моментально. Цепочка загрузки состоит из пяти этапов.

Этап 1. Запускается Primary Program Loader — MaskROM.

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

Этап 2. MaskROM загружает Secondary Program Loader — Prebin-loader.

Его задача — инициализировать железо (trust-зону) и подготовить все для запуска третьего этапа: собственно, настоящего загрузчика. Обычно SPL загружает U-boot, но при желании его можно изменить произвольным образом и загружать что-то другое.

Этап 3. SPL загружает U-boot.

Загрузчик U-boot может запускать разные ОС, в нашем случае это Linux.

Этап 4. U-boot загружает ядро Linux.

Сперва загружаются ядро и RAM-диск, а уже потом монтируется корневой раздел. Именно здесь LUKS расшифровывает корневой раздел: в нашем случае это важный момент, поэтому мы выделяем под него отдельный этап, хотя ядро и останется неизменным.

Этап 5. Монтируется Rootfs, запускаются программы — устройство загрузилось и начинает работать.

Важно обеспечить работу Secure Boot и Encrypted Boot по всей цепочке: MaskROM проверяет SPL, SPL проверяет U-boot и т. д. Для разных звеньев можно использовать разные ключи. Причем во фьюзы достаточно записать только хеш первого публичного ключа и первый ключ шифрования, а остальные можно безопасно разместить в самой прошивке.

Отмечу, что первый этап самый сложный: в SPL или U-Boot мы можем добавить свой код, а вот в MaskROM придется довольствоваться тем, что написал производитель.

Найти то, чего нет: документация

Рисунок2.png

Так уж получилось, что производители чипов не очень-то любят делиться документацией, SDK и драйверами. Для их получения нужно подписать NDA и заказать кучу чипов для производства своего девайса, но даже тогда нет гарантии, что вам предоставят нужные документы и инструменты. Поэтому мне пришлось провести небольшое исследование:

  • В интернете нашелся репозиторий rockchip-linux с rkbin — набором бинарей прошивок и утилит от RockChip. Одна из них — rksigntool — позволяет подписывать бинарные файлы. То, что нужно для Secure Boot!
  • Из служебных прошивок самой интересной оказалась RAMBOOT — отладочная прошивка, которая загружается по USB напрямую в RAM.
  • Нашел утилиту rkdeveloptool — она позволяет отправлять сервисные команды RockChip по USB.
  • Также в интернете нашлись Datasheet и Technical Reference Manual (TRM) — полезный документ с описанием регистров хардварных устройств процессора. 
     

Поигравшись с rkbin и rkdeveloptool, я понял, что больше всего сервисных команд доступно в прошивке RAMBOOT — она загружается напрямую в RAM и позволяет выполнять разные действия с платой (например, читать Spi-флешку). Забираем!

Первый этап: Secure Boot

Поскольку проверка подписи Secure Boot происходит в MaskROM, я решил его заполучить. Из TRM я узнал, что до ремапа MaskROM доступен по адресам с 0xffff0000 по 0xffff8000. Чтобы считать ее, можно попробовать пропатчить RAMBOOT. Я написал небольшую программку на ассемблере для вывода hexdump этой области в сериал и перевел результат обратно в бинарный формат. Полученный файл был похож на армовый код. Таким образом я получил MaskROM и был готов начинать анализ.

Рисунок2_2.png
Рисунок 2. Получение MaskROM

Реверс rksigntool

Идея полноценного реверса казалась не особенно приятной, поэтому для начала я решил просто сравнить подписанный и неподписанный бинари SPL по внешним признакам:

  • Сразу бросается в глаза отличие магической константы в начале: RKSS вместо RKNS.
  • Второе отличие — блок с 0x200 по 0x600. Вместо нулей там появилась куча байтов, подозрительно смахивающих на ключ.
  • Третья часть — с 0x600 по 0x700. Видимо, в несекьюрном варианте здесь хранится хеш, а в секьюрном — подпись.
Рисунок3.png
Рисунок 3. Подписанный и неподписанный бинари

Общая структура понятна — теперь можно переходить к реверсу MaskROM.

Реверс MaskROM

Загружаем MaskROM в IDE и начинаем поиск загрузки SPL. Ищем место, где производится проверка — RKSS или RKNS. И… такого места нет: все грузится одинаково! Это странно, попробуем зайти с другой стороны.

Рисунок4.png
Рисунок 4. MaskROM одинаково обрабатывает RKSS и RKNS

Я уже говорил, что ключ шифрования или его хеш должен лежать в OTP. Значит, перед проверкой Secure Boot они должны оттуда вычитываться. В TRM работа с OTP не описана, однако в таблице памяти указаны OTP_S и OTP_NS — два контроллера фьюзов (S и NS обозначают Secure/NotSecure World). Создаем в IDA Pro сегменты под оба контроллера и ищем обращения к ним. Таким образом находим функции SECotpRead и SECotpWrite и смотрим, откуда они вызываются. Сделать это можно двумя способами:

  • найти чтение флага включенного Secure Boot, после которого идут две ветки — с проверкой подписи и без;
  • найти чтение хеша ключа — скорее всего, где-то должны читаться 32 байта подряд. 
Рисунок5.png
Рисунок 5. Функция чтения фьюзов в MaskROM

Таких мест не оказалось! Читались какие-то конфигурационные флаги, не очень похожие на SecureBoot и совсем не похожие на ключ или хеш. Пытаясь во всем разобраться, я изучил материалы о чипе и понял, что у него целых два MaskROM. Я впервые столкнулся с чем-то подобным: обычно вопрос Secure/NotSecure Boot решается с помощью if’ов, а не второй ромкой. Видимо, RockChip не жалели места под MaskROM, раз могут себе такое позволить.

Ситуация: чтобы понять, как включить Secure Boot, мне нужен второй MaskROM, а чтобы получить второй MaskROM, нужно включить Secure Boot. Рекурсия, однако!

Реверс RAMBOOT

Я рассудил, что должна существовать команда, включающая Secure Boot. В rkdeveloptool ее нет, поэтому реверсим еще одно богатое на команды место — RAMBOOT. Поскольку включение Secure Boot — это, скорее всего, запись фьюзов, начать стоит именно с поиска этих функций.

Как и в MaskROM, ищем обращения к контроллеру OTP и находим функции RkOTPRead/Write. И тут мне повезло: RkOTPWrite вызывается только из SecureWriteOTP, а она очень похожа на включение Secure Boot. Сперва она считала хеш от области заголовка с 0x200 по 0x430 байт — именно в том месте, где, по моим предположениям, находился ключ. Потом полученный ключ записался в OTP, а следом за ним — и флаги. Более того, функция содержала множество отладочных сообщений, не оставляющих сомнений в ее предназначении.

Рисунок6.png
Рисунок 6. Функции SecureInit и SecureWriteOTP

Потеря первой апельсинки

Уровнем выше SecureWriteOTP стояла функция SecureInit, также вызывающая SecureCheckHeader. SecureCheckHeader проверяет правильность подписи прошивки и тем самым обеспечивает защиту от записи неправильного ключа во фьюзы. Быстро понять, какой именно командой можно вызвать SecureInit, у меня не получилось, поэтому я выбрал простой и надежный путь — пропатчить RAMBOOT, чтоб он дергал SecureCheckHeader и SecureWriteOTP.

И тут усталость и желание поскорее добиться результата сыграли со мной злую шутку. Я забыл добавить проверку возвращаемого значения SecureCheckHeader (впрочем, я был уверен, что правильно подписал прошивку). Несмотря на то, что проверка не прошла, флаги и хеш записались во фьюзы.

После этого апельсинка запускала только MaskROM, а дальше грузиться отказывалась. Похоже, SecureBoot включился, но с неверными параметрами. Это было невозможно исправить, и мне пришлось взять новую плату... Если будете повторять дома, помните: фьюзы не прощают ошибок ;)

Рисунок7.png
Рисунок 7. Почему окирпичилась апельсинка

Почему же я потерял апельсинку? Первое предположение — неверное значение глобальной переменной some flag. Пишем очередной маленький патч для RAMBOOT и выясняем: some flag — поднят. Я решил опустить его и на этот раз добавил проверку возвращаемых значений. SecureCheckHeader вернул 0, фьюзы записались без ошибок. На этот раз апельсинка снова осталась в MaskROM, но подписанный RAMBOOT приняла. Это успех!

Рисунок8.png
Рисунок 8. Запись фьюзов

Для полной уверенности я дополнительно проверил, что прошивки, подписанные не тем ключом, не принимаются (мало ли что я там включил). SecureBoot наконец заработал.

Если будете повторять дома, помните: фьюзы не прощают ошибок ;)

Подпись SPL

Грузить только RAMBOOT неинтересно, поэтому пора было учиться подписывать SPL. Это можно сделать с помощью того же rksigntool, только флаги будут немного другими. Но тут появляется важный нюанс: MaskROM загружается с первого загрузчика, который находит во внешней памяти. Если загрузчик не проходит проверку Secure Boot, RK3588 остается в MaskROM. Обычно на Orange Pi есть U-boot на SPI-флешке, а она идет в списке источников раньше, чем SD-карта. Из-за этого корректно подписанный U-boot SPL с microSD не загружается, так как на SPI есть неподписанный U-boot SPL.

Для простоты я затирал SPI flash через rkdeveloptool (мне удобнее писать на microSD), но ничто не мешает вам иметь подписанный U-Boot на SPI и грузиться с нее. Или же запретить загрузку с SPI через прошивку определенных фьюзов.

После затирки SPI я вставил microSD и запустил апельсинку. И тут она говорит, что U-boot не подписан и дальше работать она не собирается, поскольку включен Secure Boot. Но это не значит, что подпись SPL не верна. Мы знаем, что MaskROM, если ему не нравится подпись, ничего не выводит, а просто не грузит SPL. Значит, это сообщение выводит U-boot SPL…

Secure Boot в U-Boot

Начинаем разбираться, как устроен Secure Boot в U-Boot. Первый шаг — забыть про готовые бинари и научиться самостоятельно собирать U-boot. Скачиваем репозиторий с U-boot для Orange Pi и собираем defconfig для RK3588. Fit signature по-прежнему выключен, но теперь это можно исправить. Включаем `CONFIG_FIT_SIGNATURE` и `CONFIG_SPL_FIT_SIGNATURE` и получаем новую ошибку: теперь SPL пишет, что U-boot не подписан, и не грузит его. Конечно, можно пропатчить проверку подписи так, чтобы она всегда возвращала ОК, но хотелось ведь не этого.

Тогда я решил углубиться в процесс сборки бинарей. За сборку финального образа отвечала утилита mkimage, которая вроде как умеет подписывать бинарные файлы. Но оказалось, что U-boot SPL берет ключ подписи не из фьюзов, а из своего конфига. Положить его туда может mkimage, но в моем случае что-то пошло не так и SPL не видел ключ. В документации U-boot я не нашел ответов, так что пришлось действовать вслепую — немного менять код и добавлять отладочные сообщения. Так я узнал: ключи-то там есть, но формат не тот.

RK3588 хочет иметь экспоненту как 2048-битное число и еще пару констант для ускорения вычислений. В идеале было бы сразу разобраться, как автоматически класть ключи в нужном формате, но я решил, что проще пока сделать это руками через fdtput. После этого я смог загрузить подписанный U-boot, однако теперь он не видел Linux. Оказалось, что U-Boot умеет проверять подпись только в формате FIT (Flattend Image Tree).

Что ж, для сборки FIT нужен конфиг с описанием: какие бинари куда и как грузить. За основу можно взять конфиг от U-boot и найти значения параметров в коде или подобрать их опытным путем. Также потребуются бинари ядра, RAM-диска и Flat Device Tree. Все это можно найти в разделе /boot (что логично). Набор бинарных файлов не должен зависеть от формата. Также отмечу, что мне пришлось разжать RAM-диск — с загрузкой сжатого возникали проблемы.

Еще несколько важных моментов:

  • Параметры ядра раньше генерировались из OrangePiEnv, а теперь их необходимо в явном виде прописывать в /choose/bootcmd. Сделать это можно через fdtput.
  • Нужно убрать /boot из fstab, а его содержимое перенести в основной раздел.
  • Напоследок необходимо переименовать раздел bootfs в boot (U-boot ищет его по метке).

После этого система будет грузиться, но с патчем, убирающим проверку Secure Boot — ведь ядро пока не подписано. Подписывается оно также через mkimage — при этом необходимо, чтобы утилита положила конфиг и бинари отдельно, иначе проверка подписи на устройстве не пройдет.

Бонус: когда я это сделал, ключи автоматически легли в FIT U-boot в рокчиповском формате. Впрочем, потраченное время не пропало даром: знания, полученные при изучении кода, очень помогли мне в дальнейшем.

На этом первый этап исследования можно считать завершенным:

  • Secure Boot включен и работает.
  • Цепочка доверия выглядит как MaskROM -> SPL -> U-Boot -> Kernel.
  • На каждом этапе используется RSA-2048, все три ключа могут быть разными.
  • Ключи подписи ядра и U-Boot можно менять от прошивки к прошивке, ключ подписи SPL записывается в SOC раз и навсегда.
  • RootFS не защищена от изменения, но это нормально для Secure Boot.
  • RootFS содержит динамично меняющиеся файлы, их трудно подписать приватным ключом с другого устройства.
  • Если защита от изменения все-таки нужна, потребуется отделить изменяемое от неизменяемого и воспользоваться, например, dm-verity.

Второй этап: Encrypted Boot

Наличие Secure Boot казалось мне чем-то само собой разумеющимся, а вот в существовании Encrypted Boot я не был уверен, потому решил сделать свой.

В чем состоит идея Encrypted Boot

В свободное место во фьюзах можно записать ключ шифрования -> SPL расшифровывает U-boot -> U-boot расшифровывает ядро и RAM-диск -> в RAM-диске лежит ключ от LUKS.

Вы наверняка обратили внимание, что в этой схеме нет MaskROM, а SPL хранится в незашифрованном виде. Этот подход заметно снижает объем работы без ущерба для защищенности системы: SPL не содержит критической информации, поэтому его раскрытие не критично. Конечно, зная конкретную версию U-boot SPL, проще искать в ней уязвимости, но это довольно слабая подсказка.

Encrypted Boot в U-boot

Я не заметил флага, который позволял бы включить Encrypted Boot в U-boot, и решил написать немного своего кода. Для начала хотел реализовать максимально простое, но все-таки шифрование — чтобы убедиться, что мой код вызывается где надо, прошивка расшифровывается и т. д.

В качестве первого POC я решил взять XOR и захардкодить ключ. Естественно, это предельно небезопасное решение, зато его можно реализовать за пару минут и точно убедиться: если прошивка не загрузилась, значит, не вызвался модуль расшифровки.

Модуль расшифровки

Разбираясь с Secure Boot, я заметил, что бинари могут быть сжатыми, а сжатие некоторым образом напоминает шифрование. Поэтому я решил добавить в board fit image post processing модуль расшифровки на основе fit decomp image. У бинаря внутри FIT появился параметр, указывающий тип шифрования: NONE, XOR или AES (но обработку последнего я пока не писал). Сама же функция расшифровки была довольно примитивной (см. рис. 9).

Рисунок9.png
Рисунок 9. Функция расшифровки

Проверив все на незашифрованной прошивке, я поксорил бинари и установил значение encryption type на XOR. Прошивка загрузилась, а значит, расшифровка работала. Казалось бы, можно переходить к AES, но сначала предстояло решить еще один вопрос — спрятать ключ в OTP.

Запись и хранение ключей

Для этого нам нужно прочитать все фьюзы и узнать, есть ли там свободное место. В U-Boot есть фьюз-драйвер, однако он позволяет читать только некоторые из них. Тогда я решил пропатчить драйвер, благо он опенсорсный. Ну или почти… Очевидно, создатели не хотели делиться исходниками и выложили дизассемблер скомпилированного драйвера. Фу такими быть — дизассемблировано отвратительно, но я решил оставить это на их совести и воспользоваться IDA Pro, такой вот опенсорс :) Находим проверку адреса и убираем ее.

Рисунок10.png
Рисунок 10. Патчим драйвер

Теперь мы можем прочитать все фьюзы, добавив чуть-чуть кода в SPL (на этот раз на C++). Выбираем понравившееся место и пробуем записать туда ключ. Еще раз напомню: будьте осторожны при записи фьюзов! Не забывайте, что адресация местами идет в DWORD (32 бита), а старшая часть отбрасывается. При этом в начале расположены крайне важные фьюзы: я не знаю, за что именно они отвечают, но однажды я забыл поделить адрес на 4 и запись попала в них. После этого чип не запускал даже MaskROM :(

Чтение ключа из фьюзов

Теперь, когда ключ успешно записан, попробуем заставить модуль шифрования читать его из фьюзов. Получилось? Да, но наполовину: U-boot грузится, а Linux нет. Это связанно с тем, что SPL живет в SecureWorld и имеет доступ к SecureOTP, а U-boot — в NonSecure World и читать/писать SecureOTP не может. Я потратил некоторое время, чтобы разобраться в работе NonSecureOTP, но у меня ничего внятного не получилось в итоге решил пойти более простым путем :)

Помните, где лежит ключ для подписи Linux? Правильно, в U-boot-fit. Это безопасно, поскольку U-boot-fit подписан на ключе из SPL-fit, а он, в свою очередь, подписан на ключе из фьюзов. Это так называемая Chain of trust. А что, если хранить ключи шифрования таким же образом? Ключ для Linux положить в U-boot-fit, а сам U-boot-fit зашифровать на ключе из фьюзов — безопасность в этом случае не пострадает.

Здесь мне пригодилась возможность задавать разные методы шифрования: XOR и AES поменялись на OTP и FIT и стали указывать, где брать ключ. Таким образом решается проблема с невозможностью чтения ключа из фьюзов.

Метод шифрования

Итак, прошивка шифруется и расшифровывается, ключи хранятся где нужно. Осталось поменять метод шифрования: естественно, шифровать надо не XOR’ом, а AES’ом. Обычно в таких задачах используется AES-CTR, но я предпочитаю AES-GCM: это почти то же самое плюс бонус — проверка целостности. Отчасти она дублирует Secure Boot, но раз это бесплатно, почему бы и нет.

Конечно, я НЕ хотел писать свою реализацию AES, и не столько из-за лени — такие поделки обычно получаются уязвимыми. Поэтому я стал смотреть в сторону встроенного блока криптографии в RK3588. Я уже заметил, что U-boot использует аппаратную криптографию для проверки подписи, поэтому представлял, что нужно делать. К сожалению, в драйвере было только шифрование AES-GCM, но для него не так уж сложно сделать Decrypt из Encrypt. Поскольку Encrypt нигде не использовался, я просто поменял бит, указывающий хардвари направление, и добавил в конце проверку тега.

Тем не менее заголовки остались незашифрованными, что несколько портило ощущения от проделанной работы. Хотелось, чтобы по прошивке вообще не было понятно, что происходит внутри. Для этого нужно зашифровать итоговый образ, а не бинари в его составе.

Рисунок11.png
Рисунок 11. Разница между шифрованием самого итогового образа и бинарей в его составе

Шифровка готового образа

Я перенес расшифровку ближе к началу: от постпроцессинга бинарей к чтению microSD. Процесс немного усложнился, ведь теперь размер бинаря лежал в зашифрованном заголовке. Это сподвигло меня реализовать возможность частичной расшифровки.

Чтение загрузочного образа проходит в три этапа:

  • заголовок заголовка;
  • заголовок;
  • собственно прошивка.

Возможно, здесь стоило бы вернуться к AES-CTR (там возможность частичной расшифровки реализована из коробки), но я уже привык к двойной проверке целостности и не хотел от нее отказываться. Я тоже разделил шифрование на три части (каждая со своим тегом целостности) и стал подмешивать теги целостности предыдущих частей в качестве AAD. Таким образом обеспечивается зацепление частей, и злоумышленник не сможет взять части тегов от разных версий прошивок и собрать из них свою (этим вряд ли можно чего-то добиться, но все же).

Защита Rootfs

Подписывать Rootfs — дело сомнительное. Обычно она меняется от запуска к запуску, поэтому я решил ограничиться шифрованием. Да, можно сделать Rootfs read-only и воспользоваться чем-то вроде dm-verity, но это уже задача из области настройки Linux, которая выходит за рамки моего исследования.

Изначально я думал, что фьюзы можно читать с помощью LUKS, но обнаружил, что начиная с U-Boot, у меня нет к ним доступа. Напрашивалась идея использовать ключ в виде файла (однажды я уже так делал), оставалось только решить, как запихнуть его в RAM-диск.

Ставим LUKS, пересобираем ramfs и используем костыль: ramfs лежит в подписанном FIT, поэтому можно взять непрошитую апельсинку без защиты, пересобрать RAM-диск на ней и уже потом упаковать его в FIT. В результате получаем полностью защищенную прошивку.

Второй MaskROM

Под конец во мне разыгралось любопытство, и я решил заполучить два MaskROM. Оказалось, что старый код работает: достаточно просто запустить его на апельсинке со включенным Secure Boot. Плюс потребовалось подписать пропатченный RAMBOOT. Затем я загрузил его в IDA Pro и посмотрел, как происходит загрузка и проверка подписи. 
 

Далее я наткнулся на интересный момент: один из битов заголовка решает, в какую ветку мы пойдем. Разница между ними в том, как используется криптодвижок. Сверившись с TRM, я понял, что в одной из веток используется SHA-256, а в другой — AES-CTR + SHA-256. На что это похоже? Конечно на eBoot! У нас есть AES-CTR, а Nonce берется из основного заголовка. Также в регистр IV добавляется Counter из заголовка части — это позволяет расшифровывать части по отдельности. Здесь необходимо подобрать правильный формат (Big/Little-endian и т. д.), но в начале стоит оставить только нули — они в любом формате должны выглядеть одинаково.

Рисунок12.png
Рисунок 12. eBoot в секретном MaskROM

Судя по флагам криптодвижка, ключ берется из Keylader — специального блока для безопасного хранения и использования ключей без загрузки в RAM-память. Смотрим на функцию loadkey и видим, что она читает фьюзы, причем в нулевой адрес. Видимо, так осуществляется загрузка ключа из OTP в Keylader. Судя по параметрам, читаются четыре дворда по адресу 0x20, что как раз совпадает с размером ключа.

Когда я вычитывал все фьюзы, в этом месте были нули. Стало ясно, что и где должно быть для реализации eBoot — осталось только подобрать формат и пересобрать прошивку (причем здесь rksigntool уже не поможет).

Сборщик прошивки

Хорошая новость в том, что подпись вполне стандартная и написать свой сборщик не так уж трудно. Сначала идет заголовок прошивки, затем — заголовки бинарей, ключ, подпись и сами бинари. Проверяем сборщик на прошивке без шифрования и переходим к делу.

Единственная проблема: при записи во фьюзы в ключе необходимо развернуть порядок байтов в двордах. Видимо, в какой-то момент происходит смена Big/Little-endian (хотя я всегда считал, что ключ AES — это массив байтов и там таких приколов быть не должно).

Наконец, мы получаем полноценный eBoot на апельсинке! Для единообразия можно перенести ключ от U-boot в SPL — теперь он зашифрован.

Рисунок13.png
Рисунок 13. Общая схема работы системы

***

Скачать упаковщик SPL с поддержкой Encrypted Boot можно с моего Github. Модифицированный U-boot и бинари для прошивки фьюзов появятся чуть позже — когда я найду время их отладить и убедиться, что они не окирпичат ваши апельсинки ;)

Мы дěлаем Positive Research → для ИБ-экспертов, бизнеса и всех, кто интересуется ✽ {кибербезопасностью}