ESPILON-CTF-2026-Writeups/OT/Protocol_Seven
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
..
README.md Add 107 terminal screenshots and replace all 📸 placeholders 2026-03-27 00:34:47 +00:00

Protocol Seven

Field Value
Category OT
Difficulty Hard
Points 600
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}