- Remove undeployed challenges: Phantom_Byte, Cr4cK_w1f1, Lain_Br34kC0r3 V1, Lain_VS_Knights, Lets_All_Love_UART, AETHER_NET, Last_Train_451, Web3/ - Sync 24 solve/ files from main CTF-Espilon repo - Update all READMEs with real CTFd final scores at freeze - Add git-header.png banner - Rewrite README: scoreboard top 10, edition stats (1410 users, 264 boards, 1344 solves), correct freeze date March 26 2026
2.5 KiB
2.5 KiB
Operating Room -- Solution
Overview
Modbus TCP server simulating a hospital operating room control system. The player must discover the correct unit ID, map the registers, reverse the XOR-encoded state machine, and execute 6 timed transitions.
Steps
1. Discover Unit ID
Scan Modbus unit IDs (slave addresses). The server only responds to unit ID 13. Default unit ID 1 returns Modbus exceptions.
from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("HOST")
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!")
2. Map Registers
Read holding registers 0-255 on unit 13:
- Registers 0-19: Operating room telemetry (temperature, humidity, pressure, O2, etc.)
- Register 13:
0x4C4E("LN" -- Lain easter egg) - Register 19:
0x0D13-- this is the XOR key - Registers 100-105: State machine (state, encoded hint, timer, transitions, error, key)
- Register 105:
0x0D13-- XOR key copy (confirms reg 19) - Register 110: Write target (trigger register)
- Registers 200-215: All zeros (flag area, populated after completion)
3. Understand the State Machine
- Register 100 = current state (starts at 0)
- Register 101 = encoded hint
- Register 105 = XOR key (0x0D13)
- Decode:
expected_value = reg_101 XOR 0x0D13 - Write
expected_valueto register 110 to advance the state - Each transition must happen before register 102 (timer) reaches 0
4. Execute Transitions
| State | Subsystem | Decoded Value | Source |
|---|---|---|---|
| 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) | special |
5. Read Flag
After all 6 transitions, register 100 = 7 (complete). Read registers 200-215 and decode uint16 pairs to ASCII.
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)
Key Insights
- XOR key
0x0D13is stored in two places (reg 19 and reg 105) as a breadcrumb - The decoded values for states 0-4 match the current telemetry readings
- State 5 uses the special value
0x1337(hacker reference) - Wrong writes or timeouts reset the state machine to 0
Flag
ESPILON{m0dbu5_0p3r4t1ng_r00m}
Author
Eun0us