write-up: OT/Operating_Room/README.md

This commit is contained in:
Eun0us 2026-03-26 17:33:46 +00:00
parent 216f71dbc0
commit 8df3eae070

View File

@ -1,62 +1,133 @@
# Operating Room -- Solution # Operating Room
## Overview | Field | Value |
Modbus TCP server simulating a hospital operating room control system. |-------|-------|
The player must discover the correct unit ID, map the registers, reverse | Category | OT |
the XOR-encoded state machine, and execute 6 timed transitions. | Difficulty | Medium-Hard |
| Points | 500 |
| Author | Eun0us |
| CTF | Espilon 2026 |
## Steps ---
### 1. Discover Unit ID ## Description
Scan Modbus unit IDs (slave addresses). The server only responds to **unit ID 13**.
Default unit ID 1 returns Modbus exceptions. 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/<host>: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
### Step 1 — Discover the correct unit ID
Default unit ID 1 returns Modbus exceptions. Scan IDs 1-20:
```python ```python
from pymodbus.client import ModbusTcpClient from pymodbus.client import ModbusTcpClient
client = ModbusTcpClient("HOST")
client = ModbusTcpClient("<HOST>")
client.connect() client.connect()
for uid in range(1, 20): for uid in range(1, 20):
r = client.read_holding_registers(0, 1, slave=uid) r = client.read_holding_registers(0, 1, slave=uid)
if not r.isError(): if not r.isError():
print(f"Unit ID {uid} responds!") print(f"Unit ID {uid} responds!")
break
``` ```
### 2. Map Registers **Unit ID 13 responds.** (Reference to Room 013.)
Read holding registers 0-255 on unit 13: ### Step 2 — Map registers on unit 13
- **Registers 0-19**: Operating room telemetry (temperature, humidity, pressure, O2, etc.) ```python
- **Register 13**: `0x4C4E` ("LN" -- Lain easter egg) regs = client.read_holding_registers(0, 256, slave=13).registers
- **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 Key registers:
- Register 100 = current state (starts at 0) | Register | Value | Meaning |
- Register 101 = encoded hint |----------|-------|---------|
- Register 105 = XOR key (0x0D13) | 0-19 | Telemetry | HVAC, humidity, pressure, O2, fan RPM, lux... |
- Decode: `expected_value = reg_101 XOR 0x0D13` | 13 | `0x4C4E` | "LN" — Lain easter egg |
- Write `expected_value` to register 110 to advance the state | 19 | `0x0D13` | XOR key |
- Each transition must happen before register 102 (timer) reaches 0 | 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 |
### 4. Execute Transitions > 📸 `[screenshot: register dump highlighting XOR key at reg 19 and 105, and state machine at 100-105]`
| State | Subsystem | Decoded Value | Source | ### Step 3 — Decode the state machine logic
|-------|-----------|--------------|--------|
| 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 - Register 100 = current state (06)
- 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
After all 6 transitions, register 100 = 7 (complete). ### Step 4 — Execute all 6 transitions
Read registers 200-215 and decode uint16 pairs to ASCII.
| 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)
```
> 📸 `[screenshot: script executing each transition and state advancing from 0 to 6]`
### Step 5 — Read the flag
After state 6 (complete), read registers 200-215:
```python ```python
regs = client.read_holding_registers(200, 16, slave=13).registers regs = client.read_holding_registers(200, 16, slave=13).registers
@ -68,17 +139,10 @@ for val in regs:
print(flag) print(flag)
``` ```
## Key Insights > 📸 `[screenshot: flag registers decoded to ASCII]`
- 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 ## Flag
`ESPILON{m0dbu5_0p3r4t1ng_r00m}` `ESPILON{m0dbu5_0p3r4t1ng_r00m}`
## Author
Eun0us