# 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

```bash
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

```python
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:

```python
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](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/p7_bacnet.png)

### Layer 2 — OPC-UA payload extraction

```python
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](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/p7_opcua.png)

### Layer 3 — EtherNet/IP nonce extraction

```python
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

```python
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](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/p7_decrypt.png)

---

## 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}`
