| .. | ||
| README.md | ||
The Wired — Writeup
Difficulty: Medium | Flag: ESPILON{th3_w1r3d_kn0ws_wh0_y0u_4r3}
Recon
Connect to the Navi shell on port 1337 and start reading.
nc <HOST> 1337
cat README_FIRST.txt
ls -la
Directories: notes/, comms/, dumps/, logs/, tools/, journal/, wired/
The key files to understand the protocol:
notes/protocol.txt— frame format isbase64( ChaCha20( protobuf(AgentMessage) ) ) + '\n'notes/derivation.txt— ChaCha20 with 32-byte key, 12-byte nonce, counter=0notes/hardening.txt— keys are baked into the firmware ELF. WARNING: there's a dev key at the bottom (Xt9Lm2Qw7KjP4rNvB8hYc3fZ0dAeU6sG) — it's a trap planted by "the other Lain". The server drops you silently if you use it. The journal entry from Jan 17 warns about this.
Identify the target
notes/eiri.txt and tools/devices.json tell you the interesting device is ce4f626b — alias "Eiri_Master", role root-coordinator, status quarantine. Regular devices just get a heartbeat back. This one triggers the flag path.
Understand the handshake
comms/msg_ops_20260114.txt describes the 2-step session:
- Agent sends
AGENT_INFO→ coordinator repliessession_initwith a random token inargv[0] - Agent sends
AGENT_CMD_RESULTwithrequest_id= that token → coordinator replies with the flag
comms/msg_lain_20260116.txt confirms both messages have to go on the same TCP connection. Token is per-connection, can't reuse it.
Extract the key
Any device ELF works since they all share the same key (see notes/changelog.txt about v0.9.0 migration).
strings dumps/7f3c9a12/bot-lwip.elf | grep -E '^[A-Za-z0-9]{32}$'
strings dumps/7f3c9a12/bot-lwip.elf | grep -E '^[A-Za-z0-9]{12}$'
- Key (32 bytes):
7Kj2mPx9LwR4nQvT1hYc3bFz8dAeU6sG - Nonce (12 bytes):
X3kW7nR9mPq2
Alternative: Ghidra → find chacha_cd() in crypto.c → follow xrefs to CONFIG_CRYPTO_KEY / CONFIG_CRYPTO_NONCE.
Exploit
Single TCP connection to port 2626.
MSG1 — AGENT_INFO:
AgentMessage {
device_id = "ce4f626b"
type = 0 (INFO)
payload = b"ce4f626b"
}
Serialize as protobuf → ChaCha20 encrypt → base64 + \n → send.
Server replies with:
Command {
command_name = "session_init"
argv = ["<hex_token>"]
request_id = "handshake"
}
Decrypt + decode the response, grab the token from argv[0].
MSG2 — CMD_RESULT:
AgentMessage {
device_id = "ce4f626b"
type = 4 (CMD_RESULT)
request_id = "<token>"
payload = b"ce4f626b"
}
Same connection. Encrypt, encode, send.
Server replies:
Command {
command_name = "flag"
argv = ["ESPILON{th3_w1r3d_kn0ws_wh0_y0u_4r3}"]
}
Things that will get you dropped
- Using the fake dev key from
hardening.txt - Sending a device_id not in the allowlist
- Using a valid but non-master device (you get
heartbeat, notflag) - Sending MSG2 on a new connection (token is tied to the session)
- Wrong
typein MSG2 (needs to be4) - Wrong
request_id(needs to match the token exactly)
Solver
python3 solve.py --host <HOST> --port 2626
Protobuf reference
The .proto is at wired/registry/c2.proto on the target:
message AgentMessage {
string device_id = 1;
AgentMsgType type = 2;
string source = 3;
string request_id = 4;
bytes payload = 5;
bool eof = 6;
}
message Command {
string device_id = 1;
string command_name = 2;
repeated string argv = 3;
string request_id = 4;
}
No need for protoc — manual varint encoding works fine. See solve.py.
Author: Eun0us