Fuite d'Ingrédient Critique

Arthur P., Elsa F., Francois L., Joey G., Julie D., Raphael G.

← Fuite d'Ingrédient Critique · Au cœur de la base de données / Une activité bien trop suspecte / Un vol et une attaque des plus communs / Un binaire rendant malade...

Un binaire rendant malade...

Une analyse du binaire trouvé sur la machine d'administration de la production s'impose.

Dans les logs précédents, il a été remarqué que l'attaquant a déposé plusieurs fichiers dont un binaire sur l'ordinateur responsable de l'administration du réseau de production SCADA. L'entreprise nous précise qu'ils utilisent le protocole MODBUS TCP/IP pour les échanges avec leurs automates.

L'objectif va donc être de comprendre le fonctionnement de ce binaire et de déterminer s'il est toujours possible de mettre en vente les produits réalisés durant cette période.

Cote 95 pts

Indices

WinDivert est votre ami mais attention à la couche Ethernet.
Pour qu'une trame soit reforgée, il faut qu'elle se présente comme ceci : IP/TCP/MBAP/PDU !

Faire son rapport

Résolution

Afin de faire une rétro-ingénierie du binaire, nous utilisions ici Ghidra.

Type d'attaque et machine visée

Avant de commencer, il est de convenance de jeter un coup d'œil aux fichiers qui sont mis à notre disposition : une bibliothèque dynamique, le driver associé et un binaire. En se renseignant sur WinDivert, voici ce qu'indique leur documentation :

WinDivert permet la capture/injection/modification/rejet depuis l'espace utilisateur de paquets réseaux envoyé depuis/vers la pile réseau Windows.
Cela donne une idée de ce que le binaire va potentiellement réaliser.

Une fois le binaire chargé et analysé, il faut trouver le point d'entrée. Deux méthodes s'offrent à nous :
- Il est possible d’utiliser le fait d'avoir la dll qui va permettre de charger le nom des fonctions. Ainsi, dans l'arbre de symboles, il suffit de chercher WinDivert, et les fonctions associées devraient apparaître, notamment WinDivertOpen.
- Sinon, passer par l'entrée de l'arbre et fouiller fera aussi l'affaire.

Ensuite, en nous munissant de la documentation WinDivert, dans un premier temps pour WinDivertOpen :

WinDivert documentation ouvrir
WinDivert documentation ouvrir

La fonction permet donc d'ouvrir un descripteur, sur lequel un filtre est appliqué. Sachant qu'aucun flag n'est donné, seuls les paquets correspondant à cette adresse IP et ce port seront capturés tandis que le reste du trafic ne sera pas touché.

Ensuite, toujours en suivant la documentation, la couche utilisée est WINDIVERT_NETWORK_LAYER, qui permet donc toutes les actions citées précédemment.

WinDivert couche

Enfin, en parcourant le code et les différentes fonctions de WinDivert utilisées, il est possible de comprendre ce que va faire le binaire :

WinDivert reçoit

Le code fait donc une boucle sur tous les fichiers qui sont capturés et qui respectent le filtre.

Ensuite, vers la fin du code, le binaire se sert à nouveau de WinDivert mais cette fois pour recalculer les sommes de contrôle juste avant d'envoyer le paquet. Ainsi, dans certains cas, il reçoit un paquet, à besoin de recalculer les sommes de contrôle, avant de le renvoyer.

WinDivert recalcule

Ainsi, s'il ne faisait que de l'écoute, il n'aurait pas besoin de recalculer ces sommes. Cependant, il reçoit quand même des paquets donc, il ne fait pas une injection pure. Il y a forcément une modification.

Taille et codes des fonctions

Dans un premier temps, il est nécessaire de se renseigner sur le protocole Modbus TCP/IP. Il se présente sous cette forme :

Protocole Modbus

En nous basant sur les signatures des fonctions WinDivert, nous pouvons donc commencer à renommer plusieurs variables.

Il est alors possible de remarquer que la taille maximale du tampon est 65 536.

WinDivert tampon

Avec un peu de connaissance réseau, il s'agit donc de 2^16, soit la taille maximale d'un paquet IPv4 (fragmenté). Une autre manière d'expliquer ceci serait de comprendre que le paquet est intercepté au niveau de la couche réseau de Windows. Il n'y a donc pas l'en-tête Ethernet dans ce qui est capturé.

Ainsi, le premier octet du buffer correspond donc au début de l'en-tête IPv4 ! Selon Ghidra, le buffer n'est pas alloué d'un coup mais en trois fois.

WinDivert allocation

Les conditions qui vont suivre la capture du paquet sont donc des vérifications de conformité du paquet, ainsi qu'un affinage.

Taille minimale
En-tête MBAP

Nous pouvons donc conclure sur la taille minimale du paquet (IPv4 = 20, TCP = 20 minimum, MBAP = 7, et les données ≥ 1).

Ensuite, en continuant l'exploration du code, nous distinguons une condition qui mène à deux cas sur le port:
- Source 502
- Destination 502.

Cela n'est pas nécessaire maintenant mais cela sera utile pour la suite.

Enfin, nous observons un accès dans le buffer à ce qui correspond au code de fonction modbus (pointeur sur le paquet + taille de l'entête IP et TCP + 7).

Codes Modbus

Ils correspondent à :
- Écriture dans un seul registre de stockage
- Lecture de plusieurs registres de stockage

Registre et valeur

Tout d'abord, il faut savoir que Modbus marche avec un système de requête et de réponse qui se corrèlent via un ID de transaction :
- La machine d'administration SCADA envoie une requête avec l'ID 416 à une adresse IP précise vers le port 502.
- L'automate lui répond avec le même ID avec pour port source 502.

Nous allons donc déterminer quel registre est manipulé par l'attaquant. Pour cela, nous savons qu'il utilise une fonction Modbus pour écrire une valeur dans un registre.

Écriture dans un registre

Il suffit alors de déterminer les valeurs qui sont changées lorsque la machine d'administration envoie une requête d'écriture vers l'automate.

Registre et valeur

Affirmations

La réécriture lors d'une lecture est soumise à des conditions. Elle ne peut pas être réalisée à la première requête/réponse.

Requête lecture

Au passage, cela permet de voir que l'ID de la transaction est sauvegardée dans ce qui semble correspondre à un dictionnaire.

Le programme intercepte aussi les fonctions d'écritures, en remplaçant ce qui devait être écrit par la fausse valeur.

Deuxième affirmation

Lors d’une opération de lecture, l’ensemble du tableau de données est d’abord sauvegardé. Ensuite, un booléen est mis à jour pour indiquer que la valeur a bien été enregistrée. Après cette première étape, la trame renvoyée est convertie en écriture ; puis, dans le sens inverse, la trame d’écriture est réinterprétée comme une lecture. Dans le cas simple, pour les deux fonctions, la valeur est simplement modifiée en place dans les données.