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

Инъекция своих параметров в строку загрузки ядра (Яндекс/Amlogic)

Это отложенная статья, так как информация передана в Яндекс Вug Bounty. В станции Яндекс Мини 3 Про неплохо подкрутили вопросы безопасности - используется цепочка Secure Boot (правда, всё еще с возможностью ее обхода), u-boot в случае ошибок более не запускает скрипты и не пытается восстановить прошивку с USB устройства, а переменные в ENV устанавливаются только по "белому списку". Или не только по нему? Мое внимание привлекла следующая конструкция в ENV разделе: Здесь переменные deviceid, localization, mac, region_code читаются из хранилища unifykey, после чего передаются в командную строку ядра (переменная bootargs): Kernel command line: console=ttyS0,921600 no_console_suspend loglevel=9 loop.max_part=4 ubi.mtd=system root=ubi0:rootfs rootfstype=ubifs ubi.mtd=data otg_device=1 androidboot.selinux=enforcing androidboot.firstboot=1 jtag=disable androidboot.bootloader=01.01.251114.162721 mtdbootparts=aml-nand:256k(bl2e),256k(bl2x),256k(ddrfip),3072k(devfip) androidboot.localization=RU.R

Это отложенная статья, так как информация передана в Яндекс Вug Bounty.

В станции Яндекс Мини 3 Про неплохо подкрутили вопросы безопасности - используется цепочка Secure Boot (правда, всё еще с возможностью ее обхода), u-boot в случае ошибок более не запускает скрипты и не пытается восстановить прошивку с USB устройства, а переменные в ENV устанавливаются только по "белому списку". Или не только по нему?

Мое внимание привлекла следующая конструкция в ENV разделе:

в ENV она написана в одну строку, я переформатировал для читабельности
в ENV она написана в одну строку, я переформатировал для читабельности

Здесь переменные deviceid, localization, mac, region_code читаются из хранилища unifykey, после чего передаются в командную строку ядра (переменная bootargs):

Kernel command line: console=ttyS0,921600 no_console_suspend loglevel=9 loop.max_part=4 ubi.mtd=system root=ubi0:rootfs rootfstype=ubifs ubi.mtd=data otg_device=1 androidboot.selinux=enforcing androidboot.firstboot=1 jtag=disable androidboot.bootloader=01.01.251114.162721 mtdbootparts=aml-nand:256k(bl2e),256k(bl2x),256k(ddrfip),3072k(devfip) androidboot.localization=RU.RU androidboot.wificountrycode=US androidboot.serialno=СерийникУстройства androidboot.rh_unlock=0 meson-gx-mmc.caps2_quirks=mmc-hs400 reboot_mode=cold_boot

В случае Мини 3 Про, после значений androidboot.localization, androidboot.wificountrycode, androidboot.serialno видим полезный параметр androidboot.rh_unlock, который в случае установки в 1 включает интерактивную консоль Linux с правами root (без пароля).

Консоль запускается так - файл /etc/inittab запускает скрипт getty_rabbit_hole

-2

который ищет в строке параметров ядра в любом месте наличие "androidboot.rh_unlock=1":

-3

Что будет, если взять бесполезный параметр, например, androidboot.wificountrycode и поменять его значение с "US" на "US androidboot.rh_unlock=1"?

Чтобы проверить эту гипотезу, требуется устройство с возможностью подключения к нему по ADB с правами root. У меня как раз такой Мини 3 Про.

Работа с ключами unifykeys в Linux на устройствах с процессорами Amlogic производится через файлы в папке /sys/class/unifykeys и даже содержит справку по использованию:

-4

Значение androidboot.wificountrycode устанавливается из ключа region_code. Для его исправления делаем так:

cd /sys/class/unifykeys/
echo 1>lock
echo region_code>name
cat read
echo "US console=ttyS0,921600 androidboot.rh_unlock=1">write
echo 0>lock

Кроме установки androidboot.rh_unlock=1 я также на всякий случай прописываю параметры serial консоли console=ttyS0,921600 - это требуется в случае, если в ENV прописано silent=1.

После записи рекомендую проверить записанное командой "cat read":

-5

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

Kernel command line: console=ttyS0,921600 no_console_suspend loglevel=9 loop.max_part=4 ubi.mtd=system root=ubi0:rootfs rootfstype=ubifs ubi.mtd=data otg_device=1 androidboot.selinux=enforcing androidboot.firstboot=1 jtag=disable androidboot.bootloader=01.01.251114.162721 mtdbootparts=aml-nand:256k(bl2e),256k(bl2x),256k(ddrfip),3072k(devfip) androidboot.localization=RU.RU androidboot.wificountrycode=US console=ttyS0,921600 androidboot.rh_unlock=1 androidboot.serialno= СерийникУстройства androidboot.rh_unlock=0 meson-gx-mmc.caps2_quirks=mmc-hs400 reboot_mode=normal

Видим появление androidboot.rh_unlock=1 перед серийником. То, что далее идет androidboot.rh_unlock=0 - на поведение скрипта getty_rabbit_hole никак не влияет. И то, что console= в строке появляется несколько раз - также не страшно, ядро берет последнее вхождение.

В чем польза от описанного в данной статье? Ключи в unifykeys в процессе жизни устройства остаются неизменными. То есть они переживут и сброс устройства до заводских настроек, и регулярные обновления - мы будем всегда иметь root на консоли (причем, доступный независимо от значения silent= в ENV).

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

Подобная инъекция возможна не только на Мини 3 Про, но и на других устройствах, которые используют команды keyman read в скриптах u-boot и передают прочитанные значения в командную сроку ядра (а это почти все устройства Яндекс).

Что еще можно сделать - зависит только от фантазии. Можно запустить shell вручную (добавить "init=/bin/sh -- -- " - первые два минуса нужны, чтобы отрезать остатки командной строки ядра, а вторые два минуса говорят sh игнорировать весь мусор, идущий после них). Можно использовать известные эксплоиты, навскидку нашелся довольно старый, но, возможно, до сих пор не закрытый (не проверял). А можно просто залогиниться под рутом, перемонтировать /system как rw и изменить там файлы.