
L’heure est grave, il se pourrait que quelqu’un ait volé le secret de la sauce Szechuan, un mystère millénaire et très bien gardé. Quel goût elle a ? Je l’ignore mais nous sommes mandatés pour découvrir si oui ou non ce secret a été compromis. Mettons-nous dans la peau d’un détective pour cette enquête.
Une seule contrainte pour nous : nous utiliserons uniquement la capture réseau pour mener notre investigation. Dans cet article nous verrons à quel point l’observation du réseau est cruciale pour la compréhension d’une attaque et nous comprendrons comment extraire et charger des données avec zeek et python.
Dans ce premier article nous allons voir comment l’analyse réseau peut nous aider à comprendre une cyberattaque et nous allons commencer à disséquer notre jeu de données. Ici, nous privilégierons une approche Data Science s’appuyant sur des outils garantissant la reproductibilité de nos analyses : Zeek et Python.
Attention : dans l’article suivant je vais essayer de vulgariser plusieurs notions autour du forensic mais cela demande une compréhension initiale sur l’environnement Windows et ses protocoles. Je vous souhaite une bonne lecture !
Le réseau dans la réponse à incident
Aujourd’hui, tout est réseau et tout communique ! Sur un réseau d’entreprise, les postes communiquent vers l’extérieur mais aussi entre eux, pour récupérer des ressources internes, accéder aux mails et beaucoup d’actions banales.
Mais un attaquant doit communiquer aussi pour contrôler les machines à distance ou piloter des malwares. Une fois qu’il a réussi son accès initial qui peut souvent passer par le réseau, il doit bien communiquer depuis l’extérieur vers la machine compromise pour la piloter.
Ainsi, observer ce qui se passe sur le réseau d’une entreprise est crucial pour comprendre ce qu’un attaquant a pu y faire mais également pour s’assurer qu’il n’y en a pas. Car sur une machine, l’attaquant a des moyens d’altérer les preuves numériques pour contrer l’analyse mais sur le réseau c’est beaucoup plus compliqué.
Moyens et collecte de données réseau
Prenons pour exemple l’entreprise représentée ci-dessous :
Dans le scénario représenté, on a un malware implanté sur la machine user02 qui contacte régulièrement le site myalabe1lle[.]com détenu par un attaquant. Un RAT (pour Remote Access Trojan) fonctionne souvent en prenant toutes les X minutes ses ordres du serveur sur lequel il a été programmé pour se connecter. On appelle cela un beacon et on appelle ce processus le beaconing. C’est souvent le moyen le plus simple pour l’attaquant de communiquer avec son malware car les pare-feux sont plus souvent pensés pour bloquer les connexions entrantes que les connexions sortantes. Dans un réseau un minimum sécurisé, il est plus facile de communiquer en sortant vers internet que l’inverse.
Maintenant, du point de vue du défenseur si on voulait observer le trafic sur ce parc on aurait plusieurs moyens.
Tout d’abord on pourrait s’implanter au niveau de la balise verte “point d’observation réseau”.
Ici on pourrait réaliser une Full Packet Capture pour récupérer tout ce qui passe.
On utilise souvent le format de fichier .pcap pour stocker de telles informations.
On pourrait également récupérer les journaux des deux pare-feux : l’openSense et le PfSense. Les pare-feux stockent les connexions qui ont eu lieu dans la limite de leur stockage et de leur capacité de rétention. Ces données sont souvent moins complètes que les précédentes mais sont activées par défaut sur la plupart des équipements.
Enfin, on pourrait également utiliser d’autres données telles que les logs DNS de l’Active Directory. En effet, dans la plupart des installations Microsoft de cette taille c’est l’active directory qui porte le rôle de serveur DNS local et s’il est configuré pour (rarement fait pour des raisons de performance), on peut récupérer les résolutions qui ont été faites et avoir une idée du trafic sur notre réseau.
Actions de l’attaquant et impact sur le réseau
Parmi les actions possibles d’un attaquant lesquelles peuvent être visibles sur le réseau ? Pour y répondre, revenons à la base : la matrice mitre att&ck. Cette matrice liste dans l’ordre logique d’une attaque, toutes les actions qu’un attaquant pourrait réaliser voici comment elle se présente :

Vous pouvez retrouver cette matrice ici. L’idée est que de gauche à droite vous trouvez le déroulé logique d’une attaque du point de vue de l’attaquant. Depuis la reconnaissance, jusqu’à l’exfiltration de données et l’impact sur votre organisation. Dans l’appellation Mitre, chaque étape s’appelle une tactique et dans chacune on trouve les techniques que l’attaquant peut utiliser.
Exemple : une fois un premier poste compromis, l’attaquant souhaite souvent pouvoir survivre au redémarrage de la machine, c’est ce qu’on appelle la persistance. La technique T1053 : Scheduled Task/Job, documente qu’un attaquant peut mettre en place des tâches planifiées pour assurer que son malware sera relancé dans le futur malgré un redémarrage de la machine.
Du point de vue réseau, la plupart de ces étapes sont visibles clairement si on a les bons points de capture. Les seules étapes qui risqueraient de vraiment échapper à notre vision seraient :
- la persistance
- l’escalade de privilège
- l’évasion de défense
Ces étapes étant locales par nature il est rarement possible de les observer sur le réseau. À noter que selon notre poste d’observation nous pourrions rater certaines autres étapes. Par exemple, si nous n’observons qu’un point de sortie vers internet il est impossible de voir les mouvements latéraux entre les machines.
Les étapes de la kill chain (la chaîne d’attaque) qui sont le plus repérables par le réseau sont principalement :
- command and control : car le malware doit forcément communiquer avec son serveur
- mouvements latéraux : plutôt remarquable car les connexions d’une machine à l’autre sur certains ports peuvent être inattendues
- exfiltration de données : si mal fait, une augmentation soudaine du volume de donnée peut être remarquable
Pour un exemple de mouvement latéral visible par le réseau, je vous invite à lire mon article backdoor dce-rpc.
Limites de l’analyse réseau
Nous avons pu voir quelques limites de l’analyse réseau précédement, mais d’autres problèmes se posent encore à nous comme le chiffrement. En effet, beaucoup de communications sont chiffrées et reposent maintenant sur le protocole TLS et nous empêchent d’avoir une bonne vision sur les échanges. Nous pouvons cependant continuer à voir qui communique avec qui sans avoir le contenu, et de ce point de vue nous sommes limités à établir si quelque chose est simplement suspect ou non. C’est une des raisons qui nécessite de corréler l’observation réseau avec une analyse système dans le cadre d’une réponse à incident.
Le NAT pose souvent des problèmes pour la compréhension de l’analyste. Ainsi, avec la pénurie d’adresses IPv4 une connexion sortante se passe souvent comme ça :

Et donc si on observe la connexion depuis l’extérieur du pare-feu on sera bien en peine de déterminer qui est la machine source car leur adresses IP sont toutes remplacées par celle du pare-feu. Ce n’est pas impossible mais plus compliqué c’est donc pour cela qu’on essaye généralement d’observer le trafic depuis l’intérieur du pare-feu.
Mise en place et chargement des données
Maintenant qu’on a introduit rapidement les intérêts du forensique réseau, c’est parti pour découvrir et exploiter nos données !
Dataset
Pour cette première analyse on va utiliser un dataset fourni par dfir-madness. Il s’agit donc d’un pcap, c’est-à-dire un fichier qui enregistre toutes les données échangées sur un réseau.
L’auteur de ce site propose quelques datasets pour s’exercer au forensic, soit l’art de faire parler la donnée pour y retrouver des traces d’évènements passés.
Ainsi que vous l’avez compris précédemment, un grave problème cyber est apparu dans l’entreprise qui stockait la recette de la sauce Szechuan. Nous allons donc essayer de répondre à la question : le secret de la sauce a-t-il été volé ?
Outillage
En général en forensic, une fois les données brutes à notre disposition il reste deux étapes. Le parsing (extraction des données de manière reproductible et intelligible) puis l’indexation dans un outil qui rende possible les requêtes.
Dans cet article, nous allons essayer de faire simple avec deux outils open source bien connus : zeek et python.
Et pourquoi pas wireshark ? Wireshark est parfait pour analyser une petite capture réseau ou développer des règles de détection mais il ne passe pas du tout à l’échelle. De plus, ce n’est pas reproductible et difficilement automatisable.
Zeek
Zeek est un magnifique outil opensource qui permet de parser (soit extraire les données utiles) d’une capture réseau. En lui parlant gentiment, il peut nous extraire à peu près n’importe quel protocole dans différents formats et notamment le json.
Zeek peut également être étendu par différents plugin ainsi que son langage de programmation.
Python
Afin d’éviter la tâche de se monter un SIEM, on va utiliser un outil beaucoup plus versatile : python. En effet grâce à la librairie pandas, python est parfait pour charger des données tabulaires, les traiter et les requêter et c’est exactement ce dont nous avons besoin en forensic.
Nous ferons notre analyse dans un jupyterlab, dans lequel nous écrirons au fur et à mesure. Le présent article ne présente pas le jupyterlab en lui-même mais plutôt une version synthétique.
Installation des dépendances
Pour zeek nous allons l’installer sous forme de docker pour faciliter l’installation. Je vous invite à installer docker en suivant la documentation de votre distribution.
Ensuite, on peut télécharger le Dockerfile disponible puis construire l’image :
mkdir zeek && cd zeek
wget https://raw.githubusercontent.com/theophane-droid/blog_content/refs/heads/main/network_investigation/docker/Dockerfile
docker build -t custom_zeek . # construction de l'image zeek
Et pour python, on va créer un environnement virtuel dédié et installer nos dépendances à l’intérieur :
python3 -m venv venv
source venv/bin/activate
pip install jupyterlab pandas
Dissection des pcaps
Pour commencer notre analyse on va pouvoir télécharger le script zeek disponible ici : profile.zeek. Ce script zeek décrit les différents protocoles qu’on va disséquer, les configurations qu’on importe et d’autres automatisations.
Si l’on regarde le début de notre script :
@load base/protocols/conn
@load base/protocols/dns
@load base/protocols/http
@load base/protocols/ssl
@load base/protocols/smtp
@load base/protocols/ftp
@load base/protocols/ssh
Ici on charge plusieurs scripts zeek qui vont nous permettre d’analyser les protocoles DNS, HTTP, SMTP, FTP, TLS et SSH.
Pour chacun de ces protocoles, zeek va nous créer un fichier .log qui contiendra les sessions parsées en une forme intelligible.
S’agissant de conn, zeek va simplement nous extraire toutes les connexions dans un fichier nommé conn.log.
Procédons maintenant à l’extraction des données :
docker run --rm --name zeek -v .:/analysis -w /analysis custom_zeek \
zeek -r /analysis/sample.pcap /analysis/profile.zeek -e 'redef LogAscii::use_json=T;'
Une petite explication s’impose sur la commande ci-dessus. Tout d’abord la première partie : docker run --rm --name zeek -v .:/analysis -w /analysis custom_zeek.
On lance un conteneur avec l’image zeek précédemment téléchargée en spécifiant qu’on doit monter le répertoire actuel dans /analysis avec -v.
On spécifie aussi qu’on va travailler dans le répertoire /analysis monté dans le conteneur avec -w.
La seconde partie de la commande : zeek -r /analysis/sample.pcap /analysis/profile.zeek -e 'redef LogAscii::use_json=T;' exécute zeek dans le conteneur en lui spécifiant notre profile.
On lance zeek sur un fichier nommé sample.pcap qui est donc le pcap qu’on essaye d’analyser et on spécifie avec le -e qu’on veut une sortie en json.
L’analyse ne devrait pas prendre plus d’une minute ou deux, et une fois que celle-ci est finie on trouve les fichiers suivants :
~/project/blog_content/network_investigation> ls
╭────┬───────────────────┬──────┬──────────┬────────────────╮
│ # │ name │ type │ size │ modified │
├────┼───────────────────┼──────┼──────────┼────────────────┤
│ 0 │ capture_loss.log │ file │ 2.3 kB │ 2 minutes ago │
│ 1 │ conn.log │ file │ 13.3 MB │ 2 minutes ago │
│ 2 │ dce_rpc.log │ file │ 49.3 kB │ 2 minutes ago │
│ 3 │ dns.log │ file │ 867.1 kB │ 2 minutes ago │
│ 4 │ files.log │ file │ 339.0 kB │ 2 minutes ago │
│ 5 │ http.log │ file │ 124.1 kB │ 2 minutes ago │
│ 6 │ kerberos.log │ file │ 20.0 kB │ 2 minutes ago │
│ 7 │ ldap.log │ file │ 38.1 kB │ 2 minutes ago │
│ 8 │ ldap_search.log │ file │ 71.2 kB │ 2 minutes ago │
│ 9 │ notice.log │ file │ 68.1 kB │ 2 minutes ago │
│ 10 │ ocsp.log │ file │ 246.7 kB │ 2 minutes ago │
│ 11 │ packet_filter.log │ file │ 90 B │ 2 minutes ago │
│ 12 │ pe.log │ file │ 792 B │ 2 minutes ago │
│ 13 │ profile.zeek │ file │ 499 B │ 44 minutes ago │
│ 14 │ rdp.log │ file │ 23.2 kB │ 2 minutes ago │
│ 15 │ sample.pcap │ file │ 197.5 MB │ 5 years ago │
│ 16 │ smb_files.log │ file │ 14.0 kB │ 2 minutes ago │
│ 17 │ smb_mapping.log │ file │ 5.0 kB │ 2 minutes ago │
│ 18 │ ssl.log │ file │ 6.4 MB │ 2 minutes ago │
│ 19 │ weird.log │ file │ 6.1 kB │ 2 minutes ago │
│ 20 │ x509.log │ file │ 145.8 kB │ 2 minutes ago │
╰────┴───────────────────┴──────┴──────────┴────────────────╯
Ainsi, on a bien un fichier .log par protocole reconnu par zeek. Comme expliqué plus haut, conn.log nous décrira l’ensemble des connexions
survenues dans le pcap et celles-ci seront à nouveau décrite dans le fichier .log du protocole approprié.
Regardons la première ligne de conn.log avec jq ( petit outil très pratique pour l’ouverture de json ) :
~/project/blog_content/network_investigation> head -n 1 conn.log | jq
{
"ts": 1600466289.552099,
"uid": "CY4WhX1MsAdfYVW5rc",
"id.orig_h": "fe80::2dcf:e660:be73:d220",
"id.orig_p": 54064,
"id.resp_h": "ff02::1:3",
"id.resp_p": 5355,
"proto": "udp",
"service": "dns",
"duration": 0.4179189205169678,
"orig_bytes": 60,
"resp_bytes": 0,
"conn_state": "S0",
"local_orig": true,
"local_resp": false,
"missed_bytes": 0,
"history": "D",
"orig_pkts": 2,
"orig_ip_bytes": 156,
"resp_pkts": 0,
"resp_ip_bytes": 0,
"ip_proto": 17,
"ja4l": "",
"ja4ls": "",
"ja4t": "",
"ja4ts": ""
}
Zeek nous décrit ici la connexion entre une ip source : id.orig_h et une ip dest id.resp_h.
Notez que zeek nous donne des informations variées sur les ports sources, dest, durée de connexion, nombre de bytes échangés dans les deux sens, etc…
Si par exemple on regarde la première entrée de dns.log avec jq :
~/project/blog_content/network_investigation> head -n 1 dns.log | jq
{
"ts": 1600466289.552099,
"uid": "CY4WhX1MsAdfYVW5rc",
"id.orig_h": "fe80::2dcf:e660:be73:d220",
"id.orig_p": 54064,
"id.resp_h": "ff02::1:3",
"id.resp_p": 5355,
"proto": "udp",
"trans_id": 26344,
"query": "citadel-dc01",
"qclass": 1,
"qclass_name": "C_INTERNET",
"qtype": 255,
"qtype_name": "*",
"AA": false,
"TC": false,
"RD": false,
"RA": false,
"Z": 0,
"rejected": false,
"opcode": 0,
"opcode_name": "query"
}
Ici dns.log nous décrit la même session que le conn.log précédent, on peut le voir au champ uid qui est strictement le même.
Par rapport à conn, dns.log nous donne des informations supplémentaires spécifiques à dns, le type de query, la query, la réponse, si elle a réussi, etc…
On peut également citer weird.log qui recense les bizarreries relevées par zeek. Cela peut être un point de départ intéressant pour une analyse.
Chaque type de log standard zeek est décrit ici dans la documentation.
Chargement et exploration des données
Une fois notre pcap traité par zeek on peut lancer jupyter lab dans notre terminal :
jupyter lab
Copiez bien l’URL renvoyée par la commande avec le token qui vous permettra de vous connecter. Dans jupyter, on peut commencer par créer une cellule code pour importer nos dépendances et configurer pandas :
import os
import pandas as pd
pd.set_option("display.max_rows", 50)
pd.set_option("display.max_columns", None)
pd.set_option("display.max_colwidth", None)
pd.set_option("display.width", None)
Puis charger tous nos datasets et convertir tous nos timestamp en format lisible ( attention on est en UTC ) :
datasets = {}
for file in os.listdir('.'):
if file.endswith('.log'):
name = file.split('.')[0]
datasets[name] = pd.read_json(file, lines=True)
datasets[name]["dt_utc"] = pd.to_datetime(datasets[name]["ts"], unit="s", utc=True)
Enfin on peut afficher les premières connexions ainsi :
conn = datasets['conn']
conn[['dt_utc', 'id.orig_h', 'id.resp_h', 'id.resp_p', 'duration']].head()

Pour finir quelques exemples d’utilisation de pandas pour analyser nos données :
# 1. Top 5 des destinations les plus actives (en nombre de connexions)
conn['id.resp_h'].value_counts().head(5)
# 2. Identifier les transferts de données suspects (> 1 Mo)
conn[conn['resp_bytes'] > 1000000][['id.orig_h', 'id.resp_h', 'service', 'resp_bytes']]
# 3. Lister tous les domaines résolus (DNS) pour repérer du Beaconing
datasets['dns']['query'].unique()
Conclusion
C’est fait ! Nous sommes maintenant prêts à commencer l’analyse dans jupyterlab ! Comme nous le verrons, un des grands avantages de l’outil c’est de pouvoir alterner les cellules de code et celles de markdown afin de pouvoir commenter notre analyse à chaque étape.
Pour résumer, dans cet article nous avons pu comprendre les bases de l’investigation réseau et voir comment extraire nos données avec zeek et les charger dans jupyterlab.
Dans le prochain article nous allons regarder de plus près nos données et trouver les actions malveillantes qui auraient pu mener au vol de la sauce Szechuan. Au programme : accès initial, mouvements latéraux et exfiltration, rendez-vous dans le prochain article pour voir l’enquête en elle-même. À la prochaine !