Содержание статьи
- Однократно программируемые предохранители
- Контроллер eFuses на ESP32
- Безопасная загрузка
- Как это работает?
- Настройка безопасной загрузки
- Флеш-шифрование
- Как это работает?
- Время настроить флеш-шифрование
- Тестируем приложение в режиме полной безопасности ESP32
- Компилируем тестовое приложение
- Прошиваем тестовое приложение
- Проверяем содержимое флеш-памяти
- Пора взламывать!
- Черный ящик
- Готовим оборудование
- Модифицируем плату
- Настройка оборудования
- Режим загрузки и команда dump
- Результат
- Полный и окончательный эксплоит
- Заключение
- Таймлайн
Первый ключ используется для флеш-шифрования (BLK1), второй — для безопасной загрузки (BLK2). Поскольку вендор не может выпустить патч, который предотвратит подобную атаку на уже выпущенных устройствах, это «пожизненный» взлом. Совместно с вендором Espressif мы решили пойти на ответственное раскрытие обнаруженной уязвимости (CVE-2019-17391).
INFO
Это перевод статьи пентестера с псевдонимом LimitedResults, впервые опубликованной в его блоге. Перевела Алёна Георгиева. Все иллюстрации в статье принадлежат автору.
Статья завершает цикл публикаций Pwn the ESP32. Предыдущие статьи читай в блоге автора (на английском):
Однократно программируемые предохранители
Однократно программируемая (One-Time Programmable, OTP) память — это тип энергонезависимой памяти, в которую можно записать данные только один раз. Записав однажды, их уже нельзя изменить — после отключения питания данные все равно остаются на носителе. В ESP32 такая память базируется на технологии eFuses (electronic Fuses) — и хранит системные параметры, настройки безопасности и конфиденциальные данные.
По сути eFuse — это один бит энергонезависимой памяти; единожды получив значение 1, он уже никогда не поменяет его на 0. Контроллер eFuses программным методом присваивает каждому биту необходимый системный параметр. Некоторые из этих параметров либо считываются софтом через контроллер eFuses, либо используются железом напрямую. Часть таких электронных предохранителей защищают доступ к чтению и записи данных.
Контроллер eFuses на ESP32
Espressif предоставляет полную документацию по технологии eFuse. В техническом руководстве есть и глава, посвященная контроллеру eFuses (глава 20). Этот контроллер управляет массивами eFuses и содержит четыре блока eFuses каждый длиной 256 бит (не все из них доступны):
- EFUSE_BLK0 используется исключительно для системных задач;
- EFUSE_BLK1 содержит ключ флеш-шифрования (Flash Encryption Key, FEK);
- EFUSE_BLK2 содержит ключ безопасной загрузки (Secure Boot Key, SBK);
- EFUSE_BLK3 частично резервируется под кастомный MAC-адрес или полностью занят пользовательским приложением.
Представление eFuses выглядит следующим образом.
Как видим, самые важные блоки — это BLK1 и BLK2, которые хранят соответственно FEK и SBK. От перезаписи их защищают WR_DIS_BLK1 и WR_DIS_BLK2, а от чтения — RD_DIS_BLK1 и RD_DIS_BLK2.
Безопасная загрузка
Безопасная загрузка (Secure Boot) стоит на страже подлинности и целостности прошивки, которая хранится во внешней флеш-памяти типа SPI. Атакующему ничего не стоит изменить содержимое внешней флеш-памяти и запустить на ESP32 зловредный код. Безопасная загрузка призвана защитить от подобной модификации прошивки.
Перед запуском прошивки безопасная загрузка создает цепочку доверия — от BootROM к загрузчику. Это гарантирует, что исполняемый на устройстве код подлинный и не может быть изменен без подписи бинарников (для этого нужен секретный ключ). Неподписанные бинарники устройство просто не запустит.
WWW
- Исчерпывающая документация по безопасной загрузке на сайте Espressif
Как это работает?
Безопасную загрузку обычно устанавливают еще на производстве, которое считается безопасной средой.
Ключ безопасной загрузки (SBK) сохраняют в eFuses
Как мы уже говорили, у ESP32 есть OTP-память, которая состоит из четырех блоков по 256 eFuses (всего 1024 бит). Ключ безопасной загрузки (SBK) вшивают в электронные предохранители блока BLK2 (256 бит) на производстве. Именно с его помощью в режиме AES-256 ECB создается цепочка доверия от BootROM к загрузчику — чтобы проверить последний. Ключ нельзя считать или модифицировать — блок BLK2 защищен специальными eFuses.
Понятно, что такой ключ нужно хранить в тайне — чтобы злоумышленник не мог создать новый образ загрузчика, способный пройти верификацию. Хорошо бы также присваивать каждому устройству уникальный ключ — чтобы уменьшить масштаб катастрофы в случае, если один из SBK утечет или будет дешифрован.
Пара ключей ECDSA
Во время производства вендор также генерит пару ключей ECDSA — секретный и открытый. Первый хранят в тайне. Второй включают в конец образа загрузчика — он отвечает за проверку подписи в образах приложений.
Дайджест
На адрес 0x0 флеш-памяти SPI вшивают 192-байтный дайджест. На выходе мы получаем 192 байт данных: 128 рандомных байт плюс содержательный дайджест из 64 байт, вычисленных по хеш-функции SHA-512. Выглядит все это так:
Digest = SHA-512(AES-256((bootloader.bin + ECDSA publ. key), SBK))
Остановимся на SBK
Исходя из уже сказанного, я решил сосредоточиться на SBK, который хранится в eFuses блока BLK2. Если я его вычислю, то смогу подписать свой зловредный загрузчик и избежать верификации ECDSA.
Настройка безопасной загрузки
Благодаря документации я знаю, что на новой плате ESP32 безопасную загрузку можно включить вручную:
$ espefuse.py burn_key secure_boot ./hello_world_k1/secure-bootloader-key-256.bin
$ espefuse.py burn_efuse ABS_DONE_0
После перезагрузки можно увидеть представление eFuses, используя инструмент espefuse.py
.
Безопасная загрузка включена (ABS_DONE_0=1
), и ее ключ (BLK2) больше нельзя считать или переписать. А параметр CONSOLE_DEBUG_DISABLE
был прошит еще до того, как я получил плату. Теперь ESP32 удостоверяет загрузчик при каждом запуске, затем софт верифицирует приложение и после этого исполняет код.
Флеш-шифрование
Флеш-шифрование (Flash Encryption) — функция для шифрования содержимого встроенной в ESP32 SPI-флешки. Когда флеш-шифрование активировано, мы не можем получить доступ к большей части контента, просто физически считав SPI-носитель.
Если активировать эту функцию, то по дефолту шифруются:
- загрузчик;
- таблица разделов;
- раздел приложений.
Другие типы данных могут быть зашифрованы в зависимости от условий:
- дайджест загрузчика Secure Boot (если включена безопасная загрузка);
- любые разделы, помеченные в таблице разделов флагом
encrypted
.
WWW
- Полная документация по функции Flash Encryption на сайте Espressif
Как это работает?
Как и безопасная загрузка, флеш-шифрование обычно прошивается еще на производстве, которое считается безопасной средой.
Ключ флеш-шифрования (FEK) сохраняют в eFuses
Все то же самое: OTP-память ESP32, состоящая из четырех блоков по 256 eFuses, всего 1024 бит. Ключ флеш-шифрования (FEK) вшивается в блок электронных предохранителей BLK1. Содержимое флеш-памяти шифруется с помощью AES-256.
Ключ флеш-шифрования хранится в eFuses внутри чипа и защищен от программного доступа. Его нельзя считать или модифицировать — за это отвечают защитные eFuses.
Флеш-шифрование с помощью AES-256
Флеш-шифрование использует алгоритм AES-256, при котором ключ «корректируется» смещением каждого 32-байтного блока флеш-памяти. Это значит, что каждый 32-байтный блок (два последовательных 16-байтных AES-блока) шифруется уникальным ключом, основанным на общем ключе флеш-шифрования (FEK). Прозрачность доступа к флеш-памяти в ESP32 обеспечивает функция отображения флеш-кеша: любые области флеш-памяти, сопоставленные с адресным пространством, при чтении понятным образом дешифруются.
Остановимся на FEK
Итак, теперь я решил сосредоточиться на FEK, который хранится в eFuses блока BLK1. Заполучив его, я смогу зашифровать новый загрузчик или расшифровать всю прошивку, что тоже неплохо.
Время настроить флеш-шифрование
Для начала я сгенерировал свой собственный ключ и прошил его в BLK2:
$ espsecure.py generate_flash_encryption_key my_flash_encryption_key.bin
$ hexdump my_flash_encryption_key.bin
0000000 c838 e375 7633 1541 5ff9 4365 f2dd 2ce9
0000010 1f78 42a0 bf53 8f14 68ce 009f 5586 9b52
$ espefuse.py --port /dev/ttyUSB0 burn_key flash_encryption my_flash_encryption_key.bin
espefuse.py v2.7-dev
Connecting......
Write key in efuse block 1. The key block will be read and write protected (no further changes or readback). This is an irreversible operation.
Type 'BURN' (all capitals) to continue.
BURN
Burned key data. New value: 9b 52 55 86 00 9f 68 ce 8f 14 bf 53 42 a0 1f 78 2c e9 f2 dd 43 65 5f f9 15 41 76 33 e3 75 c8 38
Disabling read/write to key efuse block...
Затем назначил ответственные за активацию флеш-шифрования eFuses:
$ espefuse.py burn_efuse FLASH_CRYPT_CONFIG 0xf
$ espefuse.py burn_efuse FLASH_CRYPT_CNT
Для чтения eFuses также годится команда dump
:
$ espefuse.py --port /dev/ttyUSB0 dump
espefuse.py v2.7-dev
Connecting....
EFUSE block 0:
00130180 bf4dbb34 00e43c71 0000a000 00000430 f0000000 00000054
EFUSE block 1:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
EFUSE block 2:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
EFUSE block 3:
00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000000
По результатам моего реверса в значении EFUSE block0
первое 32-битное слово соответствует настройкам безопасности:
00130180 = 00000000 00010011 00000001 10000000
Две единицы в конце второго сегмента относятся к eFuses, которые защищают BLK2 и BLK1 от чтения. Любые попытки прочесть BLK1 или BLK2 возвращают 0x00
.
В итоге настройки безопасности ESP32 выглядят так.
Тестируем приложение в режиме полной безопасности ESP32
Для максимальной безопасности Espressif рекомендует использовать как безопасную загрузку, так и флеш-шифрование. Включим обе функции и протестим их с помощью специально скомпилированного и прошитого приложения.
Компилируем тестовое приложение
Тестовое приложение можно запилить простым main.c
, например:
void app_main() {
while(1) {
printf("Hello from SEC boot K1 & FE !\n");
vTaskDelay(1000 / portTICK_PERIOD_MS);
}
}
Для компиляции я активирую безопасную загрузку и флеш-шифрование с помощью make menuconfig
.
Прошиваем тестовое приложение
Все образы подписаны, зашифрованы и один за другим прошиты в память ESP32 (я делаю это вручную, чтобы получить максимум информации о процессе флеш-шифрования):
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»