
Beagle é uma ferramenta de resposta a incidentes e forense digital que transforma logs de segurança e dados em gráficos.
O Beagle é uma ferramenta de resposta a incidentes e perícia digital que transforma fontes de dados e logs em gráficos. As fontes de dados suportadas incluem Triagens FireEye HX, arquivos EVTX do Windows, logs do SysMon e imagens brutas de memória do Windows. Os gráficos resultantes podem ser enviados para bancos de dados de grafos como Neo4J ou DGraph, ou mantidos localmente como objetos NetworkX do Python.
O Beagle pode ser usado diretamente como uma biblioteca Python ou através de uma interface web fornecida.
A biblioteca pode ser usada como uma sequência de chamadas funcionais a partir de uma única fonte de dados.
>>> from beagle.datasources import SysmonEVTX
>>> graph = SysmonEVTX("malicious.evtx").to_graph()
>>> graph
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Ou como um gráfico gerado a partir de um conjunto de múltiplos artefatos.
>>> 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 chamando estritamente cada etapa intermediária do processo de fonte de dados para gráfico.
>>> from beagle.backends import NetworkX
>>> from beagle.datasources import SysmonEVTX
>>> from beagle.transformers import SysmonTransformer
>>> datasource = SysmonEVTX("malicious.evtx")
# Transformers recebem uma fonte de dados e transformam cada evento
# em uma tupla de um ou mais nós.
>>> transformer = SysmonTransformer(datasource=datasource)
>>> nodes = transformer.run()
# Transformers produzem uma matriz de nós.
[
(<SysMonProc> process_guid="{0ad3e319-0c16-59c8-0000-0010d47d0000}"),
(<File> host="DESKTOP-2C3IQHO" full_path="C:\Windows\System32\services.exe"),
...
]
# Backends recebem os nós e os transformam em gráficos
>>> backend = NetworkX(nodes=nodes)
>>> G = backend.graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x126b887f0>
Os gráficos são centrados na atividade de processos individuais e têm como objetivo principal ajudar analistas a investigar atividades em hosts, não entre eles.
O Beagle está disponível como um arquivo docker:
docker pull yampelo/beagle
mkdir -p data/beagle
docker run -v "$PWD/data/beagle":"/data/beagle" -p 8000:8000 yampelo/beagle
Também está disponível como biblioteca. A documentação completa da API está disponível em https://beagle-graphs.readthedocs.io
pip install pybeagle
Nota: Apenas Python 3.6+ é atualmente suportado.
O Rekall não é instalado automaticamente. Para instalar o Rekall, execute o seguinte comando:
pip install pybeagle[rekall]
Qualquer entrada no arquivo de configuração pode ser modificada usando variáveis de ambiente que seguem o seguinte formato: BEAGLE__{SECTION}__{KEY}. Por exemplo, para alterar a chave de API do VirusTotal usada ao usar a imagem Docker, você usaria o parâmetro -e e definiria a variável BEAGLE__VIRUSTOTAL__API_KEY:
docker run -v "data/beagle":"/data/beagle" -p 8000:8000 -e "BEAGLE__VIRUSTOTAL__API_KEY=$API_KEY" beagle
Variáveis de ambiente e diretórios podem ser facilmente definidos usando docker compose
version: "3"
services:
beagle:
image: yampelo/beagle
volumes:
- /data/beagle:/data/beagle
ports:
- "8000:8000"
environment:
- BEAGLE__VIRUSTOTAL__API_KEY=$key$
A imagem Docker do Beagle vem com uma interface web que envolve o processo de transformação de dados em gráficos, bem como seu uso para investigar dados.
O formulário de upload envolve o processo de criação do gráfico e usa automaticamente o NetworkX como backend. Dependendo dos parâmetros exigidos pela fonte de dados, o formulário solicitará o upload de um arquivo ou entrada de texto. Por exemplo:
Qualquer gráfico criado é armazenado localmente na pasta definida na chave dir da seção storage na configuração. Isso pode ser modificado definindo a variável de ambiente BEAGLE__STORAGE__DIR.
Opcionalmente, um comentário pode ser adicionado a qualquer gráfico para ajudar a descrevê-lo melhor.
Cada fonte de dados extrairá automaticamente metadados do parâmetro fornecido. Os metadados e comentários ficarão visíveis posteriormente ao visualizar os gráficos existentes da fonte de dados.
Clicar em uma fonte de dados na barra lateral renderiza uma tabela de todos os gráficos analisados para essa fonte de dados.
Visualizar um gráfico no Beagle fornece uma interface web que permite aos analistas pivotar rapidamente em torno de um incidente.
A interface é dividida em duas partes principais: a parte esquerda, que contém várias perspectivas do gráfico (Gráfico, Árvore, Tabela, etc.), e a parte direita, que permite filtrar nós e arestas por tipo, pesquisar nós e expandir as propriedades de um nó. Também permite desfazer e refazer operações realizadas no gráfico.
Qualquer elemento no gráfico que tenha um divisor acima dele é recolhível:
Os nós no gráfico exibem os primeiros 15 caracteres de um campo específico. Por exemplo, para um nó de processo, será o nome do processo.
As arestas simplesmente mostram o tipo de aresta.
Um clique simples em um nó ou aresta focará esse elemento e exibirá suas informações no painel "Node Info" na barra lateral direita.
Um duplo clique em um nó puxará quaisquer nós vizinhos. Um nó vizinho é qualquer nó conectado ao nó clicado por uma aresta. Se não houver vizinhos a serem puxados, nenhuma alteração será vista no gráfico.
Um clique simples longo em um nó o ocultará do gráfico, bem como quaisquer arestas que dependam dele.
Clicar com o botão direito em um nó exibe um menu de contexto que permite executar mutadores de gráfico. Mutadores são funções que recebem o estado do gráfico e retornam um novo estado.
Dois mutadores extremamente úteis são:
Rastrear um nó é extremamente útil e é semelhante a fazer uma análise de causa raiz em arquivos de log.
Expandir os descendentes de um nó permite visualizar imediatamente tudo o que aconteceu devido a este nó. Essa ação revela o subgrafo enraizado no nó selecionado.
Às vezes, um nó ou aresta pode não ser relevante para o incidente atual. Você pode alternar tipos de arestas e nós ligando e desligando. Assim que o tipo é alternado, os nós ou arestas desse tipo são removidos do gráfico visível.
Alternar um tipo de nó para desligado impede que esse tipo de nó seja usado ao usar mutadores ou ao puxar vizinhos.
Qualquer ação no gráfico é imediatamente reversível! Usando os botões desfazer/refazer, você pode reverter qualquer ação realizada. O botão de reiniciar redefine o estado do gráfico para quando foi carregado, poupando um refresh.
À medida que você altera o estado atual do gráfico usando as ações acima, você também pode querer visualizar o conjunto atual de nós e arestas visíveis em uma perspectiva diferente. As abas no topo da tela do gráfico permitem transformar os dados em várias visualizações:
Cada uma das perspectivas suporta focar em nós clicando neles.
O processo de geração de gráficos pode ser realizado programaticamente usando a biblioteca Python. O processo de geração de gráficos é composto por três etapas:
DataSource analisam e geram eventos um a um.Transformer recebem essas entradas e as transformam em várias classes Node, como Process.Backend recebem a matriz de nós, os colocam em uma estrutura de grafo e os enviam para um destino desejado.O pacote Python pode ser instalado via pip:
pip install pybeagle
Criar um gráfico requer encadear esses componentes. Isso pode ser feito para você usando a função to_graph().
from beagle.datasources import HXTriage
# Por padrão, usar a classe to_graph() usa NetworkX e o primeiro transformer.
G = HXTriage('test.mans').to_graph()
<networkx.classes.multidigraph.MultiDiGraph at 0x12700ee10>
Também pode ser feito explicitamente em cada etapa. Usando as chamadas funcionais, você também pode definir qual Backend deseja usar, por exemplo, para enviar dados para o DGraph.
from beagle.datasources import HXTriage
from beagle.backends import DGraph
from beagle.transformers import FireEyeHXTransformer
# Os dados serão enviados para a instância DGraph configurada no
# arquivo de configuração
backend = HXTriage('test.mans').to_graph(backend=DGraph)
# Também pode especificar o transformer
backend = HXTriage('test.mans').to_transformer(transformer=FireEyeHXTransformer).to_graph(backend=DGraph)
Ao chamar os métodos to_graph ou to_transformer, você pode passar quaisquer argumentos para essas classes:
from beagle.datasources import HXTriage
from beagle.backends import Graphistry
# Enviar para o Graphistry, anonimizar os dados primeiro e retornar a URL
graphistry_url = HXTriage('test.mans').to_graph(backend=Graphistry, anonymize=True, render=False)
Você também pode invocar manualmente cada etapa do processo acima, acessando as saídas intermediárias.
>>> 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 você quiser chamar cada etapa manualmente, precisará garantir que a instância da classe Transformer seja compatível com a saída da classe DataSource fornecida.
Cada fonte de dados define a lista de transformers com os quais é compatível, e isso pode ser acessado através do atributo .transformers:
>>> from beagle.datasources import HXTriage
>>> HXTriage.transformers
[beagle.transformers.fireeye_hx_transformer.FireEyeHXTransformer]
Por padrão, as arestas não são condensadas, o que significa que se um nó de processo u escreve em um nó de arquivo v 5000 vezes, você terá 5000 arestas entre esses nós. Às vezes, especialmente ao tentar visualizar os dados, isso pode sobrecarregar um analista.
Você pode condensar todas as 5000 arestas em uma única aresta para esse tipo de ação (escrita, neste caso), passando o parâmetro consolidate_edges=True para a classe do backend, por exemplo:
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=False)
# O gráfico contém 826 nós e 2469 arestas.
SysmonEVTX("data/sysmon/autoruns-sysmon.evtx").to_graph(NetworkX, consolidate_edges=True)
# O gráfico contém 826 nós e 1396 arestas
Por padrão, a interface web consolida as arestas.
Usar o backend NetworkX permite salvar o gráfico resultante como um objeto JSON, salvando-o para uso posterior. Você pode usar o método estático NetworkX.from_json para carregá-lo de volta.
import json
from beagle.datasources import SysmonEVTX
from beagle.backends import NetworkX
# Obter um objeto nx.MultiDiGraph
graph = SysmonEVTX("malicious.evtx").to_graph()
# Converter o objeto para JSON
graph_json = NetworkX.graph_to_json(graph)
with open("my_graph.json", "w") as f:
json.dump(graph_json, f)
# Mais tarde:
# G é o mesmo objeto nx.MultiDiGraph gerado por `.to_graph()`
G = NetworkX.from_json("my_graph.json")