
Implant de canal caché DNS pour les équipes rouges.
WEASEL est un petit implant en mémoire utilisant Python 3 sans dépendances. Le client de la balise envoie une petite quantité d'informations d'identification de son hôte vers une zone DNS que vous contrôlez. Le serveur WEASEL peut ordonner aux clients d'exécuter des commandes pré-définies ou arbitraires.
WEASEL est une charge utile de stade 1, conçue pour être difficile à détecter et utile pour retrouver un accès lorsque vos étapes complètes et bruyantes sont détectées.
Statut
Voir l'utilisation dans le README du client et le README du serveur pour des instructions spécifiques.
Pour démarrer le serveur ou le client, exécutez les scripts directement ou passez-les à l'interpréteur Python.
Assurez-vous que chaque domaine C2 possède un enregistrement NS pointant vers l'adresse IP de l'hôte exécutant server.py.
WEASEL nécessite Python 3.6+.
Le client est autonome et n'utilise que des bibliothèques standard, il peut donc être exécuté sur macOS, Linux, etc.
Le serveur a quelques dépendances pip, incluses dans requirements.txt du serveur. Le serveur doit être exécuté sur Linux, mais rien n'empêche de le faire fonctionner sur macOS ou d'autres *nix.
Pas besoin d'obfusquer et de minimiser la balise dans ce cas. Les instructions print sont conservées. Comme pour l'utilisation ci-dessus, assurez-vous que les enregistrements NS pour le(s) domaine(s) dans servers de beacon.py pointent vers l'adresse IP de server.py.
Sur l'hôte serveur :
sudo python3 server.py
Sur l'hôte victime (peut être le même que le serveur) :
python3 beacon.py
Vous n'avez pas besoin de comprendre tout cela pour utiliser WEASEL.
La balise communique via DNS en utilisant des requêtes et réponses AAAA. Elle n'utilise pas d'enregistrements TXT car ceux-ci sont connus pour être utilisés par les malwares et tunnels DNS. Les équipes bleues ont souvent des détections de tunneling DNS qui alertent sur les grandes requêtes TXT.
Le côté client n'a pas besoin de root pour fonctionner, n'utilise pas de sockets bruts et ne crée pas de paquets DNS malformés. Il utilise des interfaces système et linguistiques standard pour effectuer des requêtes DNS. Les informations sont encodées + chiffrées dans les enregistrements eux-mêmes.
Cette balise est conçue pour être lente et discrète, avec peu de bande passante. Elle doit nous indiquer sur quels hôtes elle se trouve et nous donner un moyen de lancer d'autres étapes si nécessaire, et rien de plus. Bien qu'elle prenne en charge les commandes arbitraires, elle n'est pas destinée à être utilisée comme un shell interactif régulier ou un canal de communication.
WEASEL est un stade 1 que vous laissez tourner, garantissant un accès continu pendant que vos étapes complètes (et donc plus bruyantes) sont détectées.
WEASEL était initialement ciblé pour des serveurs à haute disponibilité où nous avions un point d'appui/vecteur d'exploitation fiable. Éviter les analyses forensiques était une priorité élevée. En conséquence, il n'a pas de fonctionnalités de persistance natives.
Vous pouvez le rendre persistant en ajoutant son exécution à votre technique de persistance préférée, ce qui est laissé comme exercice au lecteur :)
Une requête (du client) est une simple requête AAAA pour un nom formaté comme suit :
<préambule><données>.<flux>.<session>.domaine.tld
Le préambule fait 2 octets. Préambule[0] est le numéro de séquence de ce paquet. Préambule[1] est le nombre total de paquets dans ce flux.
Les données sont limitées à 50 octets (configurable) et contiennent la charge utile. La charge utile est encodée en base32 avec un alphabet personnalisé.
Tout d'abord, tous les caractères 'w' sont remplacés par '-'.
Ensuite, le caractère de remplissage est remplacé de '=' à 'w' pour se conformer au jeu de caractères DNS : [a-z0-9] et [-].
Nous ne remplaçons pas '=' par '-' directement car le remplissage sera toujours à la fin de la chaîne, et terminer un nom d'hôte par '---' est à la fois suspect et contraire à la RFC DNS. De cette façon, lorsqu'une chaîne a un remplissage, elle se termine par 'www', ce qui est à la fois moins suspect et conforme à la RFC.
Une réponse (du serveur) est composée d'une ou plusieurs réponses AAAA.
Chaque réponse AAAA est une charge utile chiffrée de 16 octets représentée comme une adresse IPv6 en utilisant socket.inet_ntop. Les réponses dans une réponse DNS ne conservent pas leur ordre en transit, elles sont donc séquencées et réassemblées comme les requêtes client.
La charge utile de transport est une chaîne d'éléments de données séparés par un caractère ^.
Les requêtes et réponses suivent ce format :
<type>|<données>
Les sessions sont de longue durée : un client initie une session lorsque la balise est exécutée pour la première fois et cette session doit durer tout le temps que la balise est active sur ce client. Notez que comme la balise est en mémoire et non persistante, les données de session sont stockées dans la mémoire de ce processus Python. Toute nouvelle invocation de la balise initiera une nouvelle session.
L'initiation d'une session implique que le client crée un message avec un préambule non-données unique (pour signaler au serveur qu'il s'agit d'une nouvelle session) : la concaténation d'une clé publique Diffie-Hellman de 32 octets et d'un IV AES aléatoire de 16 octets.
Le serveur reçoit ceci et répond avec sa propre clé publique de 32 octets. À ce stade, le client et le serveur ont établi une clé de session partagée qui sera utilisée pour la durée de cette session pour chiffrer les charges utiles de données en utilisant AES-128 en mode CTR. L'échange Diffie-Hellman éphémère garantit que chaque connexion client-serveur utilise une clé de session unique avec une confidentialité persistante.
Le chiffrement est volontairement mauvais pour plusieurs raisons :
Voici quelques problèmes connus du schéma de chiffrement :
p est le groupe 5 de la RFC 3526 tronqué aux 32 premiers octets. Cela limite non seulement les clés publiques et privées à 32 octets, mais le groupe 5 est déjà déprécié et déconseillé. J'appelle cette mauvaise décision "Groupe 1".a au lieu d'un CSPRNG.Chaque message envoyé entre un client et un serveur doit être encapsulé en paquets de 50 octets maximum, afin de rester en dessous de cette limite de 52 octets pour les détections courantes de canaux cachés DNS. Tous les paquets d'un message particulier font partie du même flux. Message == Flux.
Les flux sont identifiés par un nombre hexadécimal aléatoire de 2 octets. Rappelez-vous que le format de requête client est :
<préambule><données>.<flux>.<session>.domaine.tld
Le préambule de 2 octets de chaque paquet du flux contient un numéro de séquence et le nombre total de paquets de ce flux. Cela permet au serveur de savoir quand tout est arrivé.
Parce que nous faisons cela via DNS sur UDP, qui ne fournit aucune garantie sur l'ordre d'arrivée des datagrammes. C'est pourquoi WEASEL doit prendre en compte le séquençage, le réassemblage et le suivi de plusieurs flux provenant de nombreuses balises.
Chaque flux réinitialise un chiffrement AES-128-CTR globalement partagé pour chiffrer/déchiffrer les charges utiles.
La charge utile ne peut être déchiffrée qu'une fois le flux terminé (tous les paquets sont arrivés). Si ce n'était pas pour le base32, nous pourrions déchiffrer ce que nous avons du message même si nous manquions des paquets (car AES-CTR est un chiffrement de flux) mais nous ne pouvons pas décoder partiellement des flux base32. Tant pis. Par la nature des clients DNS, les requêtes sont faites plusieurs fois (généralement 2 ou 4 fois) jusqu'à ce qu'une réponse soit reçue, donc nous avons une bonne probabilité de recevoir tous les paquets d'un flux car chaque paquet devrait être envoyé par le client au moins deux fois. Si nous perdons des paquets ou des flux, ce n'est pas grave, la balise se reconnectera plus tard et aura probablement plus de chance à ce moment-là.
Consultez le fichier CONTRIBUTING pour savoir comment aider.
WEASEL est sous licence MIT, comme indiqué dans le fichier LICENSE.
| Type | Signification (expéditeur) | Aussi appelé | Données |
|---|
| 0 | Accusé de réception | ACK | Hexadécimal aléatoire |
| 1 | Enregistrement (client) | PING | Hexadécimal aléatoire |
| 2 | Terminez-vous (serveur), je me termine (client) | FIN | |
| 3 | Message d'initialisation (client) | SYN | `version |
| 4 | Reconnexion (serveur) | RST | |
| 5 | Définir l'intervalle de rappel (serveur) | secondes | |
| 6 | Obtenir les données d'interface réseau | eth0 1.2.3.4/24\neth1 fe80:::/64\n... | |
| 8 | Évaluer du code Python3 arbitraire jusqu'à 666 octets (serveur), retourner les 400 premiers octets de la sortie (client) | EVAL | script One-liner Python3 |
| 9 | Exécuter une commande arbitraire jusqu'à 666 octets (serveur), retourner les 400 premiers octets de la sortie (client) | EXEC | commande bash |