Это отложенная статья, так как информация передана в Яндекс Вug Bounty.
Описанный ниже способ может работать не только на мини 3 про (процессор Amlogic A4, он же A113L2), но и на других устройствах с этой и некоторыми предыдущими моделями процессоров Amlogic.
Немного теории.
Пример эксплуатации будет в конце статьи.
Устройства Amlogic в качестве одного, из цепочки, загрузчиков используют модифицированный u-boot. В современных ритейл устройствах по умолчанию включен Secure Boot, препятствующий запуску неавторизованного кода, а загрузка своего DTB позволяет частично снизить эффективность данного запрета.
Исходные тексты u-boot с правками от Amlogic периодически обновляются в репозиториях Khadas, CoreELEC и других. В данной статье я для примеров кода использую репозиторий khadas-vim4-android14, дополняя цитатами из IDA.
Содержимое device tree на Яндекс мини 3 про загружается в функции board_late_init(). Так как исходников u-boot Яндекс не выкладывает, я обычно смотрю в файлы, выложенные Khadas - в данном случае в файлы для устройства ba400. В нем код board_late_init() находится в файле board\amlogic\a4_ba400\a4_ba400.c
В начале функции мы видим вызов run_command("run common_dtb_load", 0):
Этой командой загружается DTB, который далее немного обрабатывается u-boot и передается в загружаемый Linux/Android.
В случае, если команда выполнится с ошибкой - на экран просто выводится сообщение и загрузка продолжится дальше. Это первая предпосылка к эксплоиту.
Значение common_dtb_load:
common_dtb_load=imgread dtb _aml_dtb ${dtb_mem_addr}
Команда imgread реализована в файле \cmd\amlogic\imgread.c:
Загрузка DTB производится функцией do_image_read_dtb:
Так как у нас команда "imgread dtb _aml_dtb", то непосредственно чтение происходит в функции do_image_read_dtb_from_rsv. Данная функция читает DTB с NAND, затем проверяет его подпись/делает расшифровку вызовом secure_image_check(). Функция secure_image_check() делает расшифровку in place (то есть расшифрованные данные перезаписывают зашифрованные), а если расшифровка не удалась - данные остаются без изменений.
Красным выделен интересующий нас кусок исходника.
Здесь видно, что независимо от кода возврата функции secure_image_check вызывается memmove для перемещения данных на sizeof(struct aml_boot_header_t) == 0x300 байт к началу буфера.
Вот этот же код из декомпиляции в IDA:
В случае некорректной подписи, Amlogic в функции secure_image_check ничего не делает с переданными данными (а мог бы забить их нулями или мусором). И это вторая предпосылка к эксплоиту.
То есть, do_image_read_from_rsv читает данные DTB с NAND, пытается их расшифровать/проверить подпись, и в случае, если подпись не сошлась - просто сдвигает прочитанные данные на 0x300 байт к началу буфера. Данные при этом никак не меняются (кроме сдвига). Как было видно выше - код возврата игнорируется, а данные остаются в памяти.
Переходим к эксплуатации.
Для эксплуатации уязвимости нам всего лишь требуется положить незашифрованный DTB со смещения 0x300 относительно начала.
Нам потребуется Яндекс мини 3 про с доступом через ADB.
Сперва получаем текущий DTB файл с устройства. На запущенной Яндекс мини 3 про расшифрованный DTB лежит по пути /sys/firmware/fdt. Требуется его скопировать на ПК и там преобразовать в текстовый формат командой:
dtc -I dtb -O dts ./fdt -o ./fdt.dts
Далее открываем получившийся fdt.dts и вносим в него правки. Для примера, добавим в раздел chosen в переменную bootargs= свою строку, которая будет добавлена к строке параметров ядра Linux.
Сперва удаляем лишнее:
Следует удалить строку bootloader_build и все параметры bootargs, которые были добавлены u-boot автоматически. Оставляем только нашу добавляемую строку, для примера это будет "androidboot.rh_unlock=1":
Преобразуем текстовый формат в бинарный:
dtc -I dts -O dtb ./fdt.dts -o ./fdt
Добавляем 0x300 (768) байт в начало fdt файла:
(dd if=/dev/zero bs=1 count=768; cat fdt) > fdt.new
И копируем fdt.new на устройство, например, в папку /data.
На Станции выполняем команду:
cat /data/fdt.new >/dev/dtb
После чего перезагружаемся, при загрузке видим ошибку проверки подписи DTB:
Но при этом u-boot далее корректно принимает наш модифицированный DTB:
И передает добавленную нами строку в конце параметров ядра:
В отличие от метода инъекции параметров командной строки ядра через unifykey, данный метод не переживет сброс устройства до заводских настроек или установки обновления, но именно этим он и может быть полезен - можно тестировать потенциально опасные параметры командной строки без необходимости перепрошивки NAND в случае неуспеха (достаточно сделать сброс до заводских настроек).