# Lain VS Knights

| Field | Value |
|-------|-------|
| Category | IoT |
| Difficulty | Hard |
| Points | — |
| Author | Eun0us |
| CTF | Espilon 2026 |

---

## Description

Welcome to The Wired: a digital labyrinth of over a thousand mysterious nodes, where only
seven are protected by the legendary Knights.

You are **Lain**. Your mission: navigate this cryptic UART interface, explore The Wired,
and uncover the locations of the hidden Knights. Purge each Knight.

Each defeated Knight grants you a fragment. Collect all seven to unlock the final gateway:
**EIRI MASAMI** — the master of The Wired.

Seize root access. Become the new legend of The Wired.

- **TX (port 1111)**: Read only
- **RX (port 2222)**: Write only

Format: **ESPILON{flag}**

---

## TL;DR

1200 nodes, 7 Knights each at a random node, 1 Founder (Eiri). Scan all nodes to find them.
Each Knight presents a different hardware/protocol puzzle (I2C collision, CAN CRC, SPI parity,
SRAM write, logic AND, fuse bits, fault injection). Collect the 7 fragments, concatenate them
in the order Lain specifies, SHA-256 hash, take first 24 hex chars, submit to the Founder for
root access and the flag.

---

## Tools

| Tool | Purpose |
|------|---------|
| `nc` | Split UART connection |
| Python 3 | Node scanner, puzzle solvers, exploit script |
| `hashlib` | SHA-256 for exploit hash computation |

---

## Solution

### Step 1 — Connect and map the network

```bash
# Terminal 1 — TX
nc <host> 1111

# Terminal 2 — RX
nc <host> 2222
```

```text
bus.map
```

Returns: `1200 nodes total — 7 knights active.`

Scan all nodes to locate the Knights and Founder (automated script recommended):

```python
import socket, time, re

def scan_nodes(host, tx_port=1111, rx_port=2222, node_min=1, node_max=1200):
    tx = socket.socket(); tx.connect((host, tx_port))
    rx = socket.socket(); rx.connect((host, rx_port))
    found = []
    for nid in range(node_min, node_max + 1):
        rx.sendall(f"bus.connect @node:{nid:04d}\n".encode())
        time.sleep(0.08)
        out = tx.recv(4096).decode(errors="ignore")
        if re.search(r"KNIGHT|FOUNDER|EIRI", out, re.I):
            kind = "knight" if "KNIGHT" in out.upper() else "founder"
            found.append((nid, kind))
        rx.sendall(b"bus.disconnect\n")
    return found
```

Example result: Knights at nodes 0067, 0113, 0391, 0529, 0619, 0901, 0906. Founder at 0311.

![scanner output listing discovered Knight and Founder node IDs](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/knights_scan.png)

### Step 2 — Get the assembly order from Lain nodes

Connect to any ordinary Lain node and call `node.truth` repeatedly until you get the order:

```text
Order matters. Use: i2c_mirror, can_checksum, spi_parity, sram_write,
logic_and, fuse_bits, fault_injection.
Assemble fragments in this order as a single string, with no separators.
Hash this string using SHA256. Take the first 24 hex digits.
```

### Step 3 — Defeat each Knight

**Knight 1 — I2C_MIRROR**: Find two messages with the same byte-sum mod N.

```text
node.i2c_write "0000"
node.i2c_write "7600"
node.submit_pair "0000" "7600"
# fragment i2c_mirror=0000_7600
```

**Knight 2 — CAN_CHECKSUM**: Find a CAN frame whose CRC8(poly=0x2F) = target.

```python
def can_crc8(data, poly=0x2F):
    c = 0
    for b in data:
        c ^= b
        for _ in range(8):
            c = ((c << 1) ^ poly) & 0xFF if c & 0x80 else (c << 1) & 0xFF
    return c
# Target 0x91 → frame bytes 0x00 0x26
```

```text
node.can_send "0026"
# fragment can_checksum=0026
```

**Knight 3 — SPI_PARITY**: Find a byte with exactly 5 bit-transitions.

```python
# 0x15 = 00010101 → transitions: 5
```

```text
node.spi_write 15
# fragment spi_parity=15
```

**Knight 4 — SRAM_WRITE**: Write a value to the required address.

```text
node.write 0x8b 0x89
# fragment sram_write=8b_89
```

**Knight 5 — LOGIC_AND**: Find `(a, b)` where `a & b == secret`.

```text
node.and_probe 0xff 0xff   # reveals secret 0xa8
node.submit_and 0xa8 0xff
# fragment logic_and=a8_ff
```

**Knight 6 — FUSE_BITS**: Probe with full mask to reveal the fuse value.

```text
node.fuse_probe 0xff       # reveals 0x30
node.submit_fuse 0x30
# fragment fuse_bits=30
```

**Knight 7 — FAULT_INJECTION**: Try all offset/mask combinations.

```text
node.inject 2 0x08
# fragment fault_injection=2_08
```

![each Knight node returning its fragment after successful puzzle](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/knights_fragments.png)

### Step 4 — Verify fragment collection

```text
fragments
# i2c_mirror    = 0000_7600
# can_checksum  = 0026
# spi_parity    = 15
# sram_write    = 8b_89
# logic_and     = a8_ff
# fuse_bits     = 30
# fault_injection = 2_08
```

### Step 5 — Build the exploit hash

```python
import hashlib

fragments = {
    "i2c_mirror":      "0000_7600",
    "can_checksum":    "0026",
    "spi_parity":      "15",
    "sram_write":      "8b_89",
    "logic_and":       "a8_ff",
    "fuse_bits":       "30",
    "fault_injection": "2_08",
}
order = ["i2c_mirror", "can_checksum", "spi_parity",
         "sram_write", "logic_and", "fuse_bits", "fault_injection"]

payload = "".join(fragments[k] for k in order)
exploit  = hashlib.sha256(payload.encode()).hexdigest()[:24]
print(exploit)  # e.g. 69b4a17e33b0cdace34b7610
```

### Step 6 — Submit to the Founder and get root

```text
bus.connect @node:0311
node.exploit 69b4a17e33b0cdace34b7610
# [ROOT] Exploit accepted! You are now root.
node.flag
# ESPILON{0nlY_L41N_C4N_S0lv3}
```

![Founder node accepting the exploit and printing the flag](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/knights_exploit.png)

---

## Flag

`ESPILON{0nlY_L41N_C4N_S0lv3}`
