
Beagle est un outil de réponse aux incidents et de criminalistique numérique qui transforme les journaux de sécurité et les données en graphiques.
Beagle est un outil de réponse aux incidents et de forensique numérique qui transforme des sources de données et des journaux en graphes. Les sources de données prises en charge incluent les triages FireEye HX, les fichiers EVTX Windows, les journaux SysMon et les images mémoire brutes Windows. Les graphes résultants peuvent être envoyés à des bases de données de graphes comme Neo4J ou DGraph, ou conservés localement en tant qu'objets Python NetworkX.
Beagle peut être utilisé directement comme bibliothèque Python, ou via une interface Web fournie.
La bibliothèque peut être utilisée soit comme une séquence d'appels fonctionnels à partir d'une seule source de données.
>>> from beagle.datasources import SysmonEVTX
>>> graph = SysmonEVTX("malicious.evtx").to_graph()
>>> graph
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Comme un graphe généré à partir d'un ensemble de plusieurs artefacts
>>> from beagle.datasources import SysmonEVTX, HXTriage, PCAP
>>> from beagle.backends import NetworkX
>>> nx = NetworkX.from_datasources(
datasources=[
SysmonEVTX("malicious.evtx"),
HXTriage("alert.mans"),
PCAP("traffic.pcap"),
]
)
>>> G = nx.graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Ou en appelant strictement chaque étape intermédiaire du processus de source de données vers graphe.
>>> from beagle.backends import NetworkX
>>> from beagle.datasources import SysmonEVTX
>>> from beagle.transformers import SysmonTransformer
>>> datasource = SysmonEVTX("malicious.evtx")
# Les transformateurs prennent une source de données et transforment chaque événement
# en un tuple d'un ou plusieurs nœuds.
>>> transformer = SysmonTransformer(datasource=datasource)
>>> nodes = transformer.run()
# Les transformateurs produisent un tableau de nœuds.
[
(<SysMonProc> process_guid="{0ad3e319-0c16-59c8-0000-0010d47d0000}"),
(<File> host="DESKTOP-2C3IQHO" full_path="C:\Windows\System32\services.exe"),
...
]
# Les backends prennent les nœuds et les transforment en graphes
>>> backend = NetworkX(nodes=nodes)
>>> G = backend.graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x126b887f0>
Les graphes sont centrés sur l'activité de processus individuels et sont principalement destinés à aider les analystes à enquêter sur l'activité des hôtes, et non entre eux.
Beagle est disponible en tant que fichier docker :
docker pull yampelo/beagle
mkdir -p data/beagle
docker run -v "$PWD/data/beagle":"/data/beagle" -p 8000:8000 yampelo/beagle
Il est également disponible en tant que bibliothèque. La documentation complète de l'API est disponible sur https://beagle-graphs.readthedocs.io
pip install pybeagle
Note : Seul Python 3.6+ est actuellement pris en charge.
Rekall n'est pas installé automatiquement. Pour installer Rekall, exécutez plutôt la commande suivante :
pip install pybeagle[rekall]
Toute entrée du fichier de configuration peut être modifiée à l'aide de variables d'environnement qui suivent le format suivant : BEAGLE__{SECTION}__{KEY}. Par exemple, pour modifier la clé API VirusTotal utilisée lors de l'utilisation de l'image Docker, vous utiliseriez le paramètre -e et définiriez la variable BEAGLE__VIRUSTOTAL__API_KEY :
docker run -v "data/beagle":"/data/beagle" -p 8000:8000 -e "BEAGLE__VIRUSTOTAL__API_KEY=$API_KEY" beagle
Les variables d'environnement et les répertoires peuvent être facilement définis à l'aide de docker compose
version: "3"
services:
beagle:
image: yampelo/beagle
volumes:
- /data/beagle:/data/beagle
ports:
- "8000:8000"
environment:
- BEAGLE__VIRUSTOTAL__API_KEY=$key$
L'image Docker de Beagle est livrée avec une interface Web qui encapsule à la fois le processus de transformation des données en graphes et leur utilisation pour enquêter sur les données.
Le formulaire de téléchargement encapsule le processus de création du graphe et utilise automatiquement NetworkX comme backend. Selon les paramètres requis par la source de données, le formulaire demandera soit un téléchargement de fichier, soit une saisie de texte. Par exemple :
Tout graphe créé est stocké localement dans le dossier défini sous la clé dir de la section storage de la configuration. Cela peut être modifié en définissant la variable d'environnement BEAGLE__STORAGE__DIR.
Optionnellement, un commentaire peut être ajouté à tout graphe pour mieux le décrire.
Chaque source de données extrait automatiquement les métadonnées du paramètre fourni. Les métadonnées et le commentaire sont visibles ultérieurement lors de la visualisation des graphes existants de la source de données.
Cliquer sur une source de données dans la barre latérale affiche un tableau de tous les graphes analysés pour cette source de données.
La visualisation d'un graphe dans Beagle fournit une interface Web qui permet aux analystes de naviguer rapidement autour d'un incident.
L'interface est divisée en deux parties principales : la partie gauche contient diverses perspectives du graphe (Graphe, Arbre, Tableau, etc.) et la partie droite permet de filtrer les nœuds et les arêtes par type, de rechercher des nœuds et de développer les propriétés d'un nœud. Elle permet également d'annuler et de rétablir les opérations effectuées sur le graphe.
Tout élément du graphe comportant un séparateur au-dessus est pliable :
Les nœuds du graphe affichent les 15 premiers caractères d'un champ spécifique. Par exemple, pour un nœud de processus, ce sera le nom du processus.
Les arêtes affichent simplement le type d'arête.
Un simple clic sur un nœud ou une arête concentre cet élément et affiche ses informations dans le panneau « Infos nœud » de la barre latérale droite.
Un double-clic sur un nœud fait apparaître tous les nœuds voisins. Un nœud voisin est tout nœud connecté au nœud cliqué par une arête. S'il n'y a aucun voisin à faire apparaître, aucun changement n'est visible dans le graphe.
Un long clic simple sur un nœud le masque du graphe, ainsi que toutes les arêtes qui en dépendent.
Un clic droit sur un nœud affiche un menu contextuel qui permet d'exécuter des mutateurs de graphe. Les mutateurs sont des fonctions qui prennent l'état du graphe et renvoient un nouvel état.
Deux mutateurs extrêmement utiles sont :
Revenir en arrière sur un nœud est extrêmement utile et s'apparente à une recherche de cause racine dans les fichiers journaux.
Développer les descendants d'un nœud permet de visualiser immédiatement tout ce qui s'est produit à cause de ce nœud. Cette action révèle le sous-graphe enraciné au nœud sélectionné.
Parfois, un nœud ou une arête peut ne pas être pertinent pour l'incident en cours. Vous pouvez activer ou désactiver les types d'arêtes et de nœuds. Dès que le type est désactivé, les nœuds ou arêtes de ce type sont supprimés du graphe visible.
La désactivation d'un type de nœud empêche ce type de nœud d'être utilisé lors de l'utilisation de mutateurs ou lors de l'attraction de voisins.
Toute action dans le graphe est immédiatement réversible ! Utilisez les boutons Annuler/Rétablir pour annuler toute action effectuée. Le bouton Réinitialiser remet le graphe dans l'état où il était lors du chargement, vous évitant ainsi un rafraîchissement.
Lorsque vous modifiez l'état actuel du graphe à l'aide des actions ci-dessus, vous pouvez également souhaiter visualiser l'ensemble actuel des nœuds et arêtes visibles sous une perspective différente. Les onglets en haut de l'écran du graphe vous permettent de transformer les données en plusieurs vues :
Chacune des perspectives permet de se concentrer sur les nœuds en cliquant dessus.
Le processus de génération de graphe peut être effectué par programmation à l'aide de la bibliothèque Python. Le processus de génération de graphe comprend trois étapes :
DataSource analysent et produisent les événements un par un.Transformer prennent ces entrées et les transforment en diverses classes Node telles que Process.Backend prennent le tableau de nœuds, les placent dans une structure de graphe et les envoient à un emplacement souhaité.Le paquet Python peut être installé via pip :
pip install pybeagle
La création d'un graphe nécessite de chaîner ces étapes. Cela peut être fait automatiquement en utilisant la fonction to_graph().
from beagle.datasources import HXTriage
# Par défaut, l'utilisation de la méthode to_graph() utilise NetworkX et le premier transformateur.
G = HXTriage('test.mans').to_graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Cela peut également être fait explicitement à chaque étape. En utilisant les appels fonctionnels, vous pouvez également définir le backend que vous souhaitez utiliser, par exemple pour envoyer des données à DGraph
from beagle.datasources import HXTriage
from beagle.backends import DGraph
from beagle.transformers import FireEyeHXTransformer
# Les données seront envoyées à l'instance DGraph configurée dans le
# fichier de configuration
backend = HXTriage('test.mans').to_graph(backend=DGraph)
# Vous pouvez également spécifier le transformateur
backend = HXTriage('test.mans').to_transformer(transformer=FireEyeHXTransformer).to_graph(backend=DGraph)
Lors de l'appel des méthodes to_graph ou to_transformer, vous pouvez transmettre n'importe quel argument à ces classes :
from beagle.datasources import HXTriage
from beagle.backends import Graphistry
# Envoyer à Graphistry, anonymiser d'abord les données, et retourner l'URL
graphistry_url = HXTriage('test.mans').to_graph(backend=Graphistry, anonymize=True, render=False)
Vous pouvez également invoquer manuellement chaque étape du processus ci-dessus, accédant ainsi aux sorties intermédiaires
>>> from beagle.backends import NetworkX
>>> from beagle.datasources import HXTriage
>>> from beagle.transformers import FireEyeHXTransformer
>>> datasource = HXTriage("test.mans")
>>> transformer = FireEyeHXTransformer(datasource=datasource)
>>> nodes = transformer.run()
>>> backend = NetworkX(nodes=nodes)
>>> G = backend.graph()
Si vous souhaitez appeler manuellement chaque étape, vous devez vous assurer que l'instance de la classe Transformer est compatible avec la sortie de la classe DataSource fournie.
Chaque source de données définit la liste des transformateurs avec lesquels elle est compatible, et celle-ci est accessible via l'attribut .transformers :
>>> from beagle.datasources import HXTriage
>>> HXTriage.transformers
[beagle.transformers.fireeye_hx_transformer.FireEyeHXTransformer]
Par défaut, les arêtes ne sont pas condensées, ce qui signifie que si un nœud de processus u écrit 5000 fois dans un nœud de fichier v, vous aurez 5000 arêtes entre ces nœuds. Parfois, surtout lors de la visualisation des données, cela peut submerger un analyste.
Vous pouvez condenser les 5000 arêtes en une seule arête pour ce type d'action (écriture dans ce cas) en passant le paramètre consolidate_edges=True à la classe du backend, par exemple :
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=False)
# Le graphe contient 826 nœuds et 2469 arêtes.
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=True)
# Le graphe contient 826 nœuds et 1396 arêtes.
Par défaut, l'interface Web consolide les arêtes.
L'utilisation du backend NetworkX vous permet de sauvegarder le graphe résultant sous forme d'objet JSON, pour le conserver ultérieurement. Vous pouvez utiliser la méthode statique NetworkX.from_json pour le recharger.
import json
from beagle.datasources import SysmonEVTX
from beagle.backends import NetworkX
# Récupérer un objet nx.MultiDiGraph
graph = SysmonEVTX("malicious.evtx").to_graph()
# Convertir l'objet en JSON
graph_json = NetworkX.graph_to_json(graph)
with open("my_graph.json", "w") as f:
json.dump(graph_json, f)
# Plus tard :
# G est le même objet nx.MultiDiGraph généré par `.to_graph()`
G = NetworkX.from_json("my_graph.json")