write-up: OT/Protocol_Seven/README.md
This commit is contained in:
parent
8df3eae070
commit
8ef62a0572
@ -1,78 +1,168 @@
|
|||||||
# Protocol Seven -- Solution
|
# Protocol Seven
|
||||||
|
|
||||||
## Overview
|
| Field | Value |
|
||||||
Multi-protocol challenge requiring cross-referencing three OT protocols.
|
|-------|-------|
|
||||||
Eiri Masami distributed Protocol Seven's components across BACnet, OPC-UA,
|
| Category | OT |
|
||||||
and EtherNet/IP. Players must extract data from all three and combine
|
| Difficulty | Hard |
|
||||||
them to decrypt the flag.
|
| Points | 600 |
|
||||||
|
| Author | Eun0us |
|
||||||
|
| CTF | Espilon 2026 |
|
||||||
|
|
||||||
## Architecture
|
---
|
||||||
|
|
||||||
| Layer | Protocol | Port | Provides |
|
## Description
|
||||||
|-------|----------|------|----------|
|
|
||||||
| 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) |
|
|
||||||
|
|
||||||
## 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
|
No single system reveals the truth. Cross-reference all three protocols. Reconstruct Protocol Seven.
|
||||||
Scan the target -- three open ports: 47809/udp, 4841/tcp, 44819/tcp.
|
|
||||||
|
|
||||||
### 2. Layer 1 -- BACnet Key Extraction
|
Become one with the Wired.
|
||||||
Send WhoIs to port 47809 → IAm from device 7777.
|
|
||||||
Read object-list: 8 AnalogValue objects named `Harmonic_0` through `Harmonic_7`.
|
|
||||||
|
|
||||||
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:
|
Format: **ESPILON{flag}**
|
||||||
```
|
|
||||||
Harmonic_0 = 69.14 -> int(69) = 'E'
|
---
|
||||||
Harmonic_1 = 105.92 -> int(105) = 'i'
|
|
||||||
Harmonic_2 = 114.37 -> int(114) = 'r'
|
## TL;DR
|
||||||
Harmonic_3 = 105.68 -> int(105) = 'i'
|
|
||||||
Harmonic_4 = 95.44 -> int(95) = '_'
|
Read 8 BACnet AnalogValue harmonics (integer parts spell `Eiri_Key`). Read OPC-UA for the
|
||||||
Harmonic_5 = 75.81 -> int(75) = 'K'
|
32-byte encrypted payload and hints pointing to BACnet and EtherNet/IP. Read EtherNet/IP
|
||||||
Harmonic_6 = 101.22 -> int(101) = 'e'
|
for the rotation nonce (3). XOR the payload with the 8-byte repeating key, then rotate right
|
||||||
Harmonic_7 = 121.55 -> int(121) = 'y'
|
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
|
||||||
```
|
```
|
||||||
|
|
||||||
**XOR key = `Eiri_Key` (8 bytes)**
|
All three ports are open.
|
||||||
|
|
||||||
### 3. Layer 2 -- OPC-UA Payload Extraction
|
### Layer 1 — BACnet key extraction
|
||||||
Connect anonymously to `opc.tcp://HOST:4841/protocol7/`.
|
|
||||||
Read `Server.NamespaceArray` → find `urn:protocol-seven:payload`.
|
|
||||||
|
|
||||||
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
|
```python
|
||||||
# XOR payload with repeating key
|
import BAC0
|
||||||
xored = bytes(payload[i] ^ key[i % 8] for i in range(32))
|
|
||||||
# Rotate right by NONCE (undo the left rotation used during encryption)
|
bacnet = BAC0.lite(ip="<YOUR_IP>/24")
|
||||||
flag = xored[-nonce:] + xored[:-nonce]
|
bacnet.whois("*:47809")
|
||||||
# Strip null padding
|
# → Device:7777 "Protocol-Seven-Key"
|
||||||
print(flag.rstrip(b'\x00').decode())
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Key Insights
|
Device description: *"Key Harmonic Array — integer components matter"*
|
||||||
- The BACnet device description explicitly says "integer components matter"
|
|
||||||
- The OPC-UA hints point directly to BACnet and EtherNet/IP
|
Read all 8 harmonics:
|
||||||
- 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)
|
```python
|
||||||
- The challenge teaches multi-protocol OT environments and data cross-referencing
|
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://<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"
|
||||||
|
```
|
||||||
|
|
||||||
|
> 📸 `[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="<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
|
## Flag
|
||||||
`ESPILON{pr0t0c0l_7_m3rg3_c0mpl3t3}`
|
|
||||||
|
|
||||||
## Author
|
`ESPILON{pr0t0c0l_7_m3rg3_c0mpl3t3}`
|
||||||
Eun0us
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user