From 4fc11f1b914c88de07359eaf01f2043967f5737a Mon Sep 17 00:00:00 2001 From: Tim Schneeberger Date: Sun, 21 Sep 2025 11:30:38 +0200 Subject: [PATCH] patterns: Add ESP32 image pattern (#449) Co-authored-by: Nik --- README.md | 1 + patterns/esp32_image.hexpat | 119 ++++++++++++++++++ .../patterns/test_data/esp32_image.hexpat.bin | Bin 0 -> 15104 bytes 3 files changed, 120 insertions(+) create mode 100644 patterns/esp32_image.hexpat create mode 100644 tests/patterns/test_data/esp32_image.hexpat.bin diff --git a/README.md b/README.md index 7fc5ec9..3d2f2d7 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,7 @@ Everything will immediately show up in ImHex's Content Store and gets bundled wi | DTA | | [`patterns/max_v104.hexpat`](patterns/max_v104.hexpat) | Mechanized Assault and Exploration v1.04 (strategy game) save file format | | DTED | | [`patterns/dted.hexpat`](patterns/dted.hexpat) | Digital Terrain Elevation Data (DTED) | | ELF | `application/x-executable` | [`patterns/elf.hexpat`](patterns/elf.hexpat) | ELF header in elf binaries | +| ESP32 Image | | [`patterns/esp32_image.hexpat`](patterns/esp32_image.hexpat) | Firmware image format for the ESP32 chip family | | EVTX | `application/x-ms-evtx` | [`patterns/evtx.hexpat`](patterns/evtx.hexpat) | MS Windows Vista Event Log | | EXFAT | | [`patterns/fs/exfat.hexpat`](patterns/fs/exfat.hexpat) | Extensible File Allocation Table (exFAT) | | EXT4 | | [`patterns/fs/ext4.hexpat`](patterns/fs/ext4.hexpat) | Ext4 File System | diff --git a/patterns/esp32_image.hexpat b/patterns/esp32_image.hexpat new file mode 100644 index 0000000..f35daf9 --- /dev/null +++ b/patterns/esp32_image.hexpat @@ -0,0 +1,119 @@ +#pragma author timschneeb +#pragma description ESP32 Firmware Image Format + +#pragma endian little + +// Reference: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-reference/system/app_image_format.html + +import std.mem; + +enum esp_chip_id_t : u16 { + ESP_CHIP_ID_ESP32 = 0x0000, + ESP_CHIP_ID_ESP32S2 = 0x0002, + ESP_CHIP_ID_ESP32C3 = 0x0005, + ESP_CHIP_ID_ESP32S3 = 0x0009, + ESP_CHIP_ID_ESP32C2 = 0x000C, + ESP_CHIP_ID_ESP32C6 = 0x000D, + ESP_CHIP_ID_ESP32H2 = 0x0010, + ESP_CHIP_ID_ESP32P4 = 0x0012, + ESP_CHIP_ID_ESP32C5 = 0x0017, + ESP_CHIP_ID_ESP32C61 = 0x0014, + ESP_CHIP_ID_ESP32H21 = 0x0019, + ESP_CHIP_ID_ESP32H4 = 0x001C, + ESP_CHIP_ID_INVALID = 0xFFFF +}; + +enum esp_image_spi_mode_t : u8 { + QIO, + QOUT, + DIO, + DOUT, + FAST_READ, + SLOW_READ +}; + +enum esp_image_spi_freq_t : u8 { + DIV_2, + DIV_3, + DIV_4, + DIV_1 = 0xF +}; + +enum esp_image_flash_size_t : u8 { + FLASH_1MB, + FLASH_2MB, + FLASH_4MB, + FLASH_8MB, + FLASH_16MB, + FLASH_32MB, + FLASH_64MB, + FLASH_128MB, + FLASH_MAX +}; + +bitfield spi_config_t { + esp_image_spi_freq_t spi_speed : 4; + esp_image_flash_size_t spi_size : 4; +}; + +const u32 ESP_APP_DESC_MAGIC_WORD = 0xABCD5432; + +struct esp_app_desc_t { + u32 magic_word; // ESP_APP_DESC_MAGIC_WORD + u32 secure_version; + u32 reserv1[2]; + char version[32]; + char project_name[32]; + char compile_time[16]; + char compile_date[16]; + char idf_ver[32]; + u8 app_elf_sha256[32]; + u16 min_efuse_blk_rev_full; + u16 max_efuse_blk_rev_full; + u8 mmu_page_size; // in log2 format + u8 reserv3[3]; + u32 reserv2[18]; +}; + +struct esp_image_header_t { + u8 magic; // 0xE9 + u8 segment_count; + esp_image_spi_mode_t spi_mode; + spi_config_t spi_cfg; + u32 entry_addr; + u8 wp_pin [[comment("Write protect pin")]]; + u8 spi_pin_drv[3] [[comment("Drive settings for the SPI flash pins")]]; + esp_chip_id_t chip_id; + u8 min_chip_rev [[comment("Deprecated, replaced by min_chip_rev_full")]]; + u16 min_chip_rev_full [[comment("Minimal revision (major*100+minor)")]]; + u16 max_chip_rev_full [[comment("Maximal revision (major*100+minor)")]]; + u8 reserved[4]; + + u8 hash_appended [[comment("If 1, a SHA256 digest 'simple hash' (of the entire image) is appended after the checksum")]]; +}; + +struct esp_image_segment_header_t { + u32 load_addr; + u32 data_len; +}; + +union esp_image_segment_data_t { + u8 data[parent.header.data_len] [[hidden]]; + + // Application segment + if (std::mem::read_unsigned(addressof(data), 4) == ESP_APP_DESC_MAGIC_WORD) { + esp_app_desc_t app_descriptor; + } +}; + +struct esp_image_segment_t { + esp_image_segment_header_t header; + esp_image_segment_data_t data; +}; + +struct esp_image_t { + esp_image_header_t header; + esp_image_segment_t segments[header.segment_count]; +}; + +esp_image_t image @ 0x0; diff --git a/tests/patterns/test_data/esp32_image.hexpat.bin b/tests/patterns/test_data/esp32_image.hexpat.bin new file mode 100644 index 0000000000000000000000000000000000000000..fcf755ebedbdef14738d8cbae2164b51f4b0f17a GIT binary patch literal 15104 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 b@_k|1vWKcxcU-)F>ZA87AMO6Duk8N;VAoTW literal 0 HcmV?d00001