155 lines
3.1 KiB
Markdown
155 lines
3.1 KiB
Markdown
# Observe The Wired
|
|
|
|
| Field | Value |
|
|
|-------|-------|
|
|
| Category | IoT |
|
|
| Difficulty | Medium-Hard |
|
|
| Points | — |
|
|
| Author | Eun0us |
|
|
| CTF | Espilon 2026 |
|
|
|
|
---
|
|
|
|
## Description
|
|
|
|
An IoT module from Clinique Sainte-Mika (Room 013) communicates via **CoAP** on the
|
|
WIRED-MED network. The stream seems stable... but the observation layer hides a key fragment.
|
|
|
|
*In The Wired, only observers hear the truth.*
|
|
|
|
Explore the CoAP resources, observe the stream, reconstruct the key, decode the firmware
|
|
blob, and open the maintenance channel.
|
|
|
|
- CoAP: `udp/<host>:5683`
|
|
|
|
Format: **ESPILON{flag}**
|
|
|
|
---
|
|
|
|
## TL;DR
|
|
|
|
Discover CoAP resources with a `.well-known/core` GET. Collect fragments A, B, C from
|
|
`/status`, `/telemetry/heart`, and the observable `/wired/stream`. Concatenate to build
|
|
XOR key `WIREDLAIN23`. Download and decode the firmware blob from `/archive/firmware`.
|
|
POST the maintenance key `0BS3RV3-L41N-23` to unlock and get the flag.
|
|
|
|
---
|
|
|
|
## Tools
|
|
|
|
| Tool | Purpose |
|
|
|------|---------|
|
|
| `coap-client` (libcoap) | CoAP GET, observe, POST |
|
|
| Python 3 | XOR decode, base64 |
|
|
|
|
---
|
|
|
|
## Solution
|
|
|
|
### Step 1 — Discover resources
|
|
|
|
```bash
|
|
coap-client -m get coap://<HOST>/.well-known/core
|
|
```
|
|
|
|
Returns a comma-separated list of resource links (RFC 6690 format):
|
|
|
|
```
|
|
</status>,</telemetry/heart>,</telemetry/temp>,
|
|
</wired/stream>;obs,</archive/firmware>,
|
|
</maintenance/unlock>
|
|
```
|
|
|
|
> 📸 `[screenshot: .well-known/core response listing all CoAP resources]`
|
|
|
|
### Step 2 — Get fragment A
|
|
|
|
```bash
|
|
coap-client -m get coap://<HOST>/status
|
|
```
|
|
|
|
Response includes: `fragment_a: WIRED`
|
|
|
|
### Step 3 — Get fragment B
|
|
|
|
```bash
|
|
coap-client -m get coap://<HOST>/telemetry/heart
|
|
```
|
|
|
|
Response includes: `fragment_b: LAIN`
|
|
|
|
### Step 4 — Observe the stream for fragment C
|
|
|
|
Subscribe to the observable resource for 30 seconds:
|
|
|
|
```bash
|
|
coap-client -m get -s 30 -o observe_out.txt coap://<HOST>/wired/stream
|
|
```
|
|
|
|
Among the periodic notifications, one JSON payload contains:
|
|
|
|
```json
|
|
{"fragment_c": "23", "node": "013"}
|
|
```
|
|
|
|
> 📸 `[screenshot: observable stream notification containing fragment_c value]`
|
|
|
|
### Step 5 — Build the XOR key
|
|
|
|
Concatenate fragments in order A + B + C:
|
|
|
|
```
|
|
WIRED + LAIN + 23 = WIREDLAIN23
|
|
```
|
|
|
|
### Step 6 — Download and decode the firmware blob
|
|
|
|
```bash
|
|
coap-client -m get coap://<HOST>/archive/firmware
|
|
```
|
|
|
|
The response is a text payload with base64 between markers:
|
|
|
|
```
|
|
FIRMWARE_B64_BEGIN
|
|
<base64 data>
|
|
FIRMWARE_B64_END
|
|
```
|
|
|
|
Save and decode:
|
|
|
|
```python
|
|
import base64, json
|
|
|
|
with open("firmware.b64") as f:
|
|
b64_data = f.read().strip()
|
|
|
|
raw = base64.b64decode(b64_data)
|
|
|
|
# XOR with key WIREDLAIN23
|
|
key = b"WIREDLAIN23"
|
|
decoded = bytes(b ^ key[i % len(key)] for i, b in enumerate(raw))
|
|
|
|
config = json.loads(decoded.decode())
|
|
print(config["maintenance_key"])
|
|
# 0BS3RV3-L41N-23
|
|
```
|
|
|
|
> 📸 `[screenshot: Python script printing the maintenance key from the decoded firmware blob]`
|
|
|
|
### Step 7 — Unlock and get the flag
|
|
|
|
```bash
|
|
coap-client -m post -e "0BS3RV3-L41N-23" coap://<HOST>/maintenance/unlock
|
|
```
|
|
|
|
The response contains the flag.
|
|
|
|
> 📸 `[screenshot: CoAP POST response returning the flag]`
|
|
|
|
---
|
|
|
|
## Flag
|
|
|
|
`ESPILON{c0ap_0bs3rv3_th3_w1r3d}`
|