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

252 lines
5.9 KiB
Markdown
Executable File

# LAIN vs Knights — Solution
**Difficulty:** Hard | **Category:** IoT | **Flag:** `ESPILON{0nlY_L41N_C4N_S0lv3}`
## Overview
A UART interface exposes a simulated "Wired" network with 1200 nodes, 7 protected
by Knights and 1 by the Founder (Eiri Masami). Defeat all 7 Knights to collect
fragments, combine them into an exploit hash, and present it to the Founder.
- **TX (port 1111)**: Read only
- **RX (port 2222)**: Write only
## Step 1 — Enumerate nodes
```text
bus.map
```
Returns: `1200 nodes total — 7 knights active.`
Scan all nodes to find the 7 Knights and the Founder:
```python
import socket, time, re
HOST = "<host>"
TX_PORT, RX_PORT = 1111, 2222
def recv_until(sock, expect=">", timeout=2.0):
data = b""
sock.settimeout(timeout)
while True:
try:
c = sock.recv(4096)
if not c:
break
data += c
if expect.encode() in data:
break
except socket.timeout:
break
return data.decode(errors="ignore")
def scan_nodes(node_min=1, node_max=1200):
tx = socket.socket(); tx.connect((HOST, TX_PORT))
rx = socket.socket(); rx.connect((HOST, RX_PORT))
recv_until(tx) # flush banner
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 = recv_until(tx)
if re.search(r"KNIGHT|FOUNDER|EIRI", out, re.I):
kind = "knight" if "KNIGHT" in out.upper() else "founder"
print(f"[+] Node {nid:04d}: {kind}")
found.append((nid, kind, out))
rx.sendall(b"bus.disconnect\n")
recv_until(tx)
tx.close(); rx.close()
return found
```
Knights found at (example run): `0067, 0113, 0391, 0529, 0619, 0901, 0906`
Founder found at: `0311`
## Step 2 — Get hints from Lain nodes
Connect to any Lain node and use `node.truth` repeatedly until you get:
```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 of the hash.
```
## Step 3 — Defeat each Knight
### Knight 1 — I2C_MIRROR
Find two distinct messages with the same byte sum mod N.
```python
def find_i2c_pair(modulo):
for a in range(256):
for b in range(256):
for c in range(256):
if a == c: continue
if (a + b) % modulo == (c + b) % modulo:
return bytes([a, b]).hex(), bytes([c, b]).hex()
```
```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) equals the target byte.
```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
for i in range(256):
for j in range(256):
if can_crc8(bytes([i, j])) == target:
print(f"Found: {bytes([i,j]).hex()}") # → 0026
break
```
```text
node.can_send "0026"
# → fragment can_checksum=0026
```
### Knight 3 — SPI_PARITY
Find a byte with exactly 5 bit transitions (0↔1 between adjacent bits).
```python
def count_transitions(b):
bits = f"{b:08b}"
return sum(1 for i in range(7) if bits[i] != bits[i+1])
# 0x15 = 00010101 → transitions at positions 2,3,4,5,6 = 5 ✓
answer = next(hex(b)[2:] for b in range(256) if count_transitions(b) == 5)
# → '15'
```
```text
node.spi_write 15
# → fragment spi_parity=15
```
### Knight 4 — SRAM_WRITE
Write a specific value to a specific address.
```text
node.write 0x8b 0x89
# → fragment sram_write=8b_89
```
### Knight 5 — LOGIC_AND
Find a pair `(a, b)` such that `a & b` equals the secret.
Probe with `node.and_probe 0xff 0xff` → reveals the secret (e.g. `0xa8`).
Then `a = secret`, `b = 0xff`:
```text
node.and_probe 0xff 0xff
# → "ff & ff = ff" (not our target — reveals target is 0xa8)
node.submit_and 0xa8 0xff
# → fragment logic_and=a8_ff
```
### Knight 6 — FUSE_BITS
Probe with full mask to reveal the secret fuse bits directly:
```text
node.fuse_probe 0xff
# → "Probe: (fuse & ff) = 30"
node.submit_fuse 0x30
# → fragment fuse_bits=30
```
### Knight 7 — FAULT_INJECTION
Try all offset/mask combinations until the knight is purged:
```python
for offset in range(8):
for mask in [1, 2, 4, 8, 16, 32, 64, 128]:
print(f"node.inject {offset} 0x{mask:02x}")
# Correct answer: offset=2, mask=0x08
```
```text
node.inject 2 0x08
# → fragment fault_injection=2_08
```
## Step 4 — Check 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
Concatenate fragments in the order Lain specified, hash with SHA-256, take first 24 hex chars:
```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)
# → "0000_76000026158b_89a8_ff302_08"
exploit = hashlib.sha256(payload.encode()).hexdigest()[:24]
# → "69b4a17e33b0cdace34b7610"
```
## Step 6 — Submit to the Founder and get the flag
```text
bus.connect @node:0311
node.exploit 69b4a17e33b0cdace34b7610
# → "[ROOT] Exploit accepted! You are now root."
node.flag
# → ESPILON{0nlY_L41N_C4N_S0lv3}
```
## Flag
`ESPILON{0nlY_L41N_C4N_S0lv3}`
## Author
Eun0us