ESPILON-CTF-2026-Writeups/IoT/Lain_VS_Knights/README.md

230 lines
5.4 KiB
Markdown
Executable File

# 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.
> 📸 `[screenshot: scanner output listing discovered Knight and Founder node IDs]`
### 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
```
> 📸 `[screenshot: each Knight node returning its fragment after successful puzzle]`
### 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}
```
> 📸 `[screenshot: Founder node accepting the exploit and printing the flag]`
---
## Flag
`ESPILON{0nlY_L41N_C4N_S0lv3}`