
Beagle è uno strumento di risposta agli incidenti e di digital forensics che trasforma log di sicurezza e dati in grafici.
Beagle è uno strumento di risposta agli incidenti e informatica forense che trasforma fonti di dati e log in grafici. Le fonti di dati supportate includono FireEye HX Triages, file EVTX di Windows, log di SysMon e immagini di memoria Raw di Windows. I grafici risultanti possono essere inviati a database di grafi come Neo4J o DGraph, oppure possono essere mantenuti localmente come oggetti Python NetworkX.
Beagle può essere utilizzato direttamente come libreria Python, oppure tramite un'interfaccia web fornita.
La libreria può essere utilizzata come una sequenza di chiamate funzionali da una singola fonte di dati.
>>> from beagle.datasources import SysmonEVTX
>>> graph = SysmonEVTX("malicious.evtx").to_graph()
>>> graph
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Come grafico generato da un insieme di molteplici artefatti
>>> 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>
Oppure chiamando rigorosamente ogni passaggio intermedio del processo dalla fonte di dati al grafico.
>>> from beagle.backends import NetworkX
>>> from beagle.datasources import SysmonEVTX
>>> from beagle.transformers import SysmonTransformer
>>> datasource = SysmonEVTX("malicious.evtx")
# I trasformatori prendono una fonte di dati e trasformano ogni evento
# in una tupla di uno o più nodi.
>>> transformer = SysmonTransformer(datasource=datasource)
>>> nodes = transformer.run()
# I trasformatori restituiscono un array di nodi.
[
(<SysMonProc> process_guid="{0ad3e319-0c16-59c8-0000-0010d47d0000}"),
(<File> host="DESKTOP-2C3IQHO" full_path="C:\Windows\System32\services.exe"),
...
]
# I backend prendono i nodi e li trasformano in grafici
>>> backend = NetworkX(nodes=nodes)
>>> G = backend.graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x126b887f0>
I grafici sono centrati sull'attività di singoli processi e sono pensati principalmente per aiutare gli analisti a investigare l'attività sugli host, non tra di essi.
Beagle è disponibile come file Docker:
docker pull yampelo/beagle
mkdir -p data/beagle
docker run -v "$PWD/data/beagle":"/data/beagle" -p 8000:8000 yampelo/beagle
È disponibile anche come libreria. La documentazione completa dell'API è disponibile su https://beagle-graphs.readthedocs.io
pip install pybeagle
Nota: Al momento è supportato solo Python 3.6+.
Rekall non viene installato automaticamente. Per installare Rekall esegui invece il seguente comando:
pip install pybeagle[rekall]
Qualsiasi voce nel file di configurazione può essere modificata utilizzando variabili d'ambiente che seguono il seguente formato: BEAGLE__{SEZIONE}__{CHIAVE}. Ad esempio, per modificare la chiave API VirusTotal utilizzata quando si usa l'immagine Docker, si usa il parametro -e e si imposta la variabile BEAGLE__VIRUSTOTAL__API_KEY:
docker run -v "data/beagle":"/data/beagle" -p 8000:8000 -e "BEAGLE__VIRUSTOTAL__API_KEY=$API_KEY" beagle
Le variabili d'ambiente e le directory possono essere facilmente definite usando docker compose
version: "3"
services:
beagle:
image: yampelo/beagle
volumes:
- /data/beagle:/data/beagle
ports:
- "8000:8000"
environment:
- BEAGLE__VIRUSTOTAL__API_KEY=$key$
L'immagine Docker di Beagle viene fornita con un'interfaccia web che incapsula sia il processo di trasformazione dei dati in grafici, sia il loro utilizzo per investigare i dati.
Il modulo di caricamento incapsula il processo di creazione del grafico e utilizza automaticamente NetworkX come backend. A seconda dei parametri richiesti dalla fonte di dati, il modulo richiederà il caricamento di un file o l'input di testo. Ad esempio:
Ogni grafico creato viene memorizzato localmente nella cartella definita sotto la chiave dir della sezione storage nella configurazione. Questo può essere modificato impostando la variabile d'ambiente BEAGLE__STORAGE__DIR.
Opzionalmente, è possibile aggiungere un commento a qualsiasi grafico per descriverlo meglio.
Ogni fonte di dati estrae automaticamente i metadati dal parametro fornito. I metadati e il commento sono visibili successivamente quando si visualizzano i grafici esistenti della fonte di dati.
Cliccando su una fonte di dati nella barra laterale viene visualizzata una tabella di tutti i grafici analizzati per quella fonte.
La visualizzazione di un grafico in Beagle fornisce un'interfaccia web che consente agli analisti di muoversi rapidamente all'interno di un incidente.
L'interfaccia è suddivisa in due parti principali: la parte sinistra contiene varie prospettive del grafico (Grafico, Albero, Tabella, ecc.), mentre la parte destra consente di filtrare nodi e archi per tipo, cercare nodi ed espandere le proprietà di un nodo. Permette inoltre di annullare e ripetere le operazioni eseguite sul grafico.
Qualsiasi elemento nel grafico che abbia un divisore sopra di esso è comprimibile:
I nodi nel grafico mostrano i primi 15 caratteri di un campo specifico. Ad esempio, per un nodo di processo, sarà il nome del processo.
Gli archi mostrano semplicemente il tipo di arco.
Un singolo clic su un nodo o un arco metterà a fuoco quell'elemento e ne mostrerà le informazioni nel pannello "Info Nodo" sulla barra laterale destra.
Un doppio clic su un nodo trascinerà eventuali nodi vicini. Un nodo vicino è qualsiasi nodo connesso al nodo cliccato da un arco. Se non ci sono vicini da trascinare, non si vedranno cambiamenti nel grafico.
Un lungo clic singolo su un nodo lo nasconderà dal grafico, così come tutti gli archi che dipendono da esso.
Cliccando con il tasto destro su un nodo appare un menu contestuale che consente di eseguire mutatori del grafico. I mutatori sono funzioni che prendono lo stato del grafico e restituiscono un nuovo stato.
Due mutatori estremamente utili sono:
Il backtracking di un nodo è estremamente utile e simile a trovare la causa principale dell'infezione nei file di log.
Espandere i discendenti di un nodo consente di visualizzare immediatamente tutto ciò che è accaduto a causa di questo nodo. Questa azione rivela il sottografo radicato nel nodo selezionato.
A volte, un nodo o un arco potrebbe non essere rilevante per l'incidente corrente; è possibile attivare e disattivare i tipi di arco e nodo. Non appena il tipo viene disattivato, i nodi o gli archi di quel tipo vengono rimossi dal grafico visibile.
Disattivare un tipo di nodo impedisce che quel tipo di nodo venga utilizzato quando si usano i mutatori o quando si trascinano i vicini.
Qualsiasi azione nel grafico è immediatamente reversibile! Usando i pulsanti Annulla/Ripeti è possibile annullare qualsiasi azione eseguita. Il pulsante Reset riporta lo stato del grafico a quando è stato caricato, risparmiando un aggiornamento della pagina.
Mentre si modifica lo stato corrente del grafico utilizzando le azioni sopra descritte, potrebbe essere utile visualizzare l'insieme attuale di nodi e archi visibili in una prospettiva diversa. Le schede nella parte superiore della schermata del grafico consentono di trasformare i dati in una varietà di viste:
Ciascuna delle prospettive supporta la messa a fuoco sui nodi cliccandoci sopra.
Il processo di generazione del grafico può essere eseguito programmaticamente utilizzando la libreria Python. Il processo di generazione del grafico è composto da tre fasi:
DataSource analizzano e producono eventi uno per uno.Transformer prendono quegli input e li trasformano in varie classi Node come Process.Backend prendono l'array di nodi, li inseriscono in una struttura di grafo e li inviano a una posizione desiderata.Il pacchetto Python può essere installato tramite pip:
pip install pybeagle
Creare un grafico richiede di concatenare questi passaggi. Questo può essere fatto automaticamente utilizzando la funzione to_graph().
from beagle.datasources import HXTriage
# Per impostazione predefinita, l'uso di to_graph() utilizza NetworkX e il primo trasformatore.
G = HXTriage('test.mans').to_graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Può anche essere fatto esplicitamente in ogni fase. Usando le chiamate funzionali, è possibile anche definire quale Backend si desidera utilizzare, ad esempio, per inviare dati a DGraph
from beagle.datasources import HXTriage
from beagle.backends import DGraph
from beagle.transformers import FireEyeHXTransformer
# I dati verranno inviati all'istanza DGraph configurata nel
# file di configurazione
backend = HXTriage('test.mans').to_graph(backend=DGraph)
# Si può anche specificare il trasformatore
backend = HXTriage('test.mans').to_transformer(transformer=FireEyeHXTransformer).to_graph(backend=DGraph)
Quando si chiamano i metodi to_graph o to_transformer, è possibile passare eventuali argomenti a quelle classi:
from beagle.datasources import HXTriage
from beagle.backends import Graphistry
# Invia a Graphistry, anonimizza prima i dati e restituisce l'URL
graphistry_url = HXTriage('test.mans').to_graph(backend=Graphistry, anonymize=True, render=False)
È anche possibile invocare manualmente ogni passaggio del processo sopra, accedendo agli output intermedi
>>> 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()
Se si desidera chiamare manualmente ogni passaggio, è necessario assicurarsi che l'istanza della classe Transformer sia compatibile con l'output della classe DataSource fornita.
Ogni fonte di dati definisce l'elenco dei trasformatori con cui è compatibile, accessibile tramite l'attributo .transformers:
>>> from beagle.datasources import HXTriage
>>> HXTriage.transformers
[beagle.transformers.fireeye_hx_transformer.FireEyeHXTransformer]
Per impostazione predefinita, gli archi non vengono condensati, il che significa che se un nodo di processo u scrive su un nodo file v 5000 volte, ci saranno 5000 archi tra quei nodi. A volte, soprattutto quando si cerca di visualizzare i dati, questo può sopraffare un analista.
È possibile condensare tutti i 5000 archi in un unico arco per quel tipo di azione (scrittura in questo caso), passando alla classe backend il parametro consolidate_edges=True, ad esempio:
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=False)
# Il grafico contiene 826 nodi e 2469 archi.
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=True)
# Il grafico contiene 826 nodi e 1396 archi
Per impostazione predefinita, l'interfaccia web consolida gli archi.
L'uso del backend NetworkX consente di salvare il grafico risultante come oggetto JSON, conservandolo per dopo. È possibile utilizzare il metodo statico NetworkX.from_json per ricaricarlo.
import json
from beagle.datasources import SysmonEVTX
from beagle.backends import NetworkX
# Ottieni un oggetto nx.MultiDiGraph
graph = SysmonEVTX("malicious.evtx").to_graph()
# Converti l'oggetto in JSON
graph_json = NetworkX.graph_to_json(graph)
with open("my_graph.json", "w") as f:
json.dump(graph_json, f)
# Più tardi:
# G è lo stesso oggetto nx.MultiDiGraph generato da `.to_graph()`
G = NetworkX.from_json("my_graph.json")