ESPILON-CTF-2026-Writeups/OT/Operating_Room/README.md

85 lines
2.5 KiB
Markdown

# 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.
```python
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_value` to 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.
```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)
```
## Key Insights
- XOR key `0x0D13` is 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