Hydrotech - Vengeance d'un employé

M.Aharouni, C.Belloni, J.Blassiau, A.Corcione, S.Perrier, M.Trouiller

← Hydrotech - Vengeance d'un employé · Identification de l'attaquant / Analyse du PC Admin / Elévation de privilèges / Pompes défectueuses

Pompes défectueuses

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.

Cote 95 pts

Faire son rapport

Objectif

  • Identifier la première écriture Modbus malveillante dans la capture réseau.
  • Comprendre le schéma de chiffrement en analysant le binaire suspect.
  • Extraire et déchiffrer la charge utile pour reconstituer la configuration injectée.

1. Découverte de la capture

1.1 Filtrer les écritures Modbus

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.

alt text

1.2 Isoler la cible (PLC) et la zone « config »

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

alt text

1.3 Distinguer le maître légitime de l'attaquant

Vous connaissez l’IP du maître légitime, filtrez ainsi :

tcp.dstport == 502 && modbus.func_code == 16 && ip.src != <IP_MAITRE>

  1. Repérer les effacements massifs

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.

alt text

  1. Détecter la première petite écriture au même adresse de départ

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.

alt text

L’indicateur (flag) correspond à l’horodatage affiché. On peut copier la charge utile chiffrée pour la suite.

2. Analyse du binaire de l'attaquant

Utilisez cette commande pour identifier les données en lecture seule du programme :

objdump -s -j .rodata pump_update

Vous obtiendrez quelque chose comme :

alt text

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

alt text
alt text

On obtient la clé et l'IP visée par l'attaquant.

3. Déchiffrement de la charge utile

À 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.

charge utile chiffrée

# 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é.

json déchiffré