Une infrastructure SCADA pilote des pompes via Modbus/TCP. Depuis peu, certaines pompes redémarrent régulièrement et la supervision perd partiellement le contrôle. Les indices indiquent l'injection malveillante d'un bloc de configuration chiffré envoyé aux automates (PLC).
De plus, un binaire suspect retrouvé sur le serveur de supervision compromis aurait servi à préparer et pousser ce payload.
À vous d'identifier la cause de ce dysfonctionnement en analysant les fichiers fournis.
## Objectif de la mission:
- Identifier la première écriture Modbus malveillante dans la capture réseau.
- Mettre en évidence le pattern de l’attaque.
- Reverser le binaire suspect pour déterminer la méthode de chiffrement et récupérer la clé.
- Extraire et déchiffrer le payload afin d’obtenir le JSON de configuration injecté.
## Documents mis à votre disposition:
- Capture réseau (.pcap/.pcapng) du trafic SCADA (Modbus/TCP 502).
- Binaire suspect retrouvé sur un poste compromis.
## Votre rôle:
Croiser les indicateurs réseau et les indices de reverse pour reconstituer le mode opératoire :
justifier vos filtres Wireshark, isoler la trame initiale, démontrer l’algorithme et clé issus du binaire, et trouver des preuves permettant d’expliquer l’incident de bout en bout.
Ouvrez le fichier de capture (.pcap/.pcapng) dans Wireshark.
tcp.port == 502 && modbus && modbus.func_code == 16
Le code 16 correspond à « écriture multiple de registres » c’est ce que l’attaquant utilise.

Affichez par modbus.reference_num: vous verrez souvent une plage d’adresses regroupée (ex. autour de 1100).
Pour cibler des configurations typiquement en adresses élevées :
ip.dst == 10.0.3.13 && tcp.dstport == 502 && modbus.func_code == 16 && modbus.reference_num >= 1000

Vous connaissez l’IP du maître légitime, filtrez ainsi :
tcp.dstport == 502 && modbus.func_code == 16 && ip.src != <IP_MAITRE>
ip.dst == 10.0.3.13 && tcp.dstport == 502 && modbus.func_code == 16 && modbus.word_cnt >= 100
Vous devriez voir plusieurs paquets qui utilisent la fonction d'écriture Write.

Vous avez identifié l’adresse de départ. Filtrez :
ip.dst == 10.0.3.13 && tcp.dstport == 502 && modbus.func_code == 16 && modbus.reference_num == 1100 && modbus.word_cnt < 50
Triez par ordre croissant de temps et prenez la première trame qui correspond : c’est notre candidate n°1, donc la première charge utile compromise.

L’indicateur (flag) correspond à l’horodatage affiché. On peut copier la charge utile chiffrée pour la suite.
Utilisez cette commande pour identifier les données en lecture seule du programme :
objdump -s -j .rodata pump_update
Vous obtiendrez quelque chose comme :

Analysez ensuite le binaire (par ex. avec GHIDRA) pour comprendre la méthode de chiffrement.


On obtient la clé et l'IP visée par l'attaquant.
À ce stade, la clé et le PLC visé sont connus. La méthode de chiffrement extraite du binaire permet de rétro‑ingénierie et de déchiffrer la charge utile extraite de la capture.
Écrire un script Python qui utilise la clé trouvée dans le binaire et la charge utile extraite du fichier de capture permet de réaliser le déchiffrement. Sauvegardez la charge utile chiffrée dans un fichier texte (par ex. charge_utile.hex) avant d'exécuter le script.

# dechiffrement_legitime.py
import binascii
KEY = b'<CLE_TROUVEE>' # clé trouvée dans le binaire de l'attaquant
def rol(b, n): return ((b << n) & 0xFF) | (b >> (8 - n))
def ror(b, n): return (b >> n) | ((b << (8 - n)) & 0xFF)
def invert_pair(o0, o1):
for b0 in range(256):
b1 = (o0 ^ rol(b0,3)) & 0xFF
if ((ror(b1,2) ^ b0) & 0xFF) == o1:
return b0, b1
raise ValueError("Couple non inversible (inattendu)")
def decrypt_bytes(cipher_bytes, key):
out = bytearray()
it = iter(cipher_bytes)
for o0 in it:
try:
o1 = next(it)
except StopIteration:
o1 = 0
b0, b1 = invert_pair(o0, o1)
out.append(b0)
out.append(b1)
pre_xor = bytes(out[:len(cipher_bytes)]) # tronquer si nombre impair
ks = (key * ((len(pre_xor) // len(key)) + 1))[:len(pre_xor)]
plain = bytes(a ^ b for a, b in zip(pre_xor, ks))
return plain
hexdata = open('charge_utile.hex').read().strip()
ct = binascii.unhexlify(hexdata)
pt = decrypt_bytes(ct, KEY)
print(pt)
try:
import json
print(json.dumps(json.loads(pt), indent=2, ensure_ascii=False))
except Exception:
pass
Exécutez le script avec le fichier charge_utile.hex contenant les octets chiffrés, le script affichera le texte déchiffré et tentera de formater le JSON. Si tout est correct, vous verrez la configuration (JSON) injectée par l'attaquant — ce résultat confirme l'injection et permet de récupérer le marqueur demandé.
