write-up: IoT/Lain_Br34kC0r3_V2/README.md

This commit is contained in:
Eun0us 2026-03-26 17:33:32 +00:00
parent 81798e6dbd
commit fcea330c51

View File

@ -1,22 +1,76 @@
# LAIN_Br34kC0r3 V2 — Solution
# Lain_Br34kC0r3 V2
**Chapitre 2 : Core Analysis** | Difficulté : Hard | Flag : `ESPILON{3sp32_fl4sh_dump_r3v3rs3d}`
| Field | Value |
|-------|-------|
| Category | IoT |
| Difficulty | Hard |
| Points | 500 |
| Author | Eun0us |
| CTF | Espilon 2026 |
## 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.
## Description
## Étape 1 — Récupérer le dump flash
**Chapter 2 — Core Analysis**
After exploring the WIRED-MED therapy module through its UART interface, you successfully
dumped the complete flash of the device. This dump contains the bootloader, the partition
table, the NVS and the application firmware.
Your mission: **reverse engineer the ESP32 firmware** to extract the encryption keys used
to protect patient data, then decrypt the flag stored in the NVS partition.
*"Close your eyes. Open The Wired. Analyze the core."*
- **TX (read):** `nc CHALLENGE_HOST 1111`
- **RX (write):** `nc CHALLENGE_HOST 2222`
---
## TL;DR
Issue `dump_flash` over the RX UART port to receive a base64-encoded full flash dump on TX.
Decode and use `binwalk` to locate the app firmware at offset 0x10000. Run `strings` on the
binary to find the AES-256-CBC key (`W1R3D_M3D_TH3R4PY_K3Y_2024_L41N!`) and IV (`L41N_WIRED_IV_01`).
Get the encrypted flag via the `encrypted_data` command, then AES-256-CBC decrypt it.
---
## Tools
| Tool | Purpose |
|------|---------|
| `nc` | Split UART connection |
| Python 3 | Base64 decode, data parsing |
| `binwalk` / `esptool.py` | Parse the flash dump |
| `strings` | Extract AES key and IV |
| Ghidra (Xtensa) | Full reverse engineering if needed |
| `pycryptodome` | AES-256-CBC decryption |
---
## Solution
### Step 1 — Dump the flash
```bash
# Terminal 1 : TX (lecture)
# Terminal 1 — TX (read)
nc <host> 1111 | tee flash_output.txt
# Terminal 2 : RX (écriture)
# Terminal 2 — RX (write)
echo "dump_flash" | nc <host> 2222
```
Le dump est envoyé en base64. Extraire et décoder :
The dump is sent as base64 between markers:
```
=== BEGIN FLASH DUMP ===
<base64 data>
=== END FLASH DUMP ===
```
Decode:
```python
import base64
@ -24,7 +78,6 @@ 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:
@ -41,17 +94,16 @@ with open("flash_dump.bin", "wb") as f:
f.write(flash)
```
## Étape 2 — Analyse du dump flash
> 📸 `[screenshot: TX terminal showing the base64 flash dump streaming out]`
### Step 2 — Identify the flash structure
```bash
# Identifier les composants
binwalk flash_dump.bin
# Ou utiliser esptool
esptool.py image_info --version 2 flash_dump.bin
```
Structure identifiée :
Expected structure:
```
0x0000 Padding (0xFF)
0x1000 ESP32 bootloader (magic 0xE9)
@ -61,64 +113,42 @@ Structure identifiée :
0x10000 Application firmware (magic 0xE9)
```
## Étape 3 — Extraire le firmware applicatif
### Step 3 — Extract and analyze the application firmware
```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 :
Expected results:
```
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
> 📸 `[screenshot: strings output identifying the AES key and IV]`
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
For full confirmation: open in Ghidra with **Xtensa:LE:32:default** architecture,
find `app_main()``wired_med_crypto_init()``mbedtls_aes_setkey_enc()`. The
key and IV are passed as arguments pointing into `.rodata`.
## Étape 5 — Récupérer le flag chiffré
### Step 4 — Get the encrypted flag ciphertext
### Option A : Commande directe
```
On the RX port:
```text
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))
Returns the ciphertext as a hex string on TX.
# 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
```
Alternatively, extract from the NVS partition (namespace `wired_med`, key `encrypted_flag`, blob type) using `nvs_tool.py` from ESP-IDF.
Chercher l'entrée : namespace `wired_med`, key `encrypted_flag`, type blob
> 📸 `[screenshot: encrypted_data command returning the hex ciphertext]`
## Étape 6 — Déchiffrement AES-256-CBC
### Step 5 — Decrypt AES-256-CBC
```python
from Crypto.Cipher import AES
@ -127,8 +157,7 @@ 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("...")
ciphertext = bytes.fromhex("...") # from encrypted_data output
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size)
@ -136,7 +165,9 @@ print(plaintext.decode())
# ESPILON{3sp32_fl4sh_dump_r3v3rs3d}
```
## Résumé de la chaîne d'attaque
> 📸 `[screenshot: Python decryption script printing the flag]`
### Attack chain summary
```
dump_flash → base64 decode → binwalk → extract app @ 0x10000
@ -144,6 +175,8 @@ dump_flash → base64 decode → binwalk → extract app @ 0x10000
→ encrypted_data (or NVS parse) → AES-256-CBC decrypt → FLAG
```
## Script de solve complet
---
Voir `solve/solve.py`
## Flag
`ESPILON{3sp32_fl4sh_dump_r3v3rs3d}`