Writeup: Hello-ESP - EspilonCTF (ESP)
Challenge Overview
| Field | Details |
|---|---|
| Name | Hello ESP |
| Category | Reverse Engineering / Firmware |
| Platform | ESP32 (Xtensa LX6) |
| Flag Format | ESPILON{...} |
Flag
ESPILON{st4rt_th3_w1r3}
Files Provided
| File | Size | Description |
|---|---|---|
| bootloader.bin | 26,752 B | ESP32 bootloader |
| partition-table.bin | 3,072 B | Partition table |
| hello-espilon.bin | 178,848 B | Main application firmware |
Solution Walkthrough
Step 1: Initial Recon
Extracted HELLO-ESP.zip to obtain three .bin files and a README with flashing instructions. Running strings on hello-espilon.bin revealed key output messages:
Welcome to ESPILON ctf new category ESP have fun!
=== Hello ESP ===
System ready.
Encrypted flag:
XOR Key:
This indicated the firmware prints an XOR-encrypted flag at runtime.
Step 2: Binary Structure Analysis
Parsed the ESP32 application image header (magic byte 0xE9):
| Segment | Load Address | Size | Content |
|---|---|---|---|
| 0 | 0x3F400020 | 37,932 B | DROM (read-only data) |
| 1 | 0x3FFB0000 | 8,988 B | DRAM (initialized data) |
| 2 | 0x40080000 | 18,592 B | IRAM (code) |
| 3 | 0x400D0020 | 79,920 B | IROM (flash code) |
| 4 | 0x400848A0 | 33,304 B | IRAM (code) |
Step 3: Locating the Encrypted Data
Found the format strings in DROM (Segment 0) at file offset 0x30E0. Searched for 32-bit cross-references to these string addresses in the code literal pool:
| Literal Pool Offset | Points To | Content |
|---|---|---|
| 0x106DC | 0x3F40307C | Welcome banner |
| 0x106E8 | 0x3F4030D0 | "System ready." |
| 0x106EC | 0x3F4030E0 | "Encrypted flag: " |
| 0x106F0 | 0x3F407570 | Encrypted flag bytes |
| 0x106F4 | 0x3F4030F4 | "%02X " (format string) |
| 0x106F8 | 0x3F4030FC | "XOR Key: " |
| 0x106FC | 0x3F40756C | XOR key string |
Step 4: Extracting Key and Ciphertext
XOR Key at file offset 0x756C:
4C 41 49 4E -> "LAIN"
Encrypted flag at file offset 0x7570 (23 bytes):
09 12 19 07 00 0E 07 35 3F 35 7D 3C 38 1E 3D 26 7F 1E 3E 7F 3E 72 34
Step 5: Decryption
Applied repeating-key XOR with key "LAIN":
enc = bytes([0x09,0x12,0x19,0x07,0x00,0x0e,0x07,0x35,
0x3f,0x35,0x7d,0x3c,0x38,0x1e,0x3d,0x26,
0x7f,0x1e,0x3e,0x7f,0x3e,0x72,0x34])
key = b"LAIN"
flag = bytes([enc[i] ^ key[i % 4] for i in range(len(enc))])
# Result: ESPILON{st4rt_th3_w1r3}
| Index | Enc Byte | Key Byte | XOR Result |
|---|---|---|---|
| 0 | 0x09 | L (0x4C) | E |
| 1 | 0x12 | A (0x41) | S |
| 2 | 0x19 | I (0x49) | P |
| 3 | 0x07 | N (0x4E) | I |
| ... | ... | ... | ... |
Tools Used
- Python 3 (struct, byte manipulation)
strings,xxd(initial recon)- Manual ESP32 image header parsing
Key Takeaways
- ESP32 firmware images use magic byte
0xE9and contain multiple segments (DROM, DRAM, IRAM, IROM) - Xtensa architecture uses L32R literal pools for loading constants, making cross-reference tracing straightforward
- No physical hardware or flashing was needed; static analysis of the binary was sufficient to recover the flag