ESPILON-CTF-2026-Writeups/IoT/Lain_Br34kC0r3_V2/solve/solve.md
Eun0us 6a0877384d [+] Writeups v2 — sync solves, real points, scoreboard stats, cleanup
- Remove undeployed challenges: Phantom_Byte, Cr4cK_w1f1, Lain_Br34kC0r3 V1,
  Lain_VS_Knights, Lets_All_Love_UART, AETHER_NET, Last_Train_451, Web3/
- Sync 24 solve/ files from main CTF-Espilon repo
- Update all READMEs with real CTFd final scores at freeze
- Add git-header.png banner
- Rewrite README: scoreboard top 10, edition stats (1410 users, 264 boards,
  1344 solves), correct freeze date March 26 2026
2026-03-27 21:27:45 +01:00

3.8 KiB

LAIN_Br34kC0r3 V2 — Solution

Chapitre 2 : Core Analysis | Difficulté : Hard | Flag : ESPILON{3sp32_fl4sh_dump_r3v3rs3d}

Overview

Ce challenge fournit un dump flash complet d'un ESP32 (bootloader + partition table + NVS + firmware applicatif). Le joueur doit extraire le firmware, le reverse engineer pour trouver les clés AES-256-CBC, puis déchiffrer le flag stocké dans la NVS.

Étape 1 — Récupérer le dump flash

# Terminal 1 : TX (lecture)
nc <host> 1111 | tee flash_output.txt

# Terminal 2 : RX (écriture)
echo "dump_flash" | nc <host> 2222

Le dump est envoyé en base64. Extraire et décoder :

import base64

with open("flash_output.txt") as f:
    lines = f.readlines()

# Extract base64 lines between markers
b64_data = ""
capture = False
for line in lines:
    if "BEGIN FLASH DUMP" in line:
        capture = True
        continue
    if "END FLASH DUMP" in line:
        break
    if capture:
        b64_data += line.strip()

flash = base64.b64decode(b64_data)
with open("flash_dump.bin", "wb") as f:
    f.write(flash)

Étape 2 — Analyse du dump flash

# Identifier les composants
binwalk flash_dump.bin

# Ou utiliser esptool
esptool.py image_info --version 2 flash_dump.bin

Structure identifiée :

0x0000  Padding (0xFF)
0x1000  ESP32 bootloader (magic 0xE9)
0x8000  Partition table
0x9000  NVS partition (24 KiB)
0xF000  PHY init data
0x10000 Application firmware (magic 0xE9)

Étape 3 — Extraire le firmware applicatif

# Extraire l'app à partir de l'offset 0x10000
dd if=flash_dump.bin of=app_firmware.bin bs=1 skip=$((0x10000))

# Vérifier
file app_firmware.bin
# Devrait montrer un binaire ESP32 ou "data"

hexdump -C app_firmware.bin | head -5
# Premier byte devrait être 0xE9 (ESP image magic)

Étape 4 — Reverse Engineering

Méthode rapide : strings

strings -n 10 app_firmware.bin | grep -i "key\|aes\|iv\|wired\|therapy"

Résultats attendus :

W1R3D_M3D_TH3R4PY_K3Y_2024_L41N!    # AES-256 key (32 bytes)
L41N_WIRED_IV_01                      # AES IV (16 bytes)
WIRED-MED Therapy Module

Méthode complète : Ghidra

  1. Ouvrir Ghidra, importer app_firmware.bin (ou mieux, l'ELF si disponible)
  2. Architecture : Xtensa:LE:32:default
  3. Analyser → chercher app_main dans les symboles
  4. Suivre les appels : app_main()wired_med_crypto_init()mbedtls_aes_setkey_enc()
  5. Les arguments de mbedtls_aes_setkey_enc() pointent vers therapy_aes_key dans .rodata
  6. Extraire les 32 bytes de la clé et les 16 bytes de l'IV

Étape 5 — Récupérer le flag chiffré

Option A : Commande directe

encrypted_data

→ Retourne le ciphertext en hex

Option B : Parser la NVS

# Extraire la partition NVS
dd if=flash_dump.bin of=nvs_dump.bin bs=1 skip=$((0x9000)) count=$((0x6000))

# Utiliser nvs_tool.py de ESP-IDF (si disponible)
python3 $IDF_PATH/components/nvs_flash/nvs_partition_tool/nvs_tool.py --dump nvs_dump.bin

Chercher l'entrée : namespace wired_med, key encrypted_flag, type blob

Étape 6 — Déchiffrement AES-256-CBC

from Crypto.Cipher import AES
from Crypto.Util.Padding import unpad

key = b"W1R3D_M3D_TH3R4PY_K3Y_2024_L41N!"  # 32 bytes
iv  = b"L41N_WIRED_IV_01"                   # 16 bytes

# Ciphertext from encrypted_data command or NVS
ciphertext = bytes.fromhex("...")

cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
print(plaintext.decode())
# ESPILON{3sp32_fl4sh_dump_r3v3rs3d}

Résumé de la chaîne d'attaque

dump_flash → base64 decode → binwalk → extract app @ 0x10000
    → strings/Ghidra (Xtensa RE) → find AES key + IV in .rodata
    → encrypted_data (or NVS parse) → AES-256-CBC decrypt → FLAG

Script de solve complet

Voir solve/solve.py