150 lines
3.8 KiB
Markdown
150 lines
3.8 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
# 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 :
|
|
|
|
```python
|
|
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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
# 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
|
|
|
|
```bash
|
|
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
|
|
```bash
|
|
# 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
|
|
|
|
```python
|
|
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`
|