Свое масштабное исследование микроконтроллера ESP32 я закончил изучением двух его важнейших функций: безопасной загрузки (Secure Boot) и флеш-шифрования (Flash Encryption). Моей целью было получить рабочий эксплоит, который обходит и то и другое. В этой статье я покажу, как полностью считать защищенные eFuses, в которых хранятся секретные ключи.
Первый ключ используется для флеш-шифрования (BLK1), второй — для безопасной загрузки (BLK2). Поскольку вендор не может выпустить патч, который предотвратит подобную атаку на уже выпущенных устройствах, это «пожизненный» взлом. Совместно с вендором Espressif мы решили пойти на ответственное раскрытие обнаруженной уязвимости (CVE-2019-17391).
Это перевод статьи пентестера с псевдонимом LimitedResults, впервые опубликованной в его блоге. Перевела Алёна Георгиева. Все иллюстрации в статье принадлежат автору.
Статья завершает цикл публикаций Pwn the ESP32. Предыдущие статьи читай в блоге автора (на английском):
Однократно программируемая (One-Time Programmable, OTP) память — это тип энергонезависимой памяти, в которую можно записать данные только один раз. Записав однажды, их уже нельзя изменить — после отключения питания данные все равно остаются на носителе. В ESP32 такая память базируется на технологии eFuses (electronic Fuses) — и хранит системные параметры, настройки безопасности и конфиденциальные данные.
По сути eFuse — это один бит энергонезависимой памяти; единожды получив значение 1, он уже никогда не поменяет его на 0. Контроллер eFuses программным методом присваивает каждому биту необходимый системный параметр. Некоторые из этих параметров либо считываются софтом через контроллер eFuses, либо используются железом напрямую. Часть таких электронных предохранителей защищают доступ к чтению и записи данных.
Espressif предоставляет полную документацию по технологии eFuse. В техническом руководстве есть и глава, посвященная контроллеру eFuses (глава 20). Этот контроллер управляет массивами eFuses и содержит четыре блока eFuses каждый длиной 256 бит (не все из них доступны):
Представление eFuses выглядит следующим образом.
Как видим, самые важные блоки — это BLK1 и BLK2, которые хранят соответственно FEK и SBK. От перезаписи их защищают WR_DIS_BLK1 и WR_DIS_BLK2, а от чтения — RD_DIS_BLK1 и RD_DIS_BLK2.
Безопасная загрузка (Secure Boot) стоит на страже подлинности и целостности прошивки, которая хранится во внешней флеш-памяти типа SPI. Атакующему ничего не стоит изменить содержимое внешней флеш-памяти и запустить на ESP32 зловредный код. Безопасная загрузка призвана защитить от подобной модификации прошивки.
Перед запуском прошивки безопасная загрузка создает цепочку доверия — от BootROM к загрузчику. Это гарантирует, что исполняемый на устройстве код подлинный и не может быть изменен без подписи бинарников (для этого нужен секретный ключ). Неподписанные бинарники устройство просто не запустит.
Безопасную загрузку обычно устанавливают еще на производстве, которое считается безопасной средой.
Как мы уже говорили, у ESP32 есть OTP-память, которая состоит из четырех блоков по 256 eFuses (всего 1024 бит). Ключ безопасной загрузки (SBK) вшивают в электронные предохранители блока BLK2 (256 бит) на производстве. Именно с его помощью в режиме AES-256 ECB создается цепочка доверия от BootROM к загрузчику — чтобы проверить последний. Ключ нельзя считать или модифицировать — блок BLK2 защищен специальными eFuses.
Понятно, что такой ключ нужно хранить в тайне — чтобы злоумышленник не мог создать новый образ загрузчика, способный пройти верификацию. Хорошо бы также присваивать каждому устройству уникальный ключ — чтобы уменьшить масштаб катастрофы в случае, если один из SBK утечет или будет дешифрован.
Во время производства вендор также генерит пару ключей ECDSA — секретный и открытый. Первый хранят в тайне. Второй включают в конец образа загрузчика — он отвечает за проверку подписи в образах приложений.
На адрес 0x0 флеш-памяти SPI вшивают 192-байтный дайджест. На выходе мы получаем 192 байт данных: 128 рандомных байт плюс содержательный дайджест из 64 байт, вычисленных по хеш-функции SHA-512. Выглядит все это так:
Digest = SHA-512(AES-256((bootloader.bin + ECDSA publ. key), 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-носитель.
Если активировать эту функцию, то по дефолту шифруются:
Другие типы данных могут быть зашифрованы в зависимости от условий:
encrypted
.
Как и безопасная загрузка, флеш-шифрование обычно прошивается еще на производстве, которое считается безопасной средой.
Все то же самое: OTP-память ESP32, состоящая из четырех блоков по 256 eFuses, всего 1024 бит. Ключ флеш-шифрования (FEK) вшивается в блок электронных предохранителей BLK1. Содержимое флеш-памяти шифруется с помощью AES-256.
Ключ флеш-шифрования хранится в eFuses внутри чипа и защищен от программного доступа. Его нельзя считать или модифицировать — за это отвечают защитные eFuses.
Флеш-шифрование использует алгоритм AES-256, при котором ключ «корректируется» смещением каждого 32-байтного блока флеш-памяти. Это значит, что каждый 32-байтный блок (два последовательных 16-байтных AES-блока) шифруется уникальным ключом, основанным на общем ключе флеш-шифрования (FEK). Прозрачность доступа к флеш-памяти в ESP32 обеспечивает функция отображения флеш-кеша: любые области флеш-памяти, сопоставленные с адресным пространством, при чтении понятным образом дешифруются.
Итак, теперь я решил сосредоточиться на 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 выглядят так.
Для максимальной безопасности Espressif рекомендует использовать как безопасную загрузку, так и флеш-шифрование. Включим обе функции и протестим их с помощью специально скомпилированного и прошитого приложения.
Тестовое приложение можно запилить простым main.c
, например:
void app_main() { while(1) { printf("Hello from SEC boot K1 & FE !n"); vTaskDelay(1000 / portTICK_PERIOD_MS); } }
Для компиляции я активирую безопасную загрузку и флеш-шифрование с помощью make menuconfig
.
Все образы подписаны, зашифрованы и один за другим прошиты в память ESP32 (я делаю это вручную, чтобы получить максимум информации о процессе флеш-шифрования):
Материалы из последних выпусков становятся доступны по отдельности только через два месяца после публикации. Чтобы продолжить чтение, необходимо стать участником сообщества «Xakep.ru».
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», увеличит личную накопительную скидку и позволит накапливать профессиональный рейтинг Xakep Score! Подробнее
1 год5380 р. |
1 месяц720 р. |
Я уже участник «Xakep.ru»
Читайте также
Последние новости