Свое масштабное исследование микроконтроллера ESP32 я закончил изучением двух его важнейших функций: безопасной загрузки (Secure Boot) и флеш-шифрования (Flash Encryption). Моей целью было получить рабочий эксплоит, который обходит и то и другое. В этой статье я покажу, как полностью считать защищенные eFuses, в которых хранятся секретные ключи.

Первый ключ используется для флеш-шифрования (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 выглядит следующим образом.

Таблица eFuses
Таблица eFuses

Как видим, самые важные блоки — это BLK1 и BLK2, которые хранят соответственно FEK и SBK. От перезаписи их защищают WR_DIS_BLK1 и WR_DIS_BLK2, а от чтения — RD_DIS_BLK1 и RD_DIS_BLK2.

 

Безопасная загрузка

Безопасная загрузка (Secure Boot) стоит на страже подлинности и целостности прошивки, которая хранится во внешней флеш-памяти типа SPI. Атакующему ничего не стоит изменить содержимое внешней флеш-памяти и запустить на ESP32 зловредный код. Безопасная загрузка призвана защитить от подобной модификации прошивки.

Перед запуском прошивки безопасная загрузка создает цепочку доверия — от BootROM к загрузчику. Это гарантирует, что исполняемый на устройстве код подлинный и не может быть изменен без подписи бинарников (для этого нужен секретный ключ). Неподписанные бинарники устройство просто не запустит.

 

Как это работает?

Безопасную загрузку обычно устанавливают еще на производстве, которое считается безопасной средой.

Ключ безопасной загрузки (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.

Схема eFuses после настройки безопасной загрузки
Схема eFuses после настройки безопасной загрузки

Безопасная загрузка включена (ABS_DONE_0=1), и ее ключ (BLK2) больше нельзя считать или переписать. А параметр CONSOLE_DEBUG_DISABLE был прошит еще до того, как я получил плату. Теперь ESP32 удостоверяет загрузчик при каждом запуске, затем софт верифицирует приложение и после этого исполняет код.

 

Флеш-шифрование

Флеш-шифрование (Flash Encryption) — функция для шифрования содержимого встроенной в ESP32 SPI-флешки. Когда флеш-шифрование активировано, мы не можем получить доступ к большей части контента, просто физически считав SPI-носитель.

Если активировать эту функцию, то по дефолту шифруются:

  • загрузчик;
  • таблица разделов;
  • раздел приложений.

Другие типы данных могут быть зашифрованы в зависимости от условий:

  • дайджест загрузчика Secure Boot (если включена безопасная загрузка);
  • любые разделы, помеченные в таблице разделов флагом encrypted.
 

Как это работает?

Как и безопасная загрузка, флеш-шифрование обычно прошивается еще на производстве, которое считается безопасной средой.

Ключ флеш-шифрования (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 выглядят так.

Сводка eFuses при максимальных настройках безопасности. Флеш-шифрование и безопасная загрузка включены. BLK1 и BLK2 защищены от чтения и перезаписи
Сводка eFuses при максимальных настройках безопасности. Флеш-шифрование и безопасная загрузка включены. BLK1 и BLK2 защищены от чтения и перезаписи
 

Тестируем приложение в режиме полной безопасности 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»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.


  • Подпишись на наc в Telegram!

    Только важные новости и лучшие статьи

    Подписаться

  • Подписаться
    Уведомить о
    2 комментариев
    Старые
    Новые Популярные
    Межтекстовые Отзывы
    Посмотреть все комментарии