ESPILON-CTF-2026-Writeups/IoT/Lain_VS_Knights/README.md
Eun0us 1c42421380 Add 107 terminal screenshots and replace all 📸 placeholders
- Generated screenshots for all 33 challenges (ESP, Hardware, IoT, OT, Misc, Web3)
- Replaced all 123 placeholder lines with actual PNG image references
- Cleaned duplicate images from previously partial updates
- All write-ups now have full illustrated solutions
2026-03-27 00:34:47 +00:00

5.7 KiB
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

# Terminal 1 — TX
nc <host> 1111

# Terminal 2 — RX
nc <host> 2222
bus.map

Returns: 1200 nodes total — 7 knights active.

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

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

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:

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.

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.

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
node.can_send "0026"
# fragment can_checksum=0026

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

# 0x15 = 00010101 → transitions: 5
node.spi_write 15
# fragment spi_parity=15

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

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

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

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.

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

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

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

each Knight node returning its fragment after successful puzzle

Step 4 — Verify fragment collection

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

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

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


Flag

ESPILON{0nlY_L41N_C4N_S0lv3}