ESP32 – Segurança e proteção da flash

Neste artigo trataremos, de forma fácil e rápida, um assunto muito importante para quem pretende comercializar produtos com o ESP32, a segurança do seu hardware com o código presente na memória flash, a fim de impedir clonagem, furto e etc.

Utilizaremos a ESP-IDF v4.0-dev-76-g96aa08a0f-dirty (Ubuntu) para todos artigos desta série e não será abordado sobre como utilizar a IDF, sendo dever do leitor conhecer o funcionamento. Mais sobre a IDF: https://github.com/espressif/esp-idf

Figura 1 – Protegendo o ESP32.

Explicando em miúdos

Todo código (firmware) transferido ao ESP32 fica salvo, na maioria das versões, na memória flash externa, que diminui ainda mais a segurança, já que alguém pode simplesmente removê-la para leitura em um hardware externo e clonar, em segundos, nosso código que pode ter demorado anos para ser desenvolvido. Mesmo se a flash for embutida, como na versão “PICO”, é possível exportar todo conteúdo da flash com apenas um comando no terminal. Então, se todo conteúdo pode ser facilmente obtido, devemos nos proteger e é isso que a criptografia da flash do ESP32 nos proporciona.

Criptografia da flash

Atenção

  • Não abordaremos todas funções e características da criptografia da flash, sendo necessário que você estude MUITO BEM a documentação oficial, a fim de evitar qualquer dor de cabeça que pode ocorrer conforme a IDF se atualiza. Não somos responsáveis por qualquer uso errado de sua parte.
  • A criptografia da flash limita como e/ou quantas vezes é possível fazer upload de novos códigos. Caso feito incorretamente, você pode perder seu ESP32 e não será mais possível regrava-lo.
  • Abordaremos, por motivos de didática, apenas sobre a criptografia com uma chave pré-gerada, assim, podemos regravar o ESP32 sem qualquer restrição de quantidade.
  • Em ambientes que é necessário a maior segurança disponível, você não deve utilizar uma chave pré-gerada, deixando o próprio ESP32 gerar a sua, sendo individual para cada hardware. Além de também habilitar o Secure boot que não abordaremos aqui.

A criptografia da flash (AES-256) é uma característica presente no ESP32 que criptografa o conteúdo presente na flash. Quando habilitado, leituras físicas sem a chave não são suficientes para recuperar o conteúdo. Sendo assim, nos protegemos de quem tentar exportá-la para clonagem e etc. A chave é gravada em um bloco de eFuse, que pode ser protegido contra leitura e escrita (padrão) e, conhecendo a chave, podemos regravar códigos sem a limitação de quantidade, diferentemente do caso onde o ESP32 gera sua própria chave, onde estamos limitados em até 3 uploads físicos.

Vamos observar alguns itens relevantes sobre a criptografia:

  • Em um upload plaintext, o binário original (cru) é enviado ao microcontrolador.
  • Em um upload criptografado, o binário é enviado ao microcontrolador já criptografado pela IDF.
  • O eFuse “FLASH_CRYPT_CNT” (7-bit) é responsável pela permissão de uploads plaintext, pela contagem de uploads físicos plaintext (até 3x) e pelo controle do bootloader para criptografar o conteúdo da flash. Pode ser protegido contra R/W. Após 3 uploads plaintext, este eFuse chegará em seu máximo e aceitará apenas uploads criptografados.
    • Quando for um número par, o bootloader irá criptografar todo conteúdo da flash, logo, é necessário o upload plaintext.
    • Quando for um número ímpar, o bootloader não irá criptografar o conteúdo da flash, logo, é necessário o upload criptografado.
  • Se a chave não for conhecida (pré-gerada), temos no máximo 3 uploads físicos disponíveis (plaintext), que também nos permite desabilitar a criptografia. Se o “FLASH_CRYPT_CNT” for protegido enquanto ímpar, não será possível novos uploads plaintext.
  • Os binários “Bootloader”, “Partition table”, “OTA DATA”, todas “APP (seu código)” e as partições marcadas com a flag “encrypted” na tabela de partição serão criptografados. Partições que não estiverem marcados com “encrypted” não serão criptografados e poderão ser lidos externamente, tenha atenção ao utilizar APIs para acesso da flash como NVS e SPIFFS.
  • Se o “FLASH_CRYPT_CNT” não for protegido corretamente e/ou ainda houver alguma tentativa para upload plaintext, invasores podem inserir códigos maliciosos e ler o conteúdo de forma descriptografada pelo próprio ESP32 sem conhecimento da chave. Por causa disto, é comum protegê-lo contra escrita, a fim de evitar uploads plaintext.

Cientes dos detalhes básicos (há dezenas de detalhes extras na documentação oficial que você deve ler antes de efetuar os testes abaixo), vamos testar a criptografia da flash e ver se realmente funciona! Lembrando que utilizaremos a IDF no Ubuntu e quase todos comandos podem mudar de acordo com seu computador, projeto, endereços e muitas outras coisas. Os comandos utilizados aqui podem não servir para você, logo, terá que alterá-los de acordo com seu projeto, caminho de arquivos e etc. Este artigo é apenas uma demonstração e deve ser tomado como base, não seguido ao pé da letra. Os scripts utilizados estão na pasta da IDF, “esp-idf/components/esptool_py/esptool/”, tome bastante atenção ao uso dos caminhos dos arquivos e scripts, pois você pode estar tentando rodar o comando no local errado. Também tenha atenção na porta utilizada, seu ESP32 pode estar em uma porta diferente da nossa.

Primeiramente, vamos testar um código simples apenas para verificar sobre o que foi citado acima, sobre a leitura (clonagem) da memória flash sem proteção. Você pode ignorar essa parte.

void app_main()
{

	while (1)
	{
		ESP_LOGI("ESP32", "Embarcados...");
		vTaskDelay(pdMS_TO_TICKS(1000));
	}
}

Quando fazemos um upload padrão para placa, o computador utiliza um script python (esptool.py) com o comando “make flash”, que basicamente compila e faz upload (plaintext). Com o código enviado, vamos fazer uma leitura (sumário) dos eFuses para comparação após ativar a criptografia:

python espefuse.py --port /dev/ttyUSB0 summary

espefuse.py v2.6
Connecting........__
EFUSE_NAME         	Description = [Meaningful Value] [Readable/Writeable] (Hex Value)
----------------------------------------------------------------------------------------
Security fuses:
FLASH_CRYPT_CNT    	Flash encryption mode counter                 	= 0 R/W (0x0)
FLASH_CRYPT_CONFIG 	Flash encryption config (key tweak bits)      	= 0 R/W (0x0)
CONSOLE_DEBUG_DISABLE  Disable ROM BASIC interpreter fallback        	= 1 R/W (0x1)
ABS_DONE_0         	secure boot enabled for bootloader            	= 0 R/W (0x0)
ABS_DONE_1         	secure boot abstract 1 locked                 	= 0 R/W (0x0)
JTAG_DISABLE       	Disable JTAG                                  	= 0 R/W (0x0)
DISABLE_DL_ENCRYPT 	Disable flash encryption in UART bootloader   	= 0 R/W (0x0)
DISABLE_DL_DECRYPT 	Disable flash decryption in UART bootloader   	= 0 R/W (0x0)
DISABLE_DL_CACHE   	Disable flash cache in UART bootloader        	= 0 R/W (0x0)
BLK1               	Flash encryption key                         	 
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK2               	Secure boot key                              	 
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W
BLK3               	Variable Block 3                             	 
  = 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 R/W

Efuse fuses:
WR_DIS             	Efuse write disable mask                      	= 0 R/W (0x0)
RD_DIS             	Efuse read disablemask                        	= 0 R/W (0x0)
CODING_SCHEME      	Efuse variable block length scheme            	= 0 R/W (0x0)
KEY_STATUS         	Usage of efuse block 3 (reserved)             	= 0 R/W (0x0)

Config fuses:
XPD_SDIO_FORCE     	Ignore MTDI pin (GPIO12) for VDD_SDIO on reset	= 0 R/W (0x0)
XPD_SDIO_REG       	If XPD_SDIO_FORCE, enable VDD_SDIO reg on reset   = 0 R/W (0x0)
XPD_SDIO_TIEH      	If XPD_SDIO_FORCE & XPD_SDIO_REG, 1=3.3V 0=1.8V   = 0 R/W (0x0)
SPI_PAD_CONFIG_CLK 	Override SD_CLK pad (GPIO6/SPICLK)            	= 0 R/W (0x0)
SPI_PAD_CONFIG_Q   	Override SD_DATA_0 pad (GPIO7/SPIQ)           	= 0 R/W (0x0)
SPI_PAD_CONFIG_D   	Override SD_DATA_1 pad (GPIO8/SPID)           	= 0 R/W (0x0)
SPI_PAD_CONFIG_HD  	Override SD_DATA_2 pad (GPIO9/SPIHD)          	= 0 R/W (0x0)
SPI_PAD_CONFIG_CS0 	Override SD_CMD pad (GPIO11/SPICS0)           	= 0 R/W (0x0)
DISABLE_SDIO_HOST  	Disable SDIO host                             	= 0 R/W (0x0)

Identity fuses:
MAC                	Factory MAC Address                          	 
  = b4:e6:2d:96:dc:41 (CRC 84 OK) R/W
CHIP_VER_REV1      	Silicon Revision 1                            	= 1 R/W (0x1)
CHIP_VERSION       	Reserved for future chip versions             	= 2 R/W (0x2)
CHIP_PACKAGE       	Chip package identifier                       	= 0 R/W (0x0)

Calibration fuses:
BLK3_PART_RESERVE  	BLOCK3 partially served for ADC calibration data  = 0 R/W (0x0)
ADC_VREF           	Voltage reference calibration                 	= 1114 R/W (0x2)

Flash voltage (VDD_SDIO) determined by GPIO12 on reset (High for 1.8V, Low/NC for 3.3V).

Sabendo que este ESP32 não está protegido, podemos exportar o conteúdo da flash e usar para clonagem, engenharia reversa ou o que der na cabeça! Vamos ver se achamos a palavra usada no ESP_LOGI(), “Embarcados…”, ao ler o conteúdo da memória:

python esptool.py --port /dev/ttyUSB0 read_flash 0x0 4096000 dump.bin

esptool.py v2.6
Serial port /dev/ttyUSB0
Connecting....
Detecting chip type... ESP32
Chip is ESP32D0WDQ6 (revision 1)
Features: WiFi, BT, Dual Core, 240MHz, VRef calibration in efuse, Coding Scheme None
MAC: b4:e6:2d:96:dc:41
Uploading stub...
Running stub...
Stub running...
4096000 (100 %)
4096000 (100 %)
Read 4096000 bytes at 0x0 in 367.1 seconds (89.3 kbit/s)...
Hard resetting via RTS pin...

Utilizando o comando “hexdump” para ler o arquivo gerado, conseguimos achar a palavra utilizada no código sem criptografia:

Figura 2 – Memória do ESP32 clonada antes da criptografia.

Agora que a clonagem foi demonstrada com apenas um comando no terminal, indicando que seu produto sem proteção está totalmente vulnerável nas mãos de alguém, vamos nos proteger ativando a criptografia.

  1. Criando a chave

python espsecure.py generate_flash_encryption_key key.bin

Utilizaremos o próprio script da IDF para criar uma chave (256b), mas você pode utilizar qualquer método ou chave que desejar. O comando exportará a chave no arquivo “key.bin”, que você deve deixar uma cópia dentro da pasta do seu projeto, ficando algo similar com o nosso:

Figura 3 – Pasta do projeto com chave.
  1. Gravando a chave pré-gerada no eFuse

python espefuse.py --port /dev/ttyUSB0 burn_key flash_encryption key.bin

espefuse.py v2.6
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: 56 f7 1f 29 a6 6c 01 20 c5 ee 6b 55 79 36 d7 90 73 b3 f6 2d 48 a2 dc 03 b8 ad dc cb 1b 31 fe e5
Disabling read/write to key efuse block…

O comando acima gravou nossa chave no eFuse e automaticamente protegeu contra R/W, o que impede de qualquer um conseguir lê-la ou alterá-la. Apesar deste comando escrever no terminal a chave gravada, ao tentar ler a chave diretamente do eFuse (sumário) como feito anteriormente, é retornado:

BLK1               	Flash encryption key                         	 
  = ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? ?? -/-

Agora com uma chave conhecida no eFuse, temos a possibilidade de uploads ilimitados (criptografados), o que é muito interessante na fase de desenvolvimento.

  1. Ativando a criptografia da flash

Agora com a chave gravada, basta ativar a criptografia da flash no “menuconfig” em “Security features”.

Figura 4 – Ativando a criptografia da flash.

Atenção, se você não gravou a chave pré-gerada, ao dar o upload de um novo código com a criptografia ativada, o próprio ESP32 irá gerar uma chave que nem você, nós ou a Espressif poderá ler, lhe restando 3 uploads (plaintext) e/ou tentativas para desativar a criptografia, tome cuidado! Atualizações OTA são ilimitadas mesmo sem conhecimento da chave.

Após ativar a criptografia, vamos refazer o upload do código utilizado anteriormente de forma padrão, como utilizado antes (upload plaintext). Nesse primeiro boot, o bootloader irá criptografar todo conteúdo da memória e reiniciará, esse processo pode demorar um pouco então aguarde. Após o ESP32 reiniciar, indicando que a criptografia foi ativada, vamos fazer uma nova leitura da flash para ver se encontramos a palavra “Embarcados…” novamente.

Figura 5 – Memória do ESP32 clonada após criptografia.

Além da palavra “Embarcados…” não ser encontrada, nenhum texto legível foi visto, o que anteriormente era facilmente visto (Strings utilizadas pela própria IDF). O mesmo endereço que encontramos a palavra anteriormente, agora, não passa de um texto corrompido para quem tentar ler sem a chave!

Se olharmos o sumário dos eFuses novamente, podemos ver que o “FLASH_CRYPT_CNT” foi de 0 para 1, indicando que agora só aceita uploads criptografados, logo, se utilizarmos o upload padrão (plaintext), o ESP irá ficar em boot-loop com a seguinte mensagem:

make flash monitor -j4

ets Jun  8 2016 00:22:57

rst:0x1 (POWERON_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun  8 2016 00:22:57

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun  8 2016 00:22:57

rst:0x10 (RTCWDT_RTC_RESET),boot:0x13 (SPI_FAST_FLASH_BOOT)
flash read err, 1000
ets_main.c 371
ets Jun  8 2016 00:22:57

O “FLASH_CRYPT_CNT” não é protegido contra escrita por padrão, logo, alguém ainda pode desativar a criptografia, fazer upload (plaintext) e ler sua flash de forma descriptografada, logo, devemos proteger este eFuse contra escrita ENQUANTO estiver em um número ímpar ou utilizar o Secure boot.

  1. Protegendo o eFuse

Vamos proteger o eFuse “FLASH_CRYPT_CNT” contra escritas para impossibilitar qualquer tipo de upload sem conhecimento da chave, isso automaticamente permite que suas placas estejam protegidas de uploads não permitidos, forçando seu hardware a aceitar apenas códigos que tenham sidos criptografados com a chave. Apesar desta proteção funcionar com o mesmo intuito do Secure boot (impossibilitar uploads não permitidos), há alguns casos em que manter o Secure boot ativado pode ser melhor, entretanto, na maioria dos casos, protegendo o eFuse já não precisamos do Secure boot (pesquise melhor sobre isso se for usar em ambientes agressivos onde a segurança deve prevalecer).

Antes de proteger a escrita, devemos certificar-se que ele está em algum número ímpar através do sumário, que nos retornou “FLASH_CRYPT_CNT Flash encryption mode counter = 1 R/W (0x1)”

python espefuse.py --port /dev/ttyUSB0 write_protect_efuse FLASH_CRYPT_CNT

espefuse.py v2.6
Connecting........_
Permanently write-disabling efuse FLASH_CRYPT_CNT. This is an irreversible operation.
Type 'BURN' (all capitals) to continue.
BURN

A partir de agora, é impossível desabilitar a criptografia ou enviar códigos que não estejam criptografados pela chave presente no ESP32.

Ok, se o upload padrão não funciona mais, o que faremos? Criptografamos antes de enviar! Apesar de ser utilizado AES-256, a Espressif usa métodos diferentes de funcionamento (detalhes na documentação oficial), logo, precisamos criptografar pelo próprio script da IDF

  1. Criptografando os binários

O upload agora deve ser feito “manualmente”, sendo necessário criptografar e enviar os binários criptografados. Essa parte depende muito de projeto para projeto, principalmente nos caminhos de arquivos e endereço da memória, tome muita atenção.

5.1 Crie uma pasta “enc” dentro do seu projeto, ela irá guardar os binários criptografados pelo script.

5.2 Com o terminal aberto na pasta do seu projeto, use o comando “make all” para descobrir o que e onde os arquivos são gravados.

make all -j4

Toolchain path: /home/ze/esp/xtensa-esp32-elf/bin/xtensa-esp32-elf-gcc
Toolchain version: crosstool-ng-1.22.0-80-g6c4433a
Compiler version: 5.2.0
Project is not inside a git repository, will not use 'git describe' to determine PROJECT_VER.
App "esp32" version: 1
Python requirements from /home/ze/esp/esp-idf/requirements.txt are satisfied.

To flash all build output, run 'make flash' or:
python /home/ze/esp/esp-idf/components/esptool_py/esptool/esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 --before default_reset --after hard_reset write_flash -z --flash_mode dio --flash_freq 80m --flash_size detect 0x363000 /home/ze/esp/esp32/build/ota_data_initial.bin 0x1000 /home/ze/esp/esp32/build/bootloader/bootloader.bin 0x10000 /home/ze/esp/esp32/build/esp32.bin 0x8000 /home/ze/esp/esp32/build/partitions.bin

Podemos ver que nesse nosso projeto, onde utilizamos OTA e uma tabela de partições (custom), é feito o upload de 4 binários nos seus respectivos endereços.

ota_data_initial.bin: 0x363000.

bootloader.bin: 0x1000.

esp32.bin (o código em si): 0x10000.

partitions.bin: 0x8000.

Iremos criptografá-los individualmente e posteriormente, efetuar o upload. Não se esqueça que os caminhos dos arquivos devem ser alterados para o seu projeto.

5.3 Com o terminal aberto na pasta de scripts, use os comandos abaixo e não se esqueça de tomar muita atenção com os endereços de memória e caminho dos binários.

python espsecure.py encrypt_flash_data --keyfile key.bin --address 0x1000 -o /home/ze/esp/esp32/enc/bootloader.bin /home/ze/esp/esp32/build/bootloader/bootloader.bin

python espsecure.py encrypt_flash_data --keyfile key.bin --address 0x8000 -o /home/ze/esp/esp32/enc/partitions.bin /home/ze/esp/esp32/build/partitions.bin

python espsecure.py encrypt_flash_data --keyfile key.bin --address 0x10000 -o /home/ze/esp/esp32/enc/esp32.bin /home/ze/esp/esp32/build/esp32.bin

python espsecure.py encrypt_flash_data --keyfile key.bin --address 0x363000 -o /home/ze/esp/esp32/enc/ota_data_initial.bin /home/ze/esp/esp32/build/ota_data_initial.bin

5.4 Faça o upload dos binários criptografados pelo comando: 

python esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 921600 write_flash --flash_mode dio --flash_freq 80m --flash_size detect 0x1000 /home/ze/esp/esp32/enc/bootloader.bin 0x8000 /home/ze/esp/esp32/enc/partitions.bin 0x10000 enc/esp32.bin 0x363000 /home/ze/esp/esp32/enc/ota_data_initial.bin

Após o upload criptografado, o ESP32 iniciará normalmente como se nada tivesse acontecido. Esse método apesar de ser manual, pode ser automatizado por um script (bash), basta colocá-los num arquivo e executar no terminal.

Agora que a proteção do ESP32 esta ativada, podemos ficar mais relaxados na questão sobre alguém clonar nosso firmware, já que não será possível sem a chave gravada. Você deve ler toda documentação oficial e também pode ser interessante utilizar o Secure boot sem criptografia da flash, análise seu projeto e mãos na massa!

Referências

https://docs.espressif.com/projects/esp-idf/en/latest/security/flash-encryption.html
Licença Creative Commons Esta obra está licenciada com uma Licença Creative Commons Atribuição-CompartilhaIgual 4.0 Internacional.
Comentários:
Notificações
Notificar
9 Comentários
recentes
antigos mais votados
Inline Feedbacks
View all comments
Wenderson Costa
Wenderson Costa
27/11/2021 09:10

Olá , essa metodologia se aplica também a códigos usando micropython ?

jean fiuza
jean fiuza
21/06/2021 20:57

boa noite,
tem como me ajudar nesse erro ?

A fatal error occurred: MD5 of file does not match data in flash!

Jean Ricardo dos Santos Fiuza
Jean Ricardo dos Santos Fiuza
Reply to  jean fiuza
24/06/2021 11:35

Estou tendo esse erro no esp 32 depois que compilei um código , agora não aceita mais nenhum

Last edited 4 anos atrás by jean fiuza
Willian Henrique
Willian Henrique
23/05/2020 12:32

Parabêns pelo artigo, realmente muito bom e útil. Uma coisa que estou tentando é gerar o binário criptografado para entregar para outra pessoa, é possível? Nestes exemplos o binário sempre fica descriptografado, abraços,

Willian Henrique

R
R
27/02/2020 17:31

Muito legal, mas e para o caso de verificar o conteúdo da Flash automaticamente? Tem algum exemplo de CRC self-test? Para certificar de que não ocorreu corrompimento do firmware em algum momento, chips STM32 tem um módulo de hardware CRC, o ESP32 tem esta funcionalidade?

José Morais
José Morais
Reply to  R
27/02/2020 18:52

Sim, sua pergunta me parece estar mais ligada com atualizações remotas (OTA), mas de qualquer maneira, o arquivo binário enviado ao ESP32 tem “Magic Byte” e também vários bytes de CRC. O “hardware boot”, responsável por selecionar qual partição sera “bootada”, já faz a verificação se a partição esta OK ou corrompida.

Fernando Ginez da Silva
Fernando Ginez da Silva
04/12/2019 14:45

Parabéns pelo artigo! Muito útil.
Vale apenas acompanhar o desenrolar da vulnerabilidade de segurança que encontraram no chip. Veja o CVE (https://nvd.nist.gov/vuln/detail/CVE-2019-17391) e o artigo do descobridor da falha (https://limitedresults.com/2019/11/pwn-the-esp32-forever-flash-encryption-and-sec-boot-keys-extraction/).
Abs!

Alexandre Fernandes dos Anjos
Membro
26/09/2019 18:02

Ótimo artigo José, muito didático e detalhado…

Home » Software » ESP32 – Segurança e proteção da flash

EM DESTAQUE

WEBINARS

VEJA TAMBÉM

JUNTE-SE HOJE À COMUNIDADE EMBARCADOS

Talvez você goste: