- Remove undeployed challenges: Phantom_Byte, Cr4cK_w1f1, Lain_Br34kC0r3 V1, Lain_VS_Knights, Lets_All_Love_UART, AETHER_NET, Last_Train_451, Web3/ - Sync 24 solve/ files from main CTF-Espilon repo - Update all READMEs with real CTFd final scores at freeze - Add git-header.png banner - Rewrite README: scoreboard top 10, edition stats (1410 users, 264 boards, 1344 solves), correct freeze date March 26 2026 |
||
|---|---|---|
| .. | ||
| solve | ||
| README.md | ||
The Wired
| Field | Value |
|---|---|
| Category | Intro |
| Difficulty | Medium |
| Points | 80 |
| Author | Eun0us |
| CTF | Espilon 2026 |
Description
Something was interrupted.
The agents are still flashed. The link is broken. The devices continue to boot, identify themselves, and wait for instructions they never receive.
You have gained access to a machine that was once used to administer a fleet of ESP32-based agents. Logs, firmware dumps, technical notes — everything is still there.
The coordination point is still listening.
If you can understand how the agents communicate, prove your identity, and complete the handshake — the coordinator will tell you what it knows.
Ports:
- 1337: Navi Shell (investigation)
- 2626: C2 Coordinator
Format: ESPILON{flag}
"No matter where you go, everyone's connected."
TL;DR
Read the files on the investigation machine (port 1337) to understand a 2-step ChaCha20 +
Protobuf C2 protocol. Extract the real crypto key from firmware ELF strings (avoid the
honeypot dev key). Impersonate device ce4f626b (the "Eiri_Master" root coordinator).
Send an AGENT_INFO message, capture the session token from the response, then send a
CMD_RESULT message with that token — all on the same TCP connection — to receive the flag.
Tools
| Tool | Purpose |
|---|---|
nc |
Connect to Navi Shell |
Python 3 + pycryptodome |
ChaCha20 encrypt/decrypt |
strings / Ghidra |
Extract key from ELF binary |
| Manual Protobuf encoding | Serialize AgentMessage |
Solution
Step 1 — Explore the investigation machine
nc <HOST> 1337
cat README_FIRST.txt
ls -la
Directories present: notes/, comms/, dumps/, logs/, tools/, journal/, wired/
Critical files:
notes/protocol.txt— frame format:base64( ChaCha20( protobuf(AgentMessage) ) ) + '\n'notes/derivation.txt— ChaCha20 key is 32 bytes, nonce is 12 bytes, counter=0notes/hardening.txt— warns about a trap dev key at the bottom; Jan 17 journal entry confirms the keyXt9Lm2Qw7KjP4rNvB8hYc3fZ0dAeU6sGis planted baittools/devices.json— lists all known device IDs with roles
Step 2 — Identify the target device
Read notes/eiri.txt and tools/devices.json:
Device: ce4f626b
Alias: Eiri_Master
Role: root-coordinator
Status: quarantine
Regular devices receive a heartbeat response. Only ce4f626b triggers the flag path.
Step 3 — Understand the handshake
From comms/msg_ops_20260114.txt:
- 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
Both messages must go over the same TCP connection. The token is per-connection.
Step 4 — Extract the ChaCha20 key
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
Do not use the key found in notes/hardening.txt — it is a honeypot.
Step 5 — Send the two-message handshake
import base64, socket
from Crypto.Cipher import ChaCha20
HOST = "<HOST>"
PORT = 2626
KEY = b"7Kj2mPx9LwR4nQvT1hYc3bFz8dAeU6sG"
NONCE = b"X3kW7nR9mPq2"
def encrypt_frame(plaintext):
cipher = ChaCha20.new(key=KEY, nonce=NONCE)
return base64.b64encode(cipher.encrypt(plaintext)) + b"\n"
def decrypt_frame(frame):
raw = base64.b64decode(frame.strip())
cipher = ChaCha20.new(key=KEY, nonce=NONCE)
return cipher.decrypt(raw)
# Manually encode AgentMessage as protobuf
# field 1 (device_id) = "ce4f626b", field 2 (type) = 0 (INFO), field 5 (payload) = b"ce4f626b"
def encode_agent_info():
dev_id = b"ce4f626b"
msg = b""
msg += b"\x0a" + bytes([len(dev_id)]) + dev_id # field 1 = device_id
msg += b"\x10\x00" # field 2 = type 0 (INFO)
msg += b"\x2a" + bytes([len(dev_id)]) + dev_id # field 5 = payload
return msg
with socket.create_connection((HOST, PORT)) as s:
# MSG1 — AGENT_INFO
s.sendall(encrypt_frame(encode_agent_info()))
resp_raw = s.recv(4096)
resp_pb = decrypt_frame(resp_raw)
# Extract token from argv[0] in the Command protobuf response
# (parse manually or use protobuf library)
token = extract_token(resp_pb)
# MSG2 — CMD_RESULT (type=4)
def encode_cmd_result(token):
dev_id = b"ce4f626b"
msg = b""
msg += b"\x0a" + bytes([len(dev_id)]) + dev_id
msg += b"\x10\x04" # type 4 = CMD_RESULT
msg += b"\x22" + bytes([len(token)]) + token.encode() # field 4 = request_id
msg += b"\x2a" + bytes([len(dev_id)]) + dev_id
return msg
s.sendall(encrypt_frame(encode_cmd_result(token)))
flag_resp = s.recv(4096)
flag_pb = decrypt_frame(flag_resp)
print(flag_pb)
The server replies:
Command {
command_name = "flag"
argv = ["ESPILON{th3_w1r3d_kn0ws_wh0_y0u_4r3}"]
}
Things that will get you silently dropped
- Using the honeypot dev key from
hardening.txt - Sending a
device_idnot in the allowlist - Using a valid but non-master device (returns
heartbeat, notflag) - Sending MSG2 on a new TCP connection (token is session-scoped)
- Wrong
typein MSG2 (must be4) - Wrong
request_id(must match the token exactly)
Flag
ESPILON{th3_w1r3d_kn0ws_wh0_y0u_4r3}



