ESPILON-CTF-2026-Writeups/OT/Protocol_Seven
Eun0us 6a0877384d [+] Writeups v2 — sync solves, real points, scoreboard stats, cleanup
- 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
2026-03-27 21:27:45 +01:00
..
solve [+] Writeups v2 — sync solves, real points, scoreboard stats, cleanup 2026-03-27 21:27:45 +01:00
README.md [+] Writeups v2 — sync solves, real points, scoreboard stats, cleanup 2026-03-27 21:27:45 +01:00

Protocol Seven

Field Value
Category OT
Difficulty Hard
Points 513
Author Eun0us
CTF Espilon 2026

Description

Eiri Masami split Protocol Seven across three industrial control systems before his death. The encryption key hides in BACnet harmonics. The encrypted payload lives on OPC-UA. The initialization vector is stored in an EtherNet/IP controller.

No single system reveals the truth. Cross-reference all three protocols. Reconstruct Protocol Seven.

Become one with the Wired.

Ports:

  • 47809/udp: BACnet/IP
  • 4841/tcp: OPC-UA Binary
  • 44819/tcp: EtherNet/IP

Format: ESPILON{flag}


TL;DR

Read 8 BACnet AnalogValue harmonics (integer parts spell Eiri_Key). Read OPC-UA for the 32-byte encrypted payload and hints pointing to BACnet and EtherNet/IP. Read EtherNet/IP for the rotation nonce (3). XOR the payload with the 8-byte repeating key, then rotate right by the nonce to recover the flag.


Tools

Tool Purpose
Python 3 + BAC0 BACnet/IP read
Python 3 + asyncua OPC-UA anonymous read
Python 3 + cpppo EtherNet/IP tag read

Solution

Step 1 — Port discovery

nmap -sU -p 47809 <HOST>    # BACnet
nmap -p 4841,44819 <HOST>   # OPC-UA, EtherNet/IP

All three ports are open.

Layer 1 — BACnet key extraction

import BAC0

bacnet = BAC0.lite(ip="<YOUR_IP>/24")
bacnet.whois("*:47809")
# → Device:7777 "Protocol-Seven-Key"

Device description: "Key Harmonic Array — integer components matter"

Read all 8 harmonics:

harmonics = []
for i in range(8):
    val = bacnet.read(f"7777 analogValue {i} presentValue")
    harmonics.append(int(val))  # truncate float to integer
Object presentValue int Char
AnalogValue:0 69.14 69 E
AnalogValue:1 105.92 105 i
AnalogValue:2 114.37 114 r
AnalogValue:3 105.68 105 i
AnalogValue:4 95.44 95 _
AnalogValue:5 75.81 75 K
AnalogValue:6 101.22 101 e
AnalogValue:7 121.55 121 y

XOR key = Eiri_Key

BACnet read output showing the 8 harmonic float values

Layer 2 — OPC-UA payload extraction

import asyncio
from asyncua import Client

async def get_payload():
    async with Client("opc.tcp://<HOST>:4841/protocol7/") as c:
        ns_array = await c.get_namespace_array()
        # Find urn:protocol-seven:payload
        ns = ns_array.index("urn:protocol-seven:payload")

        vault = await c.nodes.root.get_child(
            [f"0:Objects", f"{ns}:Protocol7_Vault"])

        payload = await vault.get_child(f"{ns}:Payload_Encrypted")
        iv_hint = await vault.get_child(f"{ns}:IV_Hint")

        return (await payload.get_value()), (await iv_hint.get_value())

payload_bytes, iv_hint = asyncio.run(get_payload())
# iv_hint: "Rotation offset from CIP controller — read NONCE tag"

OPC-UA browse showing Protocol7_Vault contents and hints

Layer 3 — EtherNet/IP nonce extraction

from cpppo.server.enip.get_attribute import proxy_simple as device

with device(host="<HOST>", port=44819) as via:
    nonce = via.read("NONCE")   # = 3
    check = [via.read(f"Assembly_Check[{i}]") for i in range(3)]
    # check = [47809, 4841, 44819]  confirms all three ports

Rotation nonce = 3

Decryption

key   = b"Eiri_Key"
nonce = 3

# Step 1: XOR with repeating 8-byte key
xored = bytes(payload_bytes[i] ^ key[i % 8] for i in range(32))

# Step 2: rotate right by nonce (undo left rotation used during encryption)
flag_bytes = xored[-nonce:] + xored[:-nonce]

flag = flag_bytes.rstrip(b'\x00').decode()
print(flag)

Python decryption script printing the reconstructed flag


Key insights

  • Device description "integer components matter" directs you to truncate float to int
  • OPC-UA hints explicitly name BACnet and EtherNet/IP as sources for key and nonce
  • Assembly_Check = [47809, 4841, 44819] confirms the three-protocol architecture
  • Eiri_Key (8 chars = 64-bit) as the XOR key is a lore-consistent choice

Flag

ESPILON{pr0t0c0l_7_m3rg3_c0mpl3t3}