write-up: IoT/Lets_All_Hate_UART/README.md
This commit is contained in:
parent
ac10aa1f7d
commit
95c0b63bfb
@ -1,46 +1,96 @@
|
|||||||
# Let's All Hate UART — Solution
|
# Let's All Hate UART
|
||||||
|
|
||||||
**Chapitre 1 : Peripheral Access** | Difficulté : Medium-Hard | Flag : `ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry}`
|
| Field | Value |
|
||||||
|
|-------|-------|
|
||||||
|
| Category | IoT |
|
||||||
|
| Difficulty | Medium-Hard |
|
||||||
|
| Points | 500 |
|
||||||
|
| Author | Eun0us |
|
||||||
|
| CTF | Espilon 2026 |
|
||||||
|
|
||||||
## Overview
|
---
|
||||||
|
|
||||||
Ce challenge simule l'interface UART d'un module de thérapie WIRED-MED (ESP32). Le joueur doit progresser à travers 4 couches de discovery pour extraire le flag depuis la NVS du device.
|
## Description
|
||||||
|
|
||||||
## Étape 1 — Connexion UART
|
**Chapter 1 — Peripheral Access**
|
||||||
|
|
||||||
|
In the basement of Sainte-Mika Clinic, you have gained physical access to a **WIRED-MED**
|
||||||
|
therapy module. You have identified the UART pads on the PCB. Connect and explore this
|
||||||
|
embedded device — it hides far more than it shows.
|
||||||
|
|
||||||
|
*"Let's all love Lain... but let's all hate UART debug interfaces left open in production."*
|
||||||
|
|
||||||
|
- **TX (read):** `nc CHALLENGE_HOST 1111`
|
||||||
|
- **RX (write):** `nc CHALLENGE_HOST 2222`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## TL;DR
|
||||||
|
|
||||||
|
Connect to the split UART, spot hidden commands by sending `?` or `help -a`. Read memory
|
||||||
|
at address `0x3FFB0800` to find a base64-encoded debug token (`th3rapy_m0dule=`). Authenticate
|
||||||
|
with it to unlock extended commands. List and read the NVS `crypto_flag` blob. XOR-decrypt
|
||||||
|
with key `WIRED` to get the flag.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Tools
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `nc` | Split UART connection |
|
||||||
|
| Python 3 | Base64 decode and XOR decryption |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Solution
|
||||||
|
|
||||||
|
### Step 1 — Connect
|
||||||
|
|
||||||
Ouvrir deux terminaux :
|
|
||||||
```bash
|
```bash
|
||||||
# Terminal 1 : TX (lecture seule)
|
# Terminal 1 — TX (read output)
|
||||||
nc <host> 1111
|
nc <host> 1111
|
||||||
|
|
||||||
# Terminal 2 : RX (écriture seule)
|
# Terminal 2 — RX (send commands)
|
||||||
nc <host> 2222
|
nc <host> 2222
|
||||||
```
|
```
|
||||||
|
|
||||||
Le boot sequence ESP32 s'affiche sur TX. Lire attentivement — il contient des infos cruciales.
|
Read the ESP32 boot sequence on TX carefully — it contains hints.
|
||||||
|
|
||||||
## Étape 2 — Discovery des commandes cachées
|
> 📸 `[screenshot: TX terminal showing ESP32 boot sequence with diagnostic messages]`
|
||||||
|
|
||||||
La commande `help` ne montre que les commandes basiques. Deux indices :
|
### Step 2 — Discover hidden commands
|
||||||
- `info` mentionne "Debug interface: ENABLED (restricted)"
|
|
||||||
- Envoyer `?` ou `help -a` révèle les commandes cachées : `debug`, `mem`, `nvs`, `flash`
|
|
||||||
|
|
||||||
## Étape 3 — Extraction du token debug depuis la RAM
|
|
||||||
|
|
||||||
La commande `mem read` fonctionne SANS authentification pour la plage DRAM publique `0x3FFB0000-0x3FFB1000`.
|
|
||||||
|
|
||||||
|
```text
|
||||||
|
help
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Only basic commands shown. But sending `?` or `help -a` reveals hidden ones:
|
||||||
|
|
||||||
|
```text
|
||||||
|
?
|
||||||
|
>> Extended commands: debug, mem, nvs, flash
|
||||||
|
```
|
||||||
|
|
||||||
|
The `info` command also hints: `Debug interface: ENABLED (restricted)`
|
||||||
|
|
||||||
|
### Step 3 — Extract the debug token from RAM
|
||||||
|
|
||||||
|
The public DRAM range `0x3FFB0000–0x3FFB1000` is readable without authentication:
|
||||||
|
|
||||||
|
```text
|
||||||
mem read 0x3FFB0800 48
|
mem read 0x3FFB0800 48
|
||||||
```
|
```
|
||||||
|
|
||||||
Résultat :
|
Output:
|
||||||
|
|
||||||
```
|
```
|
||||||
3FFB0800: 57 49 52 45 44 2D 4D 45 44 00 00 00 00 00 00 00 |WIRED-MED.......|
|
3FFB0800: 57 49 52 45 44 2D 4D 45 44 00 00 00 00 00 00 00 |WIRED-MED.......|
|
||||||
3FFB0810: 64 47 68 33 63 6D 46 77 65 56 39 74 4D 47 52 31 |dGgzcmFweV9tMGR1|
|
3FFB0810: 64 47 68 33 63 6D 46 77 65 56 39 74 4D 47 52 31 |dGgzcmFweV9tMGR1|
|
||||||
3FFB0820: 62 47 55 39 00 00 00 00 00 00 00 00 00 00 00 00 |bGU9............|
|
3FFB0820: 62 47 55 39 00 00 00 00 00 00 00 00 00 00 00 00 |bGU9............|
|
||||||
```
|
```
|
||||||
|
|
||||||
La partie ASCII aux offsets 0x810-0x830 contient du base64 : `dGgzcmFweV9tMGR1bGU9`
|
The ASCII at offsets 0x810–0x830 is base64: `dGgzcmFweV9tMGR1bGU9`
|
||||||
|
|
||||||
```python
|
```python
|
||||||
import base64
|
import base64
|
||||||
@ -48,40 +98,53 @@ base64.b64decode("dGgzcmFweV9tMGR1bGU9")
|
|||||||
# b'th3rapy_m0dule='
|
# b'th3rapy_m0dule='
|
||||||
```
|
```
|
||||||
|
|
||||||
## Étape 4 — Authentification debug
|
> 📸 `[screenshot: mem read output showing base64 token in ASCII column]`
|
||||||
|
|
||||||
```
|
### Step 4 — Authenticate as debug user
|
||||||
|
|
||||||
|
```text
|
||||||
debug auth th3rapy_m0dule=
|
debug auth th3rapy_m0dule=
|
||||||
```
|
```
|
||||||
|
|
||||||
→ "DEBUG MODE ENABLED. Extended commands unlocked."
|
Response: `DEBUG MODE ENABLED. Extended commands unlocked.`
|
||||||
|
|
||||||
## Étape 5 — Exploration NVS
|
### Step 5 — Read the NVS flag
|
||||||
|
|
||||||
```
|
List NVS entries:
|
||||||
|
|
||||||
|
```text
|
||||||
nvs list
|
nvs list
|
||||||
```
|
```
|
||||||
|
|
||||||
Affiche les entrées NVS dont `crypto_flag` (blob, 34 bytes).
|
Entry `crypto_flag` appears (blob, 34 bytes).
|
||||||
|
|
||||||
```
|
Read it:
|
||||||
|
|
||||||
|
```text
|
||||||
nvs read crypto_flag
|
nvs read crypto_flag
|
||||||
```
|
```
|
||||||
|
|
||||||
Retourne un hexdump du flag chiffré.
|
Returns a hexdump of the XOR-encrypted flag blob.
|
||||||
|
|
||||||
## Étape 6 — Déchiffrement XOR
|
> 📸 `[screenshot: nvs read showing the encrypted flag hexdump]`
|
||||||
|
|
||||||
Le blob est XOR'd avec la clé cyclique `WIRED` (5 bytes). Indice : le flag commence par `ESPILON{` — en XOR avec les premiers bytes du blob, on peut retrouver la clé.
|
### Step 6 — Decrypt with XOR key WIRED
|
||||||
|
|
||||||
|
The key is `WIRED` (5 bytes). Hint: the flag starts with `ESPILON{` — XOR the first 5
|
||||||
|
bytes of the ciphertext with `ESPIO` to confirm the key is `WIRED`.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
encrypted = bytes.fromhex("...") # hex du nvs read
|
encrypted = bytes.fromhex("...") # hex from nvs read
|
||||||
key = b"WIRED"
|
key = b"WIRED"
|
||||||
flag = bytes(b ^ key[i % len(key)] for i, b in enumerate(encrypted))
|
flag = bytes(b ^ key[i % len(key)] for i, b in enumerate(encrypted))
|
||||||
print(flag.decode())
|
print(flag.decode())
|
||||||
# ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry}
|
# ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry}
|
||||||
```
|
```
|
||||||
|
|
||||||
## Script de solve complet
|
> 📸 `[screenshot: Python decryption script printing the flag]`
|
||||||
|
|
||||||
Voir `solve/solve.py`
|
---
|
||||||
|
|
||||||
|
## Flag
|
||||||
|
|
||||||
|
`ESPILON{u4rt_nvs_fl4sh_d1sc0v3ry}`
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user