
Beagle es una herramienta de respuesta a incidentes y forense digital que transforma registros de seguridad y datos en gráficos.
Beagle es una herramienta de respuesta a incidentes y forense digital que transforma fuentes de datos y registros en grafos. Las fuentes de datos compatibles incluyen Triajes de FireEye HX, archivos EVTX de Windows, registros de SysMon e imágenes de memoria RAM de Windows. Los grafos resultantes pueden enviarse a bases de datos de grafos como Neo4J o DGraph, o mantenerse localmente como objetos NetworkX de Python.
Beagle puede usarse directamente como biblioteca de Python, o a través de una interfaz web proporcionada.
La biblioteca puede utilizarse como una secuencia de llamadas funcionales desde una única fuente de datos.
>>> from beagle.datasources import SysmonEVTX
>>> graph = SysmonEVTX("malicious.evtx").to_graph()
>>> graph
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Como un grafo generado a partir de un conjunto de múltiples artefactos
>>> 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>
O llamando estrictamente a cada paso intermedio del proceso de fuente de datos a grafo.
>>> from beagle.backends import NetworkX
>>> from beagle.datasources import SysmonEVTX
>>> from beagle.transformers import SysmonTransformer
>>> datasource = SysmonEVTX("malicious.evtx")
# Los transformadores toman una fuente de datos y transforman cada evento
# en una tupla de uno o más nodos.
>>> transformer = SysmonTransformer(datasource=datasource)
>>> nodes = transformer.run()
# Los transformadores devuelven un array de nodos.
[
(<SysMonProc> process_guid="{0ad3e319-0c16-59c8-0000-0010d47d0000}"),
(<File> host="DESKTOP-2C3IQHO" full_path="C:\Windows\System32\services.exe"),
...
]
# Los backends toman los nodos y los transforman en grafos
>>> backend = NetworkX(nodes=nodes)
>>> G = backend.graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x126b887f0>
Los grafos se centran en la actividad de procesos individuales y están pensados principalmente para ayudar a los analistas a investigar la actividad en los hosts, no entre ellos.
Beagle está disponible como un archivo docker:
docker pull yampelo/beagle
mkdir -p data/beagle
docker run -v "$PWD/data/beagle":"/data/beagle" -p 8000:8000 yampelo/beagle
También está disponible como biblioteca. La documentación completa de la API está disponible en https://beagle-graphs.readthedocs.io
pip install pybeagle
Nota: Solo se admite Python 3.6+ actualmente.
Rekall no se instala automáticamente. Para instalar Rekall ejecute el siguiente comando en su lugar:
pip install pybeagle[rekall]
Cualquier entrada en el archivo de configuración se puede modificar usando variables de entorno que sigan el siguiente formato: BEAGLE__{SECTION}__{KEY}. Por ejemplo, para cambiar la clave API de VirusTotal utilizada al usar la imagen Docker, usaría el parámetro -e y establecería la variable BEAGLE__VIRUSTOTAL__API_KEY:
docker run -v "data/beagle":"/data/beagle" -p 8000:8000 -e "BEAGLE__VIRUSTOTAL__API_KEY=$API_KEY" beagle
Las variables de entorno y los directorios se pueden definir fácilmente usando docker compose
version: "3"
services:
beagle:
image: yampelo/beagle
volumes:
- /data/beagle:/data/beagle
ports:
- "8000:8000"
environment:
- BEAGLE__VIRUSTOTAL__API_KEY=$key$
La imagen Docker de Beagle viene con una interfaz web que envuelve el proceso de transformación de datos en grafos, así como su uso para investigar datos.
El formulario de carga envuelve el proceso de creación del grafo y utiliza automáticamente NetworkX como backend. Dependiendo de los parámetros requeridos por la fuente de datos, el formulario solicitará una carga de archivo o una entrada de texto. Por ejemplo:
Cualquier grafo creado se almacena localmente en la carpeta definida bajo la clave dir de la sección storage en la configuración. Esto se puede modificar estableciendo la variable de entorno BEAGLE__STORAGE__DIR.
Opcionalmente, se puede agregar un comentario a cualquier grafo para ayudar a describirlo mejor.
Cada fuente de datos extraerá automáticamente metadatos del parámetro proporcionado. Los metadatos y el comentario son visibles más adelante al ver los grafos existentes de la fuente de datos.
Al hacer clic en una fuente de datos en la barra lateral se renderiza una tabla con todos los grafos analizados para esa fuente de datos.
Ver un grafo en Beagle proporciona una interfaz web que permite a los analistas pivotar rápidamente alrededor de un incidente.
La interfaz se divide en dos partes principales: la parte izquierda contiene varias perspectivas del grafo (Grafo, Árbol, Tabla, etc.), y la parte derecha permite filtrar nodos y aristas por tipo, buscar nodos y expandir las propiedades de un nodo. También permite deshacer y rehacer operaciones realizadas en el grafo.
Cualquier elemento del grafo que tenga un divisor encima es plegable:
Los nodos en el grafo muestran los primeros 15 caracteres de un campo específico. Por ejemplo, para un nodo de proceso, será el nombre del proceso.
Las aristas simplemente muestran el tipo de arista.
Un clic único en un nodo o arista enfocará ese elemento y mostrará su información en el panel "Información del nodo" en la barra lateral derecha.
Un doble clic en un nodo traerá cualquier nodo vecino. Un nodo vecino es cualquier nodo conectado al nodo clickeado mediante una arista. Si no hay vecinos que traer, no se verá ningún cambio en el grafo.
Un clic único prolongado en un nodo lo ocultará del grafo, así como cualquier arista que dependa de él.
Al hacer clic derecho en un nodo aparece un menú contextual que permite ejecutar mutadores de grafos. Los mutadores son funciones que toman el estado del grafo y devuelven un nuevo estado.
Dos mutadores extremadamente útiles son:
El rastreo hacia atrás de un nodo es extremadamente útil y es similar a realizar un análisis de causa raíz de infección en archivos de registro.
Expandir los descendientes de un nodo permite ver inmediatamente todo lo que ocurrió debido a este nodo. Esta acción revela el subgrafo enraizado en el nodo seleccionado.
A veces, un nodo o arista puede no ser relevante para el incidente actual; se pueden alternar los tipos de aristas y nodos activándolos o desactivándolos. Tan pronto como se alterna el tipo, los nodos o aristas de ese tipo se eliminan del grafo visible.
Desactivar un tipo de nodo impide que ese tipo de nodo se use al usar mutadores o al traer vecinos.
¡Cualquier acción en el grafo es inmediatamente reversible! Usando los botones de deshacer/rehacer se puede revertir cualquier acción realizada. El botón de restablecer devuelve el grafo al estado en que se cargó, ahorrando una actualización.
A medida que se cambia el estado actual del grafo usando las acciones anteriores, también se puede querer ver el conjunto actual de nodos y aristas visibles desde una perspectiva diferente. Las pestañas en la parte superior de la pantalla del grafo permiten transformar los datos en una variedad de vistas:
Cada una de las perspectivas admite enfocar nodos haciendo clic en ellos.
El proceso de generación de grafos se puede realizar mediante programación usando la biblioteca Python. El proceso de generación de grafos consta de tres pasos:
DataSource analizan y generan eventos uno por uno.Transformer toman esas entradas y las transforman en varias clases Node como Process.Backend toman el array de nodos, los colocan en una estructura de grafo y los envían a una ubicación deseada.El paquete Python se puede instalar mediante pip:
pip install pybeagle
Crear un grafo requiere encadenar estos componentes. Esto se puede hacer automáticamente usando la función to_graph().
from beagle.datasources import HXTriage
# Por defecto, usar el método to_graph() utiliza NetworkX y el primer transformador.
G = HXTriage('test.mans').to_graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
También se puede hacer explícitamente en cada paso. Usando las llamadas funcionales, también se puede definir qué Backend se desea usar, por ejemplo, para enviar datos a DGraph
from beagle.datasources import HXTriage
from beagle.backends import DGraph
from beagle.transformers import FireEyeHXTransformer
# Los datos se enviarán a la instancia de DGraph configurada en el
# archivo de configuración
backend = HXTriage('test.mans').to_graph(backend=DGraph)
# También se puede especificar el transformador
backend = HXTriage('test.mans').to_transformer(transformer=FireEyeHXTransformer).to_graph(backend=DGraph)
Al llamar a los métodos to_graph o to_transformer, se pueden pasar argumentos a esas clases:
from beagle.datasources import HXTriage
from beagle.backends import Graphistry
# Enviar a Graphistry, anonimizar los datos primero y devolver la URL
graphistry_url = HXTriage('test.mans').to_graph(backend=Graphistry, anonymize=True, render=False)
También se puede invocar manualmente cada paso del proceso anterior, accediendo a las salidas intermedias
>>> 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 se desea llamar manualmente a cada paso, es necesario asegurarse de que la clase Transformer sea compatible con la salida de la clase DataSource proporcionada.
Cada fuente de datos define la lista de transformadores con los que es compatible, y se puede acceder a ella mediante el atributo .transformers:
>>> from beagle.datasources import HXTriage
>>> HXTriage.transformers
[beagle.transformers.fireeye_hx_transformer.FireEyeHXTransformer]
Por defecto, las aristas no se condensan, lo que significa que si un nodo de proceso u escribe en un nodo de archivo v 5000 veces, habrá 5000 aristas entre esos nodos. A veces, especialmente al intentar visualizar los datos, esto puede abrumar a un analista.
Se pueden condensar las 5000 aristas en una sola arista para ese tipo de acción (escritura en este caso), pasando al backend el parámetro consolidate_edges=True, por ejemplo:
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=False)
# El grafo contiene 826 nodos y 2469 aristas.
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=True)
# El grafo contiene 826 nodos y 1396 aristas
Por defecto, la interfaz web consolida las aristas.
Usar el backend NetworkX permite guardar el grafo resultante como un objeto JSON, guardándolo para más adelante. Se puede usar el método estático NetworkX.from_json para cargarlo de nuevo.
import json
from beagle.datasources import SysmonEVTX
from beagle.backends import NetworkX
# Obtener un objeto nx.MultiDiGraph
graph = SysmonEVTX("malicious.evtx").to_graph()
# Convertir el objeto a JSON
graph_json = NetworkX.graph_to_json(graph)
with open("my_graph.json", "w") as f:
json.dump(graph_json, f)
# Más adelante:
# G es el mismo objeto nx.MultiDiGraph generado por `.to_graph()`
G = NetworkX.from_json("my_graph.json")