# 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 1111 # Terminal 2 — RX nc 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}`