Найти в Дзене
mamaich

Обход Secure Boot на Яндекс IP камере

Публикация данной статьи была отложена, так как информация была передана в Яндекс Вug Bounty. На момент публикации наверняка в прошивку внесены изменения, так что я рекомендую описанный ниже метод использовать на свежеприобретенном устройстве не выпуская его в Internet для обновления. После расшифровки u-boot я решил посмотреть на процесс его загрузки, чтобы найти возможность запуска своего кода. В отличие от других устройств Яндекса в камере отсутствует запуск скриптов с USB/SD карты в режиме восстановления. В режиме восстановления командой bootm загружается отдельное ядро Linux: Команду bootm я и решил проанализировать. Основная логика загрузки реализуется в функции do_bootm_states (файл common\bootm.c). В исходниках код простой: но в прошивке функции bootm_start, bootm_find_os и другие заинлайнены, что несколько запутывает при анализе. Приведу код вызова bootm_find_os, так как именно там делается вызов команды расшифровки и проверки подписи образа: Видно, что сперва определяется ад

Публикация данной статьи была отложена, так как информация была передана в Яндекс Вug Bounty. На момент публикации наверняка в прошивку внесены изменения, так что я рекомендую описанный ниже метод использовать на свежеприобретенном устройстве не выпуская его в Internet для обновления.

После расшифровки u-boot я решил посмотреть на процесс его загрузки, чтобы найти возможность запуска своего кода. В отличие от других устройств Яндекса в камере отсутствует запуск скриптов с USB/SD карты в режиме восстановления. В режиме восстановления командой bootm загружается отдельное ядро Linux:

Команду bootm я и решил проанализировать. Основная логика загрузки реализуется в функции do_bootm_states (файл common\bootm.c). В исходниках код простой:

и так далее
и так далее

но в прошивке функции bootm_start, bootm_find_os и другие заинлайнены, что несколько запутывает при анализе. Приведу код вызова bootm_find_os, так как именно там делается вызов команды расшифровки и проверки подписи образа:

-3

Видно, что сперва определяется адрес начала загруженного ядра в памяти (вызовом функции genimg_get_kernel_addr_fit - если посмотреть исходники u-boot, то должен быть вызов boot_get_kernel, но boot_get_kernel тоже заинлайнилась). Далее адрес ядра передается в команду sigauth для расшифровки и проверки подписи. В случае, если подпись не сошлась - sigauth завешивает устройство вызовом halt().

В реализации sigauth есть особенность - она пропускает образы не являющиеся образами RTK (в начале образа 'RTK_'), не являющиеся образами ядра (в начале образа значение 0x27051956), и образы ядра в заголовке которых установлена длина -0x40 байт (в таком случае строка image_size += image_get_header_size() установит image_size в 0).

SECURITYBOOT_INC_HEADER у нас определено
SECURITYBOOT_INC_HEADER у нас определено

Такое поведение функции sigauth поможет в дальнейшем.

Функция bootm_find_os поддерживает несколько форматов образов:

-5

У нас в прошивке реализованы только два - CONFIG_IMAGE_FORMAT_LEGACY, это классический образ ядра, который использует Яндекс, и IMAGE_FORMAT_FIT - новый формат ядра Flattened uImage Tree (что это такое можно почитать, например, тут). Данный формат в начале образа содержит сигнатуру DTB файла "D0 0D FE ED", то есть функция sigauth его не сможет проверить и завершится с ошибкой, а не остановит устройство. Так как код возврата sigauth не проверяется, то загрузка пойдет дальше, что позволит загрузить неподписанный код.

Для дальнейшей эксплуатации потребуется собрать свой образ FIT. Файл ядра требуется использовать запакованный (иначе он просто не поместится). Образ DTB дампится из загруженной Linux. Я использую такой Image Tree Source файл:

-6

Далее файл требуется скормить утилите mkimage:

./mkimage -f image.its image.ub

Утилиту следует взять из исходников u-boot. Также требуется установленный device tree compiler.

Ключевой момент: размер image.ub должен быть кратен nand erase size, в нашем случае это 0x20000 или 128 кб. Если файл не кратен, следует добить его, например, нулями, до нужного размера.

Далее - записываем полученный image.ub в раздел kernel:

flash_erase /dev/mtd7 0 36
dd if=/bkupgrade/image.ub of=/dev/mtd7 bs=4096

или rkernel:

flash_erase /dev/mtd5 0 32
dd if=/bkupgrade/image.ub of=/dev/mtd5 bs=4096

и перезагружаем устройство:

тут я для демонстрации перебил строку при загрузке
тут я для демонстрации перебил строку при загрузке

Вместо загрузки своего ядра Linux, можно заставить u-boot перейти по заданному нами адресу - например, на начало функции cli_loop, тем самым получив интерактивный режим u-boot (и снова перепрыгнув кролика):

-8

Адрес 0x23E13364 - это адрес cli_loop. Файл "Image" может быть любым, я просто сделал файл из 16 байт мусора.
Напомню, что размер итогового image.ub должен быть кратен 128 кб.

-9

В свежих версиях u-boot нажатие Enter уже убрали. Наверняка и описанная в данной статье ошибка также исправлена. Так что описанный способ будет работать либо на устройстве только из коробки с заводской прошивкой, либо откат на ранее сдампленную.

Данная уязвимость была передана в Яндекс Bug Bounty и получила там средний уровень с неплохой выплатой.