«А давайте вы сделаете какой-нибудь новогодний рисёч?» — примерно такую задачу поставили нашей Positive Labs этим летом. Поскольку мы любим изучать разнообразные железки, первым делом подумали про умные устройства. А что у нас с Новым Годом обычно ассоциируется? Правильно — новогодняя ёлка. Быстрый поиск в сети показал, что smart-ёлки существуют, и даже не от дядюшки Ляо, а вполне себе серьезной компании. На том и порешили — берем Twinkly Light Tree и смотрим, что там с безопасностью.

Прошлые исследования
До нас серию Twinkly исследовала компания MWR InfoSecurity (что интересно, с той же самой целью). В результате были обнаружены инфраструктурные ошибки, используя которые можно было выключить вообще все гирлянды этого бренда в мире:

Ошибка заключалась в протоколе MQTT и отсутствии ограничений доступа при подключении к чужим устройствам. Поскольку исследование было в далеком 2018, уязвимость давно исправлена. Но то были инфраструктурные косяки. Мы же решили изучить само устройство и его прошивку, в идеале — добиться удалённого запуска кода (спойлер — всё получилось).
А что, собственно, внутри?
Как становится понятно из названия, это не просто гирлянда, а целая светящаяся ель, состоящая из каркаса, светодиодных рядов и блока управления. Блок умеет подключаться к сетям Wi-Fi (а также играть роль точки доступа), при этом для запуска конфигурации сети сверху есть большая кнопка управления со светодиодом рядом:

Внутри корпуса блока управления можно обнаружить саму кнопку, трехцветный светодиод и модуль ESP32-WROOM.

Справа на плате можно заметить набор контактов, подозрительно похожих на отладочные. Здесь установлен стандартный модуль ESP32, его распиновка известна, поэтому назначение контактов легко определяется прозвонкой:

Эти контакты предназначены для первоначальной прошивки модуля ESP32. Если производитель не заблокировал интерфейс, их также можно использовать для считывания содержимого флэш-памяти и e-Fuses. Для этого существует удобный официальный инструмент esptool:

Чтение e-Fuses выполняем схожим образом, но уже через утилиту espefuse:

Нам интересны следующие e-Fuses:
FLASH_CRYPT_CNT = 127 (0b1111111)
JTAG_DISABLE = True (0b1)
DISABLE_DL_DECRYPT = True (0b1)
ABS_DONE_0 = True (0b1)
ABS_DONE_1 = False (0b0)
Остановимся на каждом подробнее:
Нечетное значение FLASH_CRYPT_CNT говорит о том, что на микроконтроллере активен Flash Encryption. Значит код в полученном дампе флешки зашифрован и просто так исследовать его не получится.
Активные JTAG_DISABLE и DISABLE_DL_DECRPYT означают, что отладка заблокирована и средствами самого контроллера расшифровать данные не выйдет.
Наконец, ABS_DONE_0 и ABS_DONE_1 показывают, что включен Secure Boot v1, поэтому запускать свой код запрещено (даже при наличии ключа шифрования флешки).
Вердикт: перед нами релизный вариант конфигурации, включена максимальная защита.
Differential Power Analysis
Воспользуемся самым простым методом получения прошивки из контроллера ESP32-D0WD-v3 — Differential Power Analysis (DPA). Суть в том, что по потреблению питания микроконтроллера можно косвенно вычислить значения, которыми он оперирует в регистрах. Самый банальный пример — когда в сигнале, полученном с линии питания микросхемы, можно отчетливо определить состояния некоторой внутренней линии:

В случае с ESP32 можно «подслушать» процесс шифрования AES. Конечно, настолько очевидно, как в примере на рисунке, увидеть ключ или данные не получится — здесь все 128 бит обрабатываются за один единственный «тик» процессора. Поэтому для вычисления значения ключа нужно использовать метод Correlation Power Analysis (CPA): выполняем десятки тысяч измерений с разными данными на входе, сравниваем с поведением предполагаемой модели и побайтно подбираем подходящий ключ.
Очень детально процесс извлечения ключа описан в статье за авторством Kévin Courdesses. На базе его же проекта esp-cpa получился вот такой девайс (который вы ещё встретите в других статьях про приключения с ESP32):

Этот девайс одновременно эмулирует SPI-флешку и выполняет замеры питания в момент расшифровки блока данных (а ещё умеет гличить импульсами питания, нагревать чип с высокой точностью, записывать UART, отлаживать по JTAG...). То есть эмулятор каждый раз посылает новый случайный блок данных, выжидает момент расшифровки и сохраняет замеры тока на линии:

Затем по накопленным данным с десятков и сотен тысяч замеров делается анализ корреляции — насколько реальные графики совпадают с «моделью» — предполагаемым поведением питания для конкретного значения части ключа. Сильно вдаваться в детали процесса не буду, на эту тему выйдет отдельная статья, пока что сосредоточимся на результате.
Результат анализа корреляции выглядит как 16 графиков (по одному на байт раунд-ключа) с 256 линиями (по одной на каждый вариант байта). Та линия, что сильно выделяется на фоне других, — верно угаданное значение.

Из графиков получаем ключ 8aef836729ebf14d4f17a88cdb2d69ce. С его помощью повторяем анализ уже для раунда 1 (поскольку в ESP32 применяется AES-256 и части ключа только от первого раунда недостаточно).

Таким образом получаем вторую половину ключа — 397f9cb7d00dd312d45fc7f1884661a8. Но и это еще не все!
В ESP32 ключ модифицируется в зависимости от смещения, поэтому путем анализа питания мы получили ключ уже после модификации (для смещения 0x1000). За то, как именно модифицируется ключ, отвечает e-Fuse FLASH_CRYPT_CONFIG (по умолчанию выставляется в максимальные 0xF). Алгоритм можно взять из официальной утилиты espsecure:
def flashencryption_tweak_key(key, offset, tweak_range):
addr = offset >> 5
key ^= ((mul1 addr) | ((mul2 addr) & mul2_mask)) & tweak_range
return int.to_bytes(key, length=32, byteorder="big", signed=False)Алгоритм симметричный, поэтому его применение к полученному ключу обращает модификации. В результате получаем исходный ключ шифрования, который уже можно использовать в утилите espsecure:
8A FF 83 65 29 EB B1 5D 4F 15 A8 8C 9B 2D 61 C6
39 7E 9C B7 F0 0D D7 12 D4 5D C7 F1 C8 46 69 A8Важный момент: ключ уникален для каждого устройства, так что если вам нужно расшифровать другой девайс (пусть даже той же модели и с такой же прошивкой), все придется проделывать заново ;)
Краткий обзор прошивки
У считанного дампа стандартный вид для проектов на основе ESP32: загрузчик, два OTA-слота, прошивка на базе FreeRTOS и шифрованный Non-Volatile Storage с отдельно сохраненным ключом. Нестандартными можно назвать только разделы с заводской конфигурацией и огромный раздел movie для хранения пользовательского видеоролика.
Смещение | Размер | Назначение |
0x0000 | 0xC0 | Подпись для Secure boot |
0x1000 | 0x5700 | Bootloader |
0x8000 | 0x1000 | Таблица разделов |
0x9000 | 0x4000 | NVS (Non-Volatile Storage), зашифровано keys |
0xD000 | 0x20 | otadata, информация о текущем используемом слоте прошивки |
0x10000 | 0x200000 | ota_1, слот прошивки №1 |
0x210000 | 0x200000 | ota_2, слот прошивки №2 |
0x410000 | 0x4000 | settings |
0x414000 | 0x4000 | data, информация об устройстве |
0x418000 | 0x3e0000 | movie |
0x7FF000 | 0x1000 | keys, ключи шифрования для NVS |
Неожиданностью стал включенный Stack Smash Protection: в начале каждой функции на верхушку стека записывалось случайное значение, а в конце проверялось, не изменилось ли оно.

В прошивке мы обнаружили многочисленные обработчики HTTP-протокола. Это неудивительно, ведь основной способ взаимодействия с устройством — по Wi-Fi через HTTP-запросы вида GET /xled/v1/…. При этом девайс подключается к существующей Wi-Fi сети, либо сам раздает ее. Помимо HTTP имеется возможность удаленного управления по протоколу MQTT (в том числе есть поддержка Apple Homekit).
В онлайн-источниках есть много информации о протоколе этих гирлянд, в том числе готовые проекты на Python. С их помощью можно управлять девайсом, причем если вы уже в Wi-Fi сети, никакого логина не требуется — даже знать IP-адрес не обязательно. Все гирлянды дружно отвечают на широковещательный UDP-запрос.
Уязвимости!
Конечно, полный доступ к управлению устройством из локальной сети — это не так интересно (для этого нужен пароль от Wi-Fi), как удаленный запуск своего кода на самом устройстве. Чтобы решить эту задачу, немного поковыряемся в прошивке и найдем интересную библиотеку blufi:

По сути, перед нами пример от разработчика SDK, как можно выполнить начальную настройку по Bluetooth LE (задать пароль для Wi-Fi). Интересна blufi тем, что около года назад в ней находили критические уязвимости, в том числе возможность записи произвольных данных по конкретному адресу:

Если кратко, протокол формирования секретного ключа принимает почти все параметры от клиента, при этом можно передать ключ размером до 8192 бит (хотя в коде библиотеки место предусмотрено только для 1024 бит). В результате получаем возможность перезаписи указателя на буфер и размера данных. А следующим запросом эти данные можно записать куда угодно. Самое странное, что разработчик микроконтроллера (Espressif) тогда не признал уязвимость и отказался создавать CVE, но обновил код примера-библиотеки:
