
API Python pour utilisation avec la spécification External C2 de Cobalt Strike.
Framework Python pour créer et utiliser des interfaces permettant de transférer des données entre frameworks, avec un accent particulier sur le rôle d’extension des frameworks de commande et de contrôle.
Actuellement, ce projet n’est conçu que comme une implémentation de la spécification External C2 de Cobalt Strike, telle que décrite dans ce document de spécification, mais il est susceptible d’évoluer à mesure que le projet mûrit.
Un immense crédit revient à xychix. Ce projet n’aurait pas été possible du tout sans leurs précieuses contributions. Pour l’essentiel, ce projet est une reconstruction et une extension du projet External C2 d’Outflank
Ce projet se compose des parties principales suivantes :
Le générateur lit un fichier de configuration et utilise les options configurées pour générer une build en remplaçant les markers dans les squelettes.
Le générateur peut être utilisé avec build_files.py. Un exemple de configuration du générateur est fourni sous la forme de sample_builder_config.config.sample.
skeletons sont les différents « squelettes » de code que le builder remplira dynamiquement pour générer une build complètement utilisable. skeletons contiennent des markers qui seront remplacés par des valeurs utilisables par le builder. Il existe trois « types » différents de squelettes :
Un marqueur peut être placé dans n’importe quel fichier d’un skeleton; il sera remplacé par une valeur spécifiée dans la config du générateur. En pratique, les marqueurs ne doivent jamais être utilisés pour écrire directement des variables, mais uniquement pour définir des valeurs. Si la valeur d’un marqueur doit être réutilisée, il convient de stocker la valeur dans une variable et d’y faire référence de cette manière, plutôt que de réutiliser le même marqueur.
Le format d’un marqueur est : ```[var:::identifier_for_the_marker]```
Les chaînes seront écrites dans un squelette directement entourées de guillemets simples, et les nombres seront écrits tels quels.
Si une chaîne de la configuration est entourée de guillemets doubles, la chaîne sera écrite directement dans le fichier entourée de guillemets doubles, et les guillemets simples qui l’entourent seront supprimés.
Cette relation peut être démontrée comme suit :
#################
# Skeleton Code #
#################
# Skeleton contains the following line of code:
foo = ```[var:::bar]```
##############
# End Result #
##############
# Stored in config as:
# foo = bar
# Written as:
foo = 'bar'
# Stored in config as:
# foo = "bar"
# Written as:
foo = "bar"
# Stored in config as:
# foo = 2
# Written as:
foo = 2
# Stored in config as:
# foo = "2"
# Written as:
foo = "2"
Les frameworks constituent l’application de base qui détermine quelles données sont utilisées par le transport et l’encoder, et comment ces données sont utilisées. Ce que fait réellement un framework spécifique importe peu, tant qu’il existe une logique pour importer et utiliser l’encoder et le transport. La plupart des parties essentielles d’un framework (principalement la logique client) seront stockées sous forme de skeleton, avec une interface pour interagir avec la partie serveur stockée en tant qu’objet framework de base.
Généralement, un framework contient un server et un client, et utilise des encoders et des transports pour relayer les données entre eux.
Il y a quelques principes fondamentaux à prendre en compte lors de la construction d’un framework :
framework est responsable de la mise à disposition de l’encoder pour que le transport puisse l’utiliser.framework utilise une relation client-serveur, les deux doivent être organisés en conséquence.client d’un framework. Si vous voulez que des éléments soient reconfigurables sur le client, celui-ci doit être capable de le faire au runtime sans interaction directe.skeleton de server, car l’utilisateur final va interagir directement avec le server d’un framework. Optez plutôt à la fois pour la lecture des options depuis une configuration et pour la possibilité donnée à l’utilisateur final de modifier les options (comme une temporisation de bloc ou la verbosité) au runtime.skeleton de framework sera traité par le générateur, qui itérera sur chaque fichier qu’il contient ; ainsi, si un argument donné doit être configurable au moment de la génération, cela peut être fait facilement.server de framework doit pouvoir être interfacé par un framework_manager commun.Le serveur est l’application qui sert d’intermédiaire entre le client et le c2 server. La logique du serveur est principalement statique. La logique du serveur pour le framework cobalt_strike, désigné comme third-party Client Controller dans la spécification, est présentée ci-dessous :
encodertransporttransportencodertransporttransportencoderUn server doit pouvoir gérer plusieurs clients (pas encore implémenté) et être interfacé par un framework_manager.
Le client est essentiellement la charge utile (payload) qui s’exécute sur le point de terminaison. La logique du client pour le framework cobalt_strike est principalement statique et présentée ci-dessous :
transporttransporttransport pour de nouvelles tâchestransportLe client utilise l’encoder et le transport spécifiés pour relayer les données entre lui-même et son server respectif.
Les encodeurs reçoivent des données, puis modifient ces données soit pour les préparer à être envoyées via le transport, soit pour décoder les données reçues via le transport et les ramener à leur forme brute afin qu’elles soient interprétées par le composant framework qui les utilise.
Les encodeurs doivent s’attendre à être interfacés directement par le transport et à traiter les données de manière indépendante du framework et du composant.