Introduction
The build tools of the ESP32 family (esptool, ESP-IDF) write a firmware file as an application image: a small header, a list of
memory segments to copy into place, a checksum and usually a SHA-256. The bootloader and every application are such images; the
partition table is a separate small file. A .bin extension is used by hundreds of formats, so the explorer does not take a
.bin for this one by name: Identify offers it when a strict test passes, and the file opens under Read as. Its code segments open
disassembled with the Xtensa decoder (ESP32, ESP32-S2, ESP32-S3) or the RISC-V
decoder (ESP32-C and -H chips), chosen by the chip id in the header or by a dropdown.
The image header
24 bytes, little-endian. The names are those of esp_image_header_t in ESP-IDF.
| Offset | Field | Meaning |
|---|---|---|
| 0 | magic | 0xE9 (ESP_IMAGE_HEADER_MAGIC). |
| 1 | segment count | 1 to 16. |
| 2 | SPI mode | The flash read mode (0 QIO, 1 QOUT, 2 DIO, 3 DOUT, then fast and slow read). |
| 3 | speed and size | Low 4 bits the flash clock (0 40 MHz, 1 26 MHz, 2 20 MHz, 0xF 80 MHz); high 4 bits the flash size (0 1 MB to 7 128 MB). |
| 4 | entry point | Where the CPU starts. |
| 8 | WP pin | The write-protect pin when the SPI pins are set by eFuse; 0xEE when not. |
| 9 | SPI pin drive | Three bytes of drive settings. |
| 12 | chip id | 0 ESP32, 2 ESP32-S2, 5 ESP32-C3, 9 ESP32-S3, 12 (0xC) ESP32-C2, 13 (0xD) ESP32-C6, 16 (0x10) ESP32-H2, 18 (0x12) ESP32-P4, 20 (0x14) ESP32-C61, 23 (0x17) ESP32-C5. |
| 14 | minimum chip revision | The old one-byte field; followed at 15 by the full 16-bit minimum (major × 100 + minor) and at 17 by the full maximum. |
| 19 | reserved | Four bytes, zero. |
| 23 | hash appended | 1 when a SHA-256 follows the checksum. |
Segments
After the header come the segments, each an 8-byte header (load address, data length) and its data. The load address says where the bootloader copies the data, or what it maps from flash: a DROM segment (read-only data), an IROM segment (code run from flash), or IRAM and DRAM (copied to internal memory). The explorer names the region for the ESP32 and the ESP32-S3 (ESP32: DROM 0x3F400000, IROM 0x400D0000, IRAM 0x40080000, DRAM 0x3FFB0000; ESP32-S3: DROM 0x3C000000, IROM 0x42000000, IRAM 0x40370000, DRAM 0x3FC88000); those ranges are from the ESP-IDF linker maps as remembered, and the ESP32-S3 ones agree with the firmware ELF files on this machine. Code segments (IROM and IRAM) are disassembled.
Checksum
The segments are followed by padding so that the next byte is at an offset of 15 modulo 16, then one checksum byte: the XOR of all the segment data bytes with
0xEF. The explorer checks it.
The appended SHA-256
When the header's hash flag is 1, a 32-byte SHA-256 of every byte before it (header, segments, padding and checksum) follows the checksum. The explorer computes it and says whether it matches.
The application description
An application's first read-only data segment begins with esp_app_desc_t (256 bytes, at the start of the segment data, or 32 bytes in on older images):
magic 0xABCD5432, secure version, the version string (32), the project name (32), compile time (16) and date (16), the IDF version (32), the SHA-256 of the
application ELF (32), the minimum and maximum eFuse block revision, and the MMU page size. The bootloader has none.
The partition table
Normally at flash offset 0x8000: entries of 32 bytes. Each starts with the magic 0xAA50 (bytes 0xAA, 0x50), then type (0 app, 1 data), subtype
(app: 0 factory, 0x10 to 0x1F ota_0 to ota_15, 0x20 test; data: 0 ota, 1 phy, 2 nvs, 3 coredump, 4 nvs_keys, 0x81 fat, 0x82 spiffs, 0x83 littlefs), a 4-byte offset, a 4-byte size,
a 16-byte name and 4 bytes of flags (bit 0 encrypted). An entry starting 0xEB 0xEB holds an MD5 (bytes 16 to 31) of all the entries before it; 0xFF bytes end the table.
The explorer verifies the MD5. The subtype names are from the ESP-IDF tools as remembered.
How the explorer recognises them
A first byte of 0xE9 is far too weak alone. The test needs all of: a segment count of 1 to 16, a flash mode of 0 to 5, a known clock and size, a known chip id,
zero reserved bytes, an entry point in 0x40000000 to 0x5FFFFFFF, segment headers that walk exactly inside the file (lengths multiple of 4), and a correct checksum byte.
Measured: 0 of 100,000 random files (64, 1,024 and 4,096 bytes), 0 of 100,000 random 512-byte files with 0xE9 forced as the first byte, and of 242,443 .bin, .img,
.rom and .sav files on the machine this was built on, 227 claimed: all of them ESP application images, partition tables, merged flash images or MicroPython firmware in
ESP folders, none anything else.
Not described
Known, not yet covered
- Viewer: a merged flash image (bootloader at 0x1000 or 0, partition table at 0x8000, applications after) is not shown as nested regions; open the parts.
- Viewer: the ESP8266 formats (a different header) and the 0xEA IROM boot image; the extended header's chip-revision rules for older chips; secure-boot signature blocks; encrypted images.
- Viewer: the memory-map names for chips other than the ESP32 and ESP32-S3.
Unknown
- Reference: the memory ranges and partition subtype names are from memory of ESP-IDF; the header, chip ids and application description were read from ESP-IDF's headers.
Links
Reference
- esp_app_format.h – the header structures and chip ids
- esp_app_desc.h – the application description