stihl не предоставил(а) никакой дополнительной информации.
Эта статья появилась в процессе реверса прошивок «Яндекс Станций». Я провел реверс U-Boot процессора умной колонки и создал утилиту для его извлечения из расшифрованного загрузчика. Способ универсален для всех подобных устройств и, возможно, других гаджетов с чипом Amlogic.
Разработанный мной метод подойдет:
Для просмотра ссылки Войдиили Зарегистрируйся
Вход в консоль U-Boot происходит прерыванием по какой‑либо клавише, например Enter или Ctrl-C (классический вариант), далее уже может потребоваться авторизация. Поддерживается стандартный тип авторизации — bootstopkeysha256 (по хешу пароля). Отличное описание способов приведено в статье Для просмотра ссылки Войдиили Зарегистрируйся. «Яндекс» пошел немного дальше стандартного варианта и добавил свой способ авторизации, дав ему имя Rabbit Hole. Давай посмотрим, как он устроен.
Для начала нужно получить прошивку устройства — считать ее с помощью программатора или любым другим способом. В качестве примера будем изучать колонку Lite (YNDX-00025). Для извлечения прошивки я использовал утилиту update, разработанную Amlogic для работы со своими загрузчиками через USB. Она подходит для устройств без пароля. Загрузчик состоит из двух частей — BL2 и TPL, что соответствует стандартной структуре U-Boot.
Если ты соберешь U-Boot вручную для чипа A113X, то на выходе получишь два файла: u-boot.bin.bl2 и u-boot.bin.tpl, оба незашифрованные. У нас же, к сожалению, используются зашифрованные версии.
Расшифровать их все‑таки возможно благодаря известной Для просмотра ссылки Войдиили Зарегистрируйся.
Она позволяет расшифровывать загрузочные файлы при подключении через USB-порт без пароля. На колонках Lite, Mini2 и Max пароль по умолчанию отсутствует, хотя со временем «Яндекс» начал устанавливать его удаленно. Мне повезло: платы, с которыми я работаю, пока не защищены. Поэтому я смогу воспользоваться уязвимостью и расшифровать файл TPL, а BL2 для анализа не потребуется.
или Зарегистрируйся с дополнительными элементами, в основном касающимися аппаратных особенностей, добавляемыми производителем процессора. Первым стартует BL1 (Boot ROM), жестко прошитый в процессор с завода, — замене он не подлежит. Следом за ним стартуют другие части:
У колонок Max с процессором Amlogic S905X2 загрузчик представлен одним файлом — bootloader.bin, который включает все этапы загрузки: от начальной инициализации до запуска U-Boot. В моделях на чипах A113X (например, Lite и Mini2) загрузчик разделен на два отдельных файла — с расширениями .bl2 и .tpl.
Сигнатуры
Неполные строки
Дополнительное подтверждение я нашел в Для просмотра ссылки Войдиили Зарегистрируйся Фредерика Бассе, в которой выполняется упаковка U-Boot через стандартные инструменты Amlogic со сжатием LZ4.
Для просмотра ссылки Войдиили Зарегистрируйся
Но нет, данные не подошли, архиватор ругается на испорченный заголовок. Попытки распаковать другими утилитами или дописать заголовок вручную не увенчались успехом, метод не подходит. Но в процессе я заглянул в Для просмотра ссылки Войдиили Зарегистрируйся, с целью найти возможные способы сжатия и примерить к своим данным.
Для просмотра ссылки Войдиили Зарегистрируйся
Изучение я начал с заголовка, попытавшись определить его формат и получить полезные для распаковки данные. В разделе Doc исходников есть описание двух форматов:
Отложив изучение исходников LZ4, я продолжил поиски — и на одном из форумов Для просмотра ссылки Войдиили Зарегистрируйся на нужную зацепку: ссылку на проект Для просмотра ссылки Войди или Зарегистрируйся. Этот репозиторий, в свою очередь, указывает на три других, где лежат «разобранные» утилиты Amlogic (именно то, что нужно!):
Для просмотра ссылки Войдиили Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
Помимо стандартных библиотек, будем использовать пакет K4os.Compression.LZ4. Привожу основную часть, а Для просмотра ссылки Войдиили Зарегистрируйся ты найдешь на моем GitHub.
Работа проста, получаем размеры данных до (original_size) и после (compressed_size) сжатия, считываем сжатые данные одним большим куском и передаем на распаковку, после удачного завершения записываем буфер с распакованными данными в файл. Готово!
Для просмотра ссылки Войдиили Зарегистрируйся
Целые строки
IDA
Сразу скажу, что поправить и затолкать назад U-Boot не выйдет (даже при наличии ключей шифрования). Да, он запакуется и встроится в прошивку, но не пройдет проверку из‑за наших изменений, для того‑то и нужен Trusted Firmware. Однако польза от наших упражнений все‑таки есть: теперь можно анализировать нетиповые функции, которые в «Яндексе» добавили для своих устройств, и наконец отыскать Rabbit Hole, а может, даже и нырнуть в нее.
Для просмотра ссылки Войдиили Зарегистрируйся
Всё в автоматическом режиме не распознается, поэтому, пробежавшись по прошивке вручную, обрабатываем неопределенные участки (в основном тыкая С) и переходим к размещению образа по правильному адресу (базовый адрес), чтобы получить наиболее правильные ссылки на функции и данные. Определить верный адрес нам помогает лог загрузки колонки:
Вносим правки через меню Edit → Segments → Rebase Program и указываем нужное значение. После этих действий увидим изменения. Часть ссылок ранее была неверно интерпретирована, но теперь все должно соответствовать тому, как U-Boot расположен в памяти устройства.
Для просмотра ссылки Войдиили Зарегистрируйся
Измененный вид
Для просмотра ссылки Войдиили Зарегистрируйся
Нам повезло, и ссылка действительно есть. Сразу назовем функцию Rabbit Hole. Выглядит код несложно (всего 63 строки и 5 функций), но все функции с неизвестными именами, их нужно как‑то различать.
Для просмотра ссылки Войдиили Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
Есть два способа идентифицировать функции:
При сборке в секцию данных включаются строки с версией U-Boot и используемого компилятора. Ищем строки U-Boot и GCC (для компилятора) и находим:
или Зарегистрируйся
Компилятор
или Зарегистрируйся по сборке U-Boot. Для кросс‑компиляции предлагается такая последовательность действий:
Но версия компилятора здесь отличается от нашей, к тому же их два. Если с версией все ясно (просто поищем нашу), то с количеством требуется пояснение:
Для просмотра ссылки Войдиили Зарегистрируйся
Чтобы упростить рутинные действия при итерациях сборки, я набросал небольшой скрипт — так меньше придется стучать по клавишам. Внутри оставлены закомментированные строки на случай, если понадобится сменить версию U-Boot или использовать другую версию компилятора arm-none-eabi. В качестве основы я выбрал репозиторий Khadas — производителя отладочных плат, который активно сопровождает нужную нам ветку U-Boot и регулярно ее обновляет.
Для просмотра ссылки Войдиили ЗарегистрируйсяДля просмотра ссылки Войди или Зарегистрируйся
Для просмотра ссылки Войдиили Зарегистрируйся
Процесс может слегка затянуться: моему MacBook Air десятилетней давности потребовалось около 30 минут на каждую из прошивок (собранную и в исполнении «Яндекса»).
Для просмотра ссылки Войдиили Зарегистрируйся
После завершения индексации необходимо вернуться к прошивке «Яндекса» и повторно запустить плагин. На этот раз в строке базы сравнения следует указать SQLite-базу, созданную при индексации скомпилированного U-Boot. Внимание: не запускай повторную индексацию! Если согласиться, то плагин снова выполнит ту же работу и ты зря потратишь еще полчаса.
Загрузка базы
Отказ от повторной индексации
После сканирования плагин выдаст несколько вкладок с результатами:
Результат работы
Низкая степень совпадения
Сначала переходим на вкладку с полностью совпадающими функциями, выделяем их и импортируем в проект — после этого база IDA будет обновлена. Затем открываем вкладку Partial и с осторожностью импортируем еще часть функций — я, например, использую только те, что подсвечены зеленым. Желательно просматривать содержимое перед импортом, чтобы не затянуть лишнего. Остальные вкладки можно не трогать — они нас не интересуют.
Для просмотра ссылки Войдиили Зарегистрируйся
После импорта в исследуемом U-Boot появились осмысленные имена функций и переменных.
Для просмотра ссылки Войдиили Зарегистрируйся
Для получения более полного результата можно собрать и проиндексировать другие версии U-Boot — каждая из них потенциально может помочь обработать новые функции. Никаких гарантий нет, но иногда такой подход позволяет значительно дополнить базу.
Я ограничился одним проходом и перешел к следующему плагину — Для просмотра ссылки Войдиили Зарегистрируйся. Он ищет криптографические константы — это поможет определить алгоритмы шифрования. Найдя некоторые константы, плагин автоматически переименует их в соответствии с назначением.
Анализ первой функции и вызова sub_EE2218C() показал, что в регистре a1 формируется challenge — я сразу переименовал его соответствующим образом. Запрос каждый раз получается уникальным, поскольку для его создания используется аппаратный генератор случайных чисел. Это подтверждается содержимым функции sub_EE2218C(), где единственная строка — return MEMORY[0xFF634018];. Согласно документации на A113X, этот адрес действительно принадлежит области RNG.
Для просмотра ссылки Войдиили Зарегистрируйся
Можем предположить, что функция challenge_gen принимает указатель на буфер, в который нужно записать запрос в виде строки с шестнадцатеричным представлением. В качестве результата функция возвращает длину сформированной строки в байтах.
Итак, sub_EE0E8B0 «переворачивает» байты входного массива и складывает в другой.
Назовем ее array_bswap32. Возможно, она нам еще где‑то встретится и не придется повторно в ней копаться.
С функцией sub_EE0E8E4 нам повезло не так, она уже имеет разветвленную структуру, видно много операций над данными. При изучении листинга я зацепился взглядом за функцию subM — ее имя появилось в результате работы Diaphora. Берем это имя и пробуем искать его в исходниках U-Boot от Khadas, которые мы компилировали.
Для просмотра ссылки Войдиили Зарегистрируйся
Как видим, эта функция встречается только в файле avb_rsa.с, нам очень повезло!
Для просмотра ссылки Войдиили Зарегистрируйся
Подробно изучаем этот файл и сверяем с тем, что видим в IDA. В результате я наткнулся на вызов avb_rsa_verify. Эта функция очень напоминает начало sub_EE0E8E4.
Для просмотра ссылки Войдиили Зарегистрируйся
Очень похожая проверка в начале, но при этом тестовые сообщения не выводятся, просто возвращаются коды ошибок: всё, что не 0, — плохо. Идем дальше и находим больше сходств, поэтому возвращаемся на пару уровней вверх и даем название функции sub_EE0EBA8: rsa_yndx. Теперь функция проверки ответа выглядит так:
Здесь:
В этом же файле встречается функция с говорящим названием array_bswap32, в которой реализован алгоритм так называемого переворачивания — побайтовой перестановки в 32-битных словах. Прослеживаем, где она вызывается и какую роль играет в обработке данных.
Для просмотра ссылки Войдиили Зарегистрируйся
Собираем всё воедино. При попытке входа в консоль U-Boot Rabbit Hole генерирует текстовый запрос (challenge). Пользователь должен подписать этот запрос своей закрытой частью RSA-ключа и ввести полученную подпись обратно в консоль. Далее вызывается функция rsa_yndx, которая проверяет подпись по аналогии с avb_rsa_verify, используя алгоритм RSA-2048. В случае успешной проверки функция возвращает 0.
Мы рассмотрели основные механизмы Rabbit Hole. Остальные части довольно простые, привожу листинг c комментариями:
Вот полный алгоритм авторизации:
Пример от «Станции Max»
Мне удалось получить доступ к этой консоли — как и некоторым моим знакомым. Однако, если пароль уже установлен, попасть в консоль становится заметно сложнее.
Список команд
Зато по дороге я создал инструмент для извлечения BL33 и выяснил, как защищена прошивка. Надеюсь, и ты тоже почерпнул что‑то для себя полезное.
Я не профессиональный реверсер, скорее — увлеченный любитель. Это мой первый подобный опыт, и, если я где‑то напорол чуши, не стесняйся и расскажи мне об этом!
Разработанный мной метод подойдет:
- для «Станции Mini2» (YNDX-00020/21);
- «Станции Lite» (YNDX-00025);
- «Станции Max» (YNDX-00053/0008);
- других устройств на Amlogic S905X2 (G12) и A113X (AXG).
U-Boot login
Кто подключался к UART «Яндекс Станций», тот наверняка встречал надписи «RH challenge» и «RH response». На том загрузка колонки прекращается, девайс нужно перезагрузить или ввести ответ. Запрос всегда разный и является не чем иным, как авторизацией при входе в консоль U-Boot.Для просмотра ссылки Войди
Вход в консоль U-Boot происходит прерыванием по какой‑либо клавише, например Enter или Ctrl-C (классический вариант), далее уже может потребоваться авторизация. Поддерживается стандартный тип авторизации — bootstopkeysha256 (по хешу пароля). Отличное описание способов приведено в статье Для просмотра ссылки Войди
Для начала нужно получить прошивку устройства — считать ее с помощью программатора или любым другим способом. В качестве примера будем изучать колонку Lite (YNDX-00025). Для извлечения прошивки я использовал утилиту update, разработанную Amlogic для работы со своими загрузчиками через USB. Она подходит для устройств без пароля. Загрузчик состоит из двух частей — BL2 и TPL, что соответствует стандартной структуре U-Boot.
Если ты соберешь U-Boot вручную для чипа A113X, то на выходе получишь два файла: u-boot.bin.bl2 и u-boot.bin.tpl, оба незашифрованные. У нас же, к сожалению, используются зашифрованные версии.
Расшифровать их все‑таки возможно благодаря известной Для просмотра ссылки Войди
Она позволяет расшифровывать загрузочные файлы при подключении через USB-порт без пароля. На колонках Lite, Mini2 и Max пароль по умолчанию отсутствует, хотя со временем «Яндекс» начал устанавливать его удаленно. Мне повезло: платы, с которыми я работаю, пока не защищены. Поэтому я смогу воспользоваться уязвимостью и расшифровать файл TPL, а BL2 для анализа не потребуется.
Строение загрузчика
Для защиты процесса загрузки используется Для просмотра ссылки Войди- BL2 готовит систему к старту;
- BL31, BL32 — защищенная часть для обеспечения безопасности;
- BL33 (U-Boot), который нам и нужен.
У колонок Max с процессором Amlogic S905X2 загрузчик представлен одним файлом — bootloader.bin, который включает все этапы загрузки: от начальной инициализации до запуска U-Boot. В моделях на чипах A113X (например, Lite и Mini2) загрузчик разделен на два отдельных файла — с расширениями .bl2 и .tpl.
info
Такое объединение связано с особенностями разметки разделов на устройствах с памятью eMMC. У Max загрузчик хранится в разделе bootloader, тогда как в колонках Lite и Mini2, использующих NAND-память, эти части разделены на два независимых раздела — bootloader (содержит BL2) и TPL.
Каждый этап загрузки в прошивке помечен сигнатурой @AML, за которой следует заголовок со служебной информацией. Изучив несколько файлов загрузчиков, я заметил, что последняя по порядку сигнатура @AML соответствует компоненту BL33. Его отличительная особенность — наличие строки LZ4C, что указывает на использование сжатия алгоритмом LZ4. Еще один косвенный признак сжатия — «рваные» строки в теле файла.


Дополнительное подтверждение я нашел в Для просмотра ссылки Войди
aml_encrypt_g12a --bl3sig --input u-boot.bin --compress lz4 --output u-boot.enc --level v3 --type bl33
Пытаемся извлечь U-Boot BL33
Перед нами компонент BL33, давай попробуем извлечь его. Готовых решений в сети не нашлось, поэтому пришлось разбираться самостоятельно. В качестве первого шага — самый простой способ: с помощью утилиты dd отрезаем нужную область файла, а затем пробуем распаковать полученное архиватором LZ4.Для просмотра ссылки Войди
Но нет, данные не подошли, архиватор ругается на испорченный заголовок. Попытки распаковать другими утилитами или дописать заголовок вручную не увенчались успехом, метод не подходит. Но в процессе я заглянул в Для просмотра ссылки Войди
Для просмотра ссылки Войди
Изучение я начал с заголовка, попытавшись определить его формат и получить полезные для распаковки данные. В разделе Doc исходников есть описание двух форматов:
- LZ4 Block Format — блоки данных;
- LZ4 Frame Format — заголовки данных.
Отложив изучение исходников LZ4, я продолжил поиски — и на одном из форумов Для просмотра ссылки Войди
- Для просмотра ссылки Войди
или Зарегистрируйся — GXBB, GXL и GXM; - Для просмотра ссылки Войди
или Зарегистрируйся — GXBB, GXL, GXM и AXG; - Для просмотра ссылки Войди
или Зарегистрируйся — разработаны для G12B, потенциально могут работать с G12A и SM1.
Для просмотра ссылки Войди
Для просмотра ссылки Войди
Для просмотра ссылки Войди
Для просмотра ссылки Войди
Сводка LZ4
В итоге удалось установить, что сигнатура LZ4C обозначает начало заголовка длиной 0x80 байт, за которым следует сжатый буфер данных. Для сжатия используется алгоритм LZ4HC, а все необходимые параметры для распаковки — включая размеры буфера до и после сжатия — уже содержатся внутри заголовка.Извлечение
Теперь, когда у нас есть все данные о заголовке и алгоритме, пробуем распаковать загрузчик. Для этого я набросаю утилиту на C#, он мне наиболее близок.Помимо стандартных библиотек, будем использовать пакет K4os.Compression.LZ4. Привожу основную часть, а Для просмотра ссылки Войди
Код:
Console.WriteLine("LZ4C найден");
// Считываем размер данных до запаковки
fstream.Seek(0x8 + offset, SeekOrigin.Begin);
fstream.Read(buffer, 0, 4);
var target_size = BitConverter.ToUInt32(buffer, 0);
target = new byte[target_size];
fstream.Read(buffer, 0, 4);
// Считываем размер запакованных данных
source = new byte[BitConverter.ToUInt32(buffer, 0)];
fstream.Seek(0x80 + offset, SeekOrigin.Begin);
// Читаем запакованные данные
fstream.Read(source, 0, source.Length);
// Распаковываем
var decoded = LZ4Codec.Decode(source, 0, source.Length, target, 0, target.Length);
if (decoded == -1)
{
throw new InvalidOperationException("Ошибка извлечения.Данные повреждены");
}
Console.WriteLine("target size {0:x} / source size {1:X} \r\n Распаковано {2:X}", target.Length, source.Length, decoded);
using (FileStream fstream = new FileStream(out_path, FileMode.OpenOrCreate))
{
// Преобразуем строку в байты
// Пишем массив байтов в файл
await fstream.WriteAsync(target, 0, target.Length);
Console.WriteLine("Файл записан в файл");
}
Работа проста, получаем размеры данных до (original_size) и после (compressed_size) сжатия, считываем сжатые данные одним большим куском и передаем на распаковку, после удачного завершения записываем буфер с распакованными данными в файл. Готово!
Для просмотра ссылки Войди
Переходим к дизассемблеру
Как видишь, строки теперь не «битые» и IDA/Ghydra теперь может анализировать и «разбирать» BL33. Собственно, для этого и нужно было извлечение.

Сразу скажу, что поправить и затолкать назад U-Boot не выйдет (даже при наличии ключей шифрования). Да, он запакуется и встроится в прошивку, но не пройдет проверку из‑за наших изменений, для того‑то и нужен Trusted Firmware. Однако польза от наших упражнений все‑таки есть: теперь можно анализировать нетиповые функции, которые в «Яндексе» добавили для своих устройств, и наконец отыскать Rabbit Hole, а может, даже и нырнуть в нее.
Исследуем и ищем
Чтобы дизассемблер верно обработал прошивку, выбираем архитектуру ARM little-endian. Базовый адрес и создание секций пока что не настраиваем, принимаем все параметры по умолчанию. Точка входа находится в самом начале прошивки, ставим курсор на 0x00 и жмем С — IDA автоматически создаст довольно много функций.Для просмотра ссылки Войди
Всё в автоматическом режиме не распознается, поэтому, пробежавшись по прошивке вручную, обрабатываем неопределенные участки (в основном тыкая С) и переходим к размещению образа по правильному адресу (базовый адрес), чтобы получить наиболее правильные ссылки на функции и данные. Определить верный адрес нам помогает лог загрузки колонки:
Код:
U-Boot 2015.01 (Sep 29 2023 - 16:13:15)
DRAM: 256 MiB
Relocation Offset is: 0eda0000
Вносим правки через меню Edit → Segments → Rebase Program и указываем нужное значение. После этих действий увидим изменения. Часть ссылок ранее была неверно интерпретирована, но теперь все должно соответствовать тому, как U-Boot расположен в памяти устройства.
Для просмотра ссылки Войди

Поиск норы
Подготовив прошивку к анализу, наконец‑то приступаем к поиску Rabbit Hole. Из лога загрузки мы знаем текст сообщения: «RH challenge», за него и будем цепляться. Идем в список строк с надеждой, что там будет ссылка.Для просмотра ссылки Войди
Нам повезло, и ссылка действительно есть. Сразу назовем функцию Rabbit Hole. Выглядит код несложно (всего 63 строки и 5 функций), но все функции с неизвестными именами, их нужно как‑то различать.
Для просмотра ссылки Войди
Для просмотра ссылки Войди
Есть два способа идентифицировать функции:
- Зайти в каждую, попытаться проанализировать логику и дать понятное название.
- Собрать похожую версию ПО с отладочной информацией и при помощи плагина Для просмотра ссылки Войди
или Зарегистрируйся (есть и другие) поискать стандартные функции, например printf, sha256.
Подготовка среды компиляции U-Boot
Итак, нам нужно сравнить функции с их аналогами, собранными из исходников наиболее близкой версии. Для этого нам нужно установить, какой использовался компилятор и ПО.При сборке в секцию данных включаются строки с версией U-Boot и используемого компилятора. Ищем строки U-Boot и GCC (для компилятора) и находим:
- aarch64-elf-gcc (Linaro GCC 4.9-2017.01) 4.9.4;
- ld (Linaro_Binutils-2017.01) 2.24.0.20141017 Linaro 2014_11-3-git;
- U-Boot 2015.01 (Sep 29 2023 - 19:21:39) — то же самое видно в логе загрузки.

Среда сборки
Данные о версии и компиляторе найдены, теперь ищем исходники, качаем компилятор. Я буду использовать Ubuntu. Обратимся к Для просмотра ссылки Войди
Код:
wget https://releases.linaro.org/archive/13.11/components/toolchain/binaries/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux.tar.xz
wget https://releases.linaro.org/archive/13.11/components/toolchain/binaries/gcc-linaro-arm-none-eabi-4.8-2013.11_linux.tar.xz
tar xvfJ gcc-linaro-aarch64-none-elf-4.8-2013.11_linux.tar.xz
tar xvfJ gcc-linaro-arm-none-eabi-4.8-2013.11_linux.tar.xz
export PATH=$PWD/gcc-linaro-aarch64-none-elf-4.8-2013.11_linux/bin:$PWD/gcc-linaro-arm-none-eabi-4.8-2013.11_linux/bin:$PATH
git clone https://github.com/BayLibre/u-boot.git -b n-amlogic-openlinux-20170606 amlogic-u-boot
cd amlogic-u-boot
make axg_s400_v1_defconfig
make
export FIPDIR=$PWD/fip
Но версия компилятора здесь отличается от нашей, к тому же их два. Если с версией все ясно (просто поищем нашу), то с количеством требуется пояснение:
- gcc-linaro-arm-none-eabi — для сборки кода, работающего в 32-битном режиме ARMv7;
- gcc-linaro-aarch64-none-elf — для сборки под 64-разрядный режим AArhc64.
- aarch64-none-elf — gcc-linaro-*x86_64_aarch64-elf.tar.xz (Linux 64-bit binaries for the AArch64 bare-metal cross-toolchain);
- arm-none-eabi — gcc-linaro-*x86_64_arm-eabi.tar.xz (Linux 64-bit binaries for the ARMv7 bare-metal cross-toolchain).
info
gcc-linaro-aarch64-none-elf я все же использовал другой версии из‑за проблем со сборкой, но это в нашем случае ничего не изменит.
Для просмотра ссылки Войди
Чтобы упростить рутинные действия при итерациях сборки, я набросал небольшой скрипт — так меньше придется стучать по клавишам. Внутри оставлены закомментированные строки на случай, если понадобится сменить версию U-Boot или использовать другую версию компилятора arm-none-eabi. В качестве основы я выбрал репозиторий Khadas — производителя отладочных плат, который активно сопровождает нужную нам ветку U-Boot и регулярно ее обновляет.
Код:
#/bin/bash
mkdir ~/lite_uboot
cd ~/lite_uboot
wget https://github.com/khadas/u-boot/archive/refs/tags/khadas-vims-u-boot-v2015.01-v1.5.2-release.tar.gz
tar xvf khadas-vims-u-boot-v2015.01-v1.5.2-release.tar.gz
#git clone https://github.com/khadas/u-boot.git
#cd ~/lite_uboot1/u-boot
#git checkout ff7d3af наиболее близкий по дате коммит
mkdir ~/cross_compile
cd ~/cross_compile
wget -c https://releases.linaro.org/components/toolchain/binaries/latest-4/aarch64-elf/gcc-linaro-4.9.4-2017.01-x86_64_aarch64-elf.tar.xz
tar xvfJ gcc-linaro-4.9.4-2017.01-x86_64_aarch64-elf.tar.xz
export PATH=$PWD/gcc-linaro-4.9.4-2017.01-x86_64_aarch64-elf/bin:$PATH
#tar xvfJ gcc-linaro-4.9.4-2017.01-x86_64_arm-eabi.tar.xz
#export PATH=$PWD/gcc-linaro-4.9.4-2017.01-x86_64_arm-eabi/bin:$PATH
wget -c https://releases.linaro.org/archive/13.11/components/toolchain/binaries/gcc-linaro-arm-none-eabi-4.8-2013.11_linux.tar.xz
tar xvfJ gcc-linaro-arm-none-eabi-4.8-2013.11_linux.tar.xz
export PATH=$PWD/gcc-linaro-arm-none-eabi-4.8-2013.11_linux/bin:$PATH
cd ~/lite_uboot/u-boot-khadas-vims-u-boot-v2015.01-v1.5.2-release
export ARCH=arm
export CROSS_COMPILE=aarch64-elf-
make axg_skt_v1_defconfig
make
info
Код:
Я не привожу полную инструкцию по настройке среды сборки, так что тебе могут понадобиться дополнительные пакеты и библиотеки.
Результат сборки забираем в папке build, нас интересует файл u-boot. Загрузим его в «Иду», и она автоматически определит тип ELF 64 bit и проведет полный анализ. У всех функций есть имена, заполнена информация о сегментах.
Для просмотра ссылки Войди
Запускаем Diaphora
Теперь нужно запустить плагин, который определит сигнатуры и строки и соберет всю нужную информацию в свою базу (SQLite), чтобы сравнить с U-Boot версии «Яндекса».Для просмотра ссылки Войди
Процесс может слегка затянуться: моему MacBook Air десятилетней давности потребовалось около 30 минут на каждую из прошивок (собранную и в исполнении «Яндекса»).
Для просмотра ссылки Войди
После завершения индексации необходимо вернуться к прошивке «Яндекса» и повторно запустить плагин. На этот раз в строке базы сравнения следует указать SQLite-базу, созданную при индексации скомпилированного U-Boot. Внимание: не запускай повторную индексацию! Если согласиться, то плагин снова выполнит ту же работу и ты зря потратишь еще полчаса.


После сканирования плагин выдаст несколько вкладок с результатами:
- Best matches — наиболее точно совпадающие функции;
- Partial matches — более‑менее совпадающие функции с небольшими отличиями (степень совпадения помечена цветом: ярко‑зеленый — высокое, темно‑фиолетовый — низкое);
- Problematic matches — плохо совпадающие результаты;
- Unmatched — несколько вкладок полностью уникальных функций.


Сначала переходим на вкладку с полностью совпадающими функциями, выделяем их и импортируем в проект — после этого база IDA будет обновлена. Затем открываем вкладку Partial и с осторожностью импортируем еще часть функций — я, например, использую только те, что подсвечены зеленым. Желательно просматривать содержимое перед импортом, чтобы не затянуть лишнего. Остальные вкладки можно не трогать — они нас не интересуют.
Для просмотра ссылки Войди
После импорта в исследуемом U-Boot появились осмысленные имена функций и переменных.
Для просмотра ссылки Войди
Для получения более полного результата можно собрать и проиндексировать другие версии U-Boot — каждая из них потенциально может помочь обработать новые функции. Никаких гарантий нет, но иногда такой подход позволяет значительно дополнить базу.
Я ограничился одним проходом и перешел к следующему плагину — Для просмотра ссылки Войди
Теперь уже точно анализ
Итак, в Rabbit Hole пять функций:- sub_EDC96CC;
- sub_EDC1BBC;
- sub_EDC1B74;
- sub_EDC1A14;
- sub_EDC9734.
- sub_EDC96CC — challenge_gen;
- sub_EDC1BBC — print;
- sub_EDC1B74 — getconsole;
- sub_EDC1A14 — getc;
- sub_EDC9734 — challenge_chk.
Код:
challenge_gen
bool __fastcall challenge_gen(unsigned __int64 a1)
{
int v2; // w20
int v3; // w21
int v4; // w22
int v5; // w0
v2 = sub_EE2218C();
v3 = sub_EE2218C();
v4 = sub_EE2218C();
v5 = sub_EE2218C();
return string_format(a1, "%08X%08X%08X%08X", v2, v3, v4, v5) == 32;
}
bool __fastcall challenge_chk(const uint8_t *a1, __int64 a2)
{
sha256_context ctx;
sha256_starts(&ctx);
sha256_update(&ctx, a1, 0x20u);
sha256_finish(ctx.total, 0xEE89720);
return sub_EE0EBA8(&dword_EE370C0, a2, 0x100u, 0xEE89720, &unk_EE593E0) == 0;
}
Анализ первой функции и вызова sub_EE2218C() показал, что в регистре a1 формируется challenge — я сразу переименовал его соответствующим образом. Запрос каждый раз получается уникальным, поскольку для его создания используется аппаратный генератор случайных чисел. Это подтверждается содержимым функции sub_EE2218C(), где единственная строка — return MEMORY[0xFF634018];. Согласно документации на A113X, этот адрес действительно принадлежит области RNG.
Для просмотра ссылки Войди
Можем предположить, что функция challenge_gen принимает указатель на буфер, в который нужно записать запрос в виде строки с шестнадцатеричным представлением. В качестве результата функция возвращает длину сформированной строки в байтах.
Код:
result = challenge_gen(challenge);
if ( result )
{
while ( get_console() )
getc();
print("RH challenge: %s\n", challenge);
С challenge_chk повезло немного больше — почти все функции в этой области уже получили осмысленные имена. По ним сразу становится ясно, что для хеширования входных данных используется SHA-256, а полученный хеш затем передается в sub_EE0EBA8. Заглянем в нее.
bool __fastcall challenge_chk(const uint8_t *a1, __int64 a2)
{
sha256_context ctx; // [xsp+38h] [xbp+38h] BYREF
sha256_starts(&ctx);
sha256_update(&ctx, a1, 0x20u);
sha256_finish(ctx.total, 0xEE89720);
return sub_EE0EBA8(&dword_EE370C0, a2, 0x100u, 0xEE89720, &unk_EE593E0) == 0;
}
В коде этой функции видны какие‑то преобразования, но имен тоже нет. Ныряем в каждую номерную функцию и пробуем найти намеки на то, что она делает.
__int64 __fastcall sub_EE0EBA8(int *a1, const void *a2, unsigned int a3, const void *a4, __int64 a5)
{
v5 = *a1;
v7 = *a1 - 2048;
result = 0xFFFFFFF2LL;
if ( v7 <= 0x800 )
{
v9 = v5 >> 5;
sub_EE0E8B0(&v14, *(a1 + 1), v9);
sub_EE0E8B0(&v14, *(a1 + 2), v9);
v15[0] = v9;
v15[1] = a1[1];
v18 = *(a1 + 3);
v16 = &v14;
v17 = &v14;
return sub_EE0E8E4(v15, a2, a3, a4, a5);
}
return result;
}
Итак, sub_EE0E8B0 «переворачивает» байты входного массива и складывает в другой.
Назовем ее array_bswap32. Возможно, она нам еще где‑то встретится и не придется повторно в ней копаться.
Код:
__int64 __fastcall sub_EE0E8B0(__int64 result, __int64 a2, int a3)
{
__int64 i; // x3
for ( i = 0; a3 > i; ++i )
(result + 4 * i) = bswap32((a2 + 4LL * a3 - 4 * i - 4));
return result;
}
С функцией sub_EE0E8E4 нам повезло не так, она уже имеет разветвленную структуру, видно много операций над данными. При изучении листинга я зацепился взглядом за функцию subM — ее имя появилось в результате работы Diaphora. Берем это имя и пробуем искать его в исходниках U-Boot от Khadas, которые мы компилировали.
Для просмотра ссылки Войди
Как видим, эта функция встречается только в файле avb_rsa.с, нам очень повезло!
Для просмотра ссылки Войди
Подробно изучаем этот файл и сверяем с тем, что видим в IDA. В результате я наткнулся на вызов avb_rsa_verify. Эта функция очень напоминает начало sub_EE0E8E4.
Для просмотра ссылки Войди
Код:
__int64 __fastcall sub_EE0E8E4(unsigned int *a1, const void *a2, unsigned int a3, const void *a4, __int64 a5)
{
// [COLLAPSED LOCAL DECLARATIONS. PRESS NUMPAD "+" TO EXPAND]
if ( !a1 || !a2 || !a4 || !a5 )
return 0xFFFFFFFBLL;
if ( 4LL * *a1 != a3 || a3 > 0x200 )
return 0xFFFFFFEALL;
Очень похожая проверка в начале, но при этом тестовые сообщения не выводятся, просто возвращаются коды ошибок: всё, что не 0, — плохо. Идем дальше и находим больше сходств, поэтому возвращаемся на пару уровней вверх и даем название функции sub_EE0EBA8: rsa_yndx. Теперь функция проверки ответа выглядит так:
Код:
bool __fastcall challenge_chk(const uint8_t *a1, __int64 a2)
{
sha256_context ctx; // [xsp+38h] [xbp+38h] BYREF
sha256_starts(&ctx);
sha256_update(&ctx, a1, 0x20u);
sha256_finish(ctx.total, 0xEE89720);
return rsa_yndx(&dword_EE370C0, a2, 0x100u, 0xEE89720, &unk_EE593E0) == 0;
}
Здесь:
- dword_EE370C0 — содержит открытую часть ключа RSA;
- a2 — ответ (который мы вводим);
- 0x100u — размер проверяемых данных;
- 0xEE89720 — ячейка памяти, содержащая SHA-256 от challenge;
- unk_EE593E0 — должно быть padding.
Код:
bool avb_rsa_verify(const uint8_t* key,
size_t key_num_bytes,
const uint8_t* sig,
size_t sig_num_bytes,
const uint8_t* hash,
size_t hash_num_bytes,
const uint8_t* padding,
size_t padding_num_bytes)
В этом же файле встречается функция с говорящим названием array_bswap32, в которой реализован алгоритм так называемого переворачивания — побайтовой перестановки в 32-битных словах. Прослеживаем, где она вызывается и какую роль играет в обработке данных.
Для просмотра ссылки Войди
Собираем всё воедино. При попытке входа в консоль U-Boot Rabbit Hole генерирует текстовый запрос (challenge). Пользователь должен подписать этот запрос своей закрытой частью RSA-ключа и ввести полученную подпись обратно в консоль. Далее вызывается функция rsa_yndx, которая проверяет подпись по аналогии с avb_rsa_verify, используя алгоритм RSA-2048. В случае успешной проверки функция возвращает 0.
Код:
/* Verify a RSA PKCS1.5 signature against an expected hash.
* Returns false on failure, true on success.
*/
Мы рассмотрели основные механизмы Rabbit Hole. Остальные части довольно простые, привожу листинг c комментариями:
Код:
bool Rabbit Hole()
{
result = challenge_gen(challendge);
if ( result )
{
while ( get_console() )
getc();
// Печатаем запрос
print("RH challenge: %s\n", challendge);
v1 = 0;
// Печатаем приглашение и ожидаем 344 символа ответа
print("RH response:\n");
while ( v1 != 344 )
{
v2 = getc();
// Если не пробел, то сохраняем в буфер
if ( (ctype[v2] & 0x20) == 0 )
// Проверяем введенные символы, если они из ASCII-таблицы, то помещаем в буфер 0xEE89470
*(0xEE89470 + v1++) = v2;
}
v3 = 0;
v4 = 0;
v5 = 0;
// Теперь прогоняем каждый введенный символ
while ( v3 != 344 )
// Сравниваем с таблицей Base64
{
v6 = 0;
while ( 1 )
{
v7 = v6;
// Base64_table найден FindCrypt
if ( Base64_table[v6] == *(v3 + 0xEE89470) )
break;
if ( ++v6 == 64 )
{
v7 = 64;
break;
}
}
v8 = (v5 + 1);
v12[v5 + 4048] = v7;
// Продолжаем преобразование Base64 в двоичный вид
if ( v8 == 4 )
{
v9 = v13;
v10 = v4 + 1;
*(0xEE89740LL + v4) = 4 * (v12[4048] & 0x3F) + (v13 >> 4);
v11 = v14;
if ( v14 != 64 )
{
*(0xEE89740LL + v10) = 16 * (v9 & 0xF) + (v14 >> 2);
v10 = v4 + 2;
}
v4 = v10;
if ( v15 != 64 )
{
v4 = v10 + 1;
*(0xEE89740LL + v10) = v15 + (v11 << 6);
}
v8 = 0;
}
++v3;
v5 = v8;
}
// Запускаем проверку подписи
return challenge_chk(challendge, 0xEE89740);
}
return result;
}
Вот полный алгоритм авторизации:
- При загрузке системы нажимаем Enter или Ctrl-C, что провоцирует вызов Rabbit Hole.
- Генерируется случайный запрос, который выводится в консоль.
- Пользователь должен подписать запрос закрытой частью RSA-ключа, преобразовать в Base64.
- Дальше вводим ответ в виде Base64, который преобразует его в бинарный вид и отправляет на проверку в challenge_chk.
- Если результат проверки совпал, то попадаем в интерактивный режим U-Boot.

Мне удалось получить доступ к этой консоли — как и некоторым моим знакомым. Однако, если пароль уже установлен, попасть в консоль становится заметно сложнее.

Выводы
Инженеры «Яндекса» в части защиты консоли U-Boot применили очень надежный алгоритм, не позволяющий получить вход даже при наличии расшифрованной прошивки, — асимметричный RSA-2048 со случайными входными данными. Брутфорс тут бессилен, еще и попытка ввода только одна — неверный ответ перезагружает устройство, и запрос будет уже новый. В лоб Rabbit Hole не обходится.Зато по дороге я создал инструмент для извлечения BL33 и выяснил, как защищена прошивка. Надеюсь, и ты тоже почерпнул что‑то для себя полезное.
Я не профессиональный реверсер, скорее — увлеченный любитель. Это мой первый подобный опыт, и, если я где‑то напорол чуши, не стесняйся и расскажи мне об этом!