ESPILON-CTF-2026-Writeups/OT/Cyberia_Grid
2026-03-26 17:33:45 +00:00
..
README.md write-up: OT/Cyberia_Grid/README.md 2026-03-26 17:33:45 +00:00

Cyberia Grid

Field Value
Category OT
Difficulty Medium-Hard
Points 500
Author Eun0us
CTF Espilon 2026

Description

Cyberia is not just a nightclub. An industrial PLC controller manages the building power infrastructure over EtherNet/IP.

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.

Read the tags. Find the hidden data. The Psyche Processor awaits activation.

  • EtherNet/IP: tcp/<host>:44818

Format: ESPILON{flag}


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:

from cpppo.server.enip.get_attribute import proxy_simple as device

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:

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)

Step 5 — Wait one scan cycle and read the flag

After ~500ms (one PLC scan cycle):

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"

📸 [screenshot: Decoded_Output tag returning the flag after Psyche Processor activation]

Key concepts

  • Zone_Basement_Power = 0 is the first hint that something is hidden underground
  • 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}