From 8ef62a05725d5cfb539dace8cae8b206a86d2f8d Mon Sep 17 00:00:00 2001 From: Eun0us Date: Thu, 26 Mar 2026 17:33:47 +0000 Subject: [PATCH] write-up: OT/Protocol_Seven/README.md --- OT/Protocol_Seven/README.md | 210 +++++++++++++++++++++++++----------- 1 file changed, 150 insertions(+), 60 deletions(-) diff --git a/OT/Protocol_Seven/README.md b/OT/Protocol_Seven/README.md index d64da13..297febc 100644 --- a/OT/Protocol_Seven/README.md +++ b/OT/Protocol_Seven/README.md @@ -1,78 +1,168 @@ -# Protocol Seven -- Solution +# Protocol Seven -## Overview -Multi-protocol challenge requiring cross-referencing three OT protocols. -Eiri Masami distributed Protocol Seven's components across BACnet, OPC-UA, -and EtherNet/IP. Players must extract data from all three and combine -them to decrypt the flag. +| Field | Value | +|-------|-------| +| Category | OT | +| Difficulty | Hard | +| Points | 600 | +| Author | Eun0us | +| CTF | Espilon 2026 | -## Architecture +--- -| Layer | Protocol | Port | Provides | -|-------|----------|------|----------| -| 1 | BACnet/IP | 47809/udp | XOR encryption key (8 bytes) | -| 2 | OPC-UA | 4841/tcp | Encrypted payload (32 bytes) | -| 3 | EtherNet/IP | 44819/tcp | Rotation nonce (integer) | +## Description -## Steps +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. -### 1. Port Discovery -Scan the target -- three open ports: 47809/udp, 4841/tcp, 44819/tcp. +No single system reveals the truth. Cross-reference all three protocols. Reconstruct Protocol Seven. -### 2. Layer 1 -- BACnet Key Extraction -Send WhoIs to port 47809 → IAm from device 7777. -Read object-list: 8 AnalogValue objects named `Harmonic_0` through `Harmonic_7`. +Become one with the Wired. -Read the device description: **"Key Harmonic Array -- integer components matter"** +**Ports:** +- 47809/udp: BACnet/IP +- 4841/tcp: OPC-UA Binary +- 44819/tcp: EtherNet/IP -Read presentValue of each harmonic: -``` -Harmonic_0 = 69.14 -> int(69) = 'E' -Harmonic_1 = 105.92 -> int(105) = 'i' -Harmonic_2 = 114.37 -> int(114) = 'r' -Harmonic_3 = 105.68 -> int(105) = 'i' -Harmonic_4 = 95.44 -> int(95) = '_' -Harmonic_5 = 75.81 -> int(75) = 'K' -Harmonic_6 = 101.22 -> int(101) = 'e' -Harmonic_7 = 121.55 -> int(121) = 'y' +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 # BACnet +nmap -p 4841,44819 # OPC-UA, EtherNet/IP ``` -**XOR key = `Eiri_Key` (8 bytes)** +All three ports are open. -### 3. Layer 2 -- OPC-UA Payload Extraction -Connect anonymously to `opc.tcp://HOST:4841/protocol7/`. -Read `Server.NamespaceArray` → find `urn:protocol-seven:payload`. +### Layer 1 — BACnet key extraction -Browse `Protocol7_Vault`: -- `Payload_Encrypted`: 32-byte ByteString (the ciphertext) -- `Layer_Info`: "Payload encrypted with 8-byte repeating XOR key -- see BACnet harmonics" -- `IV_Hint`: "Rotation offset from CIP controller -- read NONCE tag" - -### 4. Layer 3 -- EtherNet/IP Nonce Extraction -Connect to EtherNet/IP on port 44819. Read tags: -- `NONCE = 3` (the rotation offset) -- `Layer_Hint`: "Rotate payload by NONCE bytes before XOR decryption" -- `Assembly_Check = [47809, 4841, 44819]` (confirms all three ports) - -### 5. Decryption ```python -# XOR payload with repeating key -xored = bytes(payload[i] ^ key[i % 8] for i in range(32)) -# Rotate right by NONCE (undo the left rotation used during encryption) -flag = xored[-nonce:] + xored[:-nonce] -# Strip null padding -print(flag.rstrip(b'\x00').decode()) +import BAC0 + +bacnet = BAC0.lite(ip="/24") +bacnet.whois("*:47809") +# → Device:7777 "Protocol-Seven-Key" ``` -## Key Insights -- The BACnet device description explicitly says "integer components matter" -- The OPC-UA hints point directly to BACnet and EtherNet/IP -- The EtherNet/IP `Assembly_Check` tag confirms the three-port architecture -- `Eiri_Key` as the XOR key is a mnemonic hint (Eiri Masami is the creator) -- The challenge teaches multi-protocol OT environments and data cross-referencing +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`** + +> 📸 `[screenshot: BACnet read output showing the 8 harmonic float values]` + +### Layer 2 — OPC-UA payload extraction + +```python +import asyncio +from asyncua import Client + +async def get_payload(): + async with Client("opc.tcp://: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" +``` + +> 📸 `[screenshot: OPC-UA browse showing Protocol7_Vault contents and hints]` + +### Layer 3 — EtherNet/IP nonce extraction + +```python +from cpppo.server.enip.get_attribute import proxy_simple as device + +with device(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) +``` + +> 📸 `[screenshot: 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}` -## Author -Eun0us +`ESPILON{pr0t0c0l_7_m3rg3_c0mpl3t3}`