(TAOMF) Analyse réseau

L'ensemble des articles tagués TAOMF ont été écrit à partir d'un ensemble de notes prises suite à la lecture du livre "The Art Of Memory Forensic" écrit par Michael Hale Ligh, Andrew Case, Jamie Levy et Aaron Walters.

Tout les crédits de ces articles leur reviennent donc de droits. Par ailleurs je vous invite vraiment à lire ce livre qui est une mine colossale d'informations.


Les malware ont souvent tendance à récupérer leurs configurations depuis un serveur distant (Command and Control server ou C&C). Pour cela ils sont obligés d'initialiser des connexions TCP. Sous Windows il existe trois manières différentes d'initialiser une connexion TCP:

  • De manière directe via l'appel à la fonction socket de la librairie ws2_32.dll
  • De manière indirecte via les fonctions de la DLL wininet.dll (wrapper de ws2_32.dll).
  • Directement via le kernel en faisant appel à la TDI (Transport Driver Interface) utilisée par la librairie Winsock2

Quand un processus fait appel à la fonction socket() il lui fournit trois informations:

  • Un type de d'adresse IP à utiliser (AF_INET pour IPv4 ou AF_INET6 pour IPv6)
  • Un type (SOCK_STREAM pour les application TCP, SOCK_DGRAM pour les applications UDP, SOCK_RAW pour faire abstraction de la couche 4 du modèle OSI)
  • Un protocole (IPPROTO_TCP pour TCP, IPPROTO_UDP pour UDP, IPPROPO_IP pour IP et IPPROTO_ICMP pour ICMP)

Une fois la socket créée, elle va soit se mettre en attente d'une connexion distante (comportement serveur) soit se connecter à un port distant (comportement client).

Voici les appels de fonction fait pour une socket de type serveur:

Crédit image : The art of memory forensic

Tout d'abord on remarque que la fonction socket() va ouvrir un handler sur la ressource \Device\Afd\Endpoint. Cette ressource, \Device\Afd\Endpoint, autorise des applications user land à communiquer avec Afd.sys coté kernel land. AFK pour Ancillary Function Driver est un pilote auxiliaire qui permet à Windows d'ouvrir des sockets via Winsock2. Une fois la socket créée, on va la binder à un port (exemple le port 80 pour les processus web) puis mettre la socket en écoute (en attente d'une connexion entrante). Si la socket reçoit une connexion va elle l'accepter (accept), recevoir (recv) des données, en renvoyer (send) puis fermer la socket une fois la communication terminée (close).

Voici les appels de fonction fait par une socket de type client:

Crédit image : The art of memory forensic

On retrouve plus ou moins les mêmes appels de fonction. La seule différence est que la socket n'est pas bindé à un port et n'est pas en état d'attente de connexion. A l'inverse c'est elle qui va initier la connexion grâce à la fonction connect(). Ensuite la socket va émettre/recevoir des données puis fermer la socket une fois la communication terminée.

Les sockets sont représentées par deux structures dont une qui n'a officiellement pas de nom puisqu'elle n'est pas documentée. Globalement ce qu'il faut retenir de ces structures c'est que les sockets sont liées les unes aux autres via une simple liste chaînée. La socket N contient un champ "Next" qui est un pointeur vers l'adresse mémoire de la socket N+1:

On peut donc, une fois qu'on a détecté la structure d'une socket, énumérer l'ensemble des sockets qui étaient actives au moment du dump mémoire.

En plus de ça on trouve aussi les champs:

  • LocalIpAddress qui contient l'adresse IP locale d'écoute (ou 0.0.0.0 si la socket écoute sur toutes les IP's)
  • LocalPort: le port local d'écoute
  • Protocole: le numéro de protocole (définie par l'IANA ici)
  • PID: le PID du processus qui a ouvert la socket
  • CreateTime: le timestamp de la création de la socket
  • RemotePort: le port de connexion si utilisation de la fonction connect()
  • RemoteIpAddress: l'adresse IP distante de connexion si utilisation de la fonction connect()

Pour lister les sockets existantes on pourra se servir du module sockets de volatility. Comme les structures liées aux sockets contiennent une entrée PID, on pourra facilement lister les processus qui ont ouverts une socket. On ne saura cependant pas si la socket était utilisée en tant que client ou serveur.

En revanche on sait que les ports dits well-known ( < 1024) sont généralement utilisés par des applications serveurs disposant de droits privilégiés.

Il est aussi possible de lister les sockets inactives ainsi que les différentes connexions en se servant, encore une fois, des pool tags via les modules connscan et sockscan. Ces modules vont nous permettre de lister de potentielles sockets/connexions initialisées dans le passé.

Il sera toujours possible de cacher les connexions. C'est typiquement se que ferait un malware. Pour cela il pourra par exemple hooker les fonctions de l'API windows utilisées par les binaires comme netstat ou tcpview (DeviceIoControl ,  ZwDeviceIoControlFile , GetTcpTable).

Les développeurs du framework volatility ont d'ailleurs passé pas mal de temps à développer le module netscan qui, comme netstat, nous permettra de lister l'ensemble des sockets, les IP distantes, l'état des sockets et les processus ayant ouvert ces sockets.

Il est aussi possible de récupérer le contenu des profiles de navigation web puisque ces derniers, avant d'être écrits sur le disque, sont stockés en RAM. Deux approches existent, soit on utilise le module volatility iehistory soit on récupère les PID des processus iexplorer (google, firefox ou autre) puis on dump l'espace mémoire de ces processus et on l'analyse via des règles yara ou des grep.

Le cache DNS pourra aussi contenir des informations intéressantes. Son contenu est stocké dans la heap du processus svchost. Le pluging suivant https://github.com/mnemonic-no/dnscache nous permettra de la dumper puis d'en étudier le contenu. Il pourra aussi être intéressant de récupérer le contenu du fichier host.