Análise de Configuração do Cobalt Strike Beacon
Visão Geral
O Cobalt Strike é uma ferramenta comercial de simulação de adversários amplamente abusada por atores de ameaças para operações de pós-exploração. Os payloads Beacon contêm dados de configuração embutidos que revelam endereços de servidores C2, protocolos de comunicação, intervalos de sleep, valores de jitter, configurações de perfil malleable C2, identificadores de watermark e chaves de criptografia. Extrair essa configuração de arquivos PE, shellcode ou dumps de memória é essencial para que respondedores de incidentes mapeiem a infraestrutura do atacante e atribuam campanhas. A configuração do beacon é codificada com XOR usando um único byte (0x69 para a versão 3, 0x2e para a versão 4) e armazenada em formato Tipo-Comprimento-Valor (TLV) dentro da seção .data.
Quando Usar
- Ao investigar incidentes de segurança que exigem análise da configuração de beacons Cobalt Strike
- Ao construir regras de detecção ou consultas de caça a ameaças para este domínio
- Quando analistas de SOC precisam de procedimentos estruturados para este tipo de análise
- Ao validar a cobertura de monitoramento de segurança para técnicas de ataque relacionadas
Pré-requisitos
- Python 3.9+ com
dissect.cobaltstrike,pefile,yara-python - CobaltStrikeParser da SentinelOne (
parse_beacon_config.py) - Editor hexadecimal (010 Editor, HxD) para inspeção manual
- Compreensão do formato de arquivo PE e codificação XOR
- Ferramentas de aquisição de dump de memória (Volatility3, WinDbg)
- Ferramentas de análise de rede (Wireshark) para correlação de tráfego C2
Conceitos Chave
Estrutura da Configuração do Beacon
Os beacons do Cobalt Strike armazenam sua configuração como um bloco de entradas TLV dentro da seção .data do PE. Beacons sem estágio codificam todo o código do beacon com uma chave de 4 bytes. O próprio bloco de configuração usa uma chave XOR de um único byte. Cada entrada TLV contém um identificador de tipo de 2 bytes (por exemplo, 0x0001 para BeaconType, 0x0008 para C2Server), um comprimento de 2 bytes e dados de comprimento variável.
Perfis Malleable C2
A configuração do beacon codifica o perfil malleable C2 que dita transformações de requisição/resposta HTTP, incluindo caminhos URI, cabeçalhos, codificação de metadados (Base64, NetBIOS) e transformações de dados. Analisar essas configurações revela como o beacon disfarça seu tráfego para se misturar com tráfego web legítimo.
Identificação de Watermark e Licença
Cada licença do Cobalt Strike incorpora um watermark único (inteiro de 4 bytes) nos beacons gerados. Extrair o watermark pode vincular vários beacons ao mesmo operador ou a uma licença quebrada. Bancos de dados de watermark conhecidos mantidos por provedores de inteligência de ameaças mapeiam watermarks para atores de ameaças específicos ou chaves de licença vazadas.
Fluxo de Trabalho
Passo 1: Extrair Configuração com CobaltStrikeParser
#!/usr/bin/env python3
"""Extrair configuração do beacon Cobalt Strike de PE ou dump de memória."""
import sys
import json
# Usando o CobaltStrikeParser da SentinelOne
# pip install dissect.cobaltstrike
from dissect.cobaltstrike.beacon import BeaconConfig
def extract_beacon_config(filepath):
"""Analisar configuração do beacon a partir do arquivo."""
configs = list(BeaconConfig.from_path(filepath))
if not configs:
print(f"[-] Nenhuma configuração de beacon encontrada em {filepath}")
return None
for i, config in enumerate(configs):
print(f"\n[+] Configuração do Beacon #{i+1}")
print(f"{'='*60}")
settings = config.as_dict()
# Campos críticos para resposta a incidentes
critical_fields = [
"SETTING_C2_REQUEST",
"SETTING_C2_RECOVER",
"SETTING_PUBKEY",
"SETTING_DOMAINS",
"SETTING_BEACONTYPE",
"SETTING_PORT",
"SETTING_SLEEPTIME",
"SETTING_JITTER",
"SETTING_MAXGET",
"SETTING_SPAWNTO_X86",
"SETTING_SPAWNTO_X64",
"SETTING_PIPENAME",
"SETTING_WATERMARK",
"SETTING_C2_VERB_GET",
"SETTING_C2_VERB_POST",
"SETTING_USERAGENT",
"SETTING_PROTOCOL",
]
for field in critical_fields:
value = settings.get(field, "N/A")
print(f" {field}: {value}")
return settings
return None
def extract_c2_indicators(config):
"""Extrair indicadores C2 acionáveis da configuração do beacon."""
indicators = {
"c2_domains": [],
"c2_ips": [],
"c2_urls": [],
"user_agent": "",
"named_pipes": [],
"spawn_processes": [],
"watermark": "",
}
if not config:
return indicators
# Extrair domínios C2
domains = config.get("SETTING_DOMAINS", "")
if domains:
for domain in str(domains).split(","):
domain = domain.strip().rstrip("/")
if domain:
indicators["c2_domains"].append(domain)
# Extrair user agent
indicators["user_agent"] = str(config.get("SETTING_USERAGENT", ""))
# Extrair named pipes
pipe = config.get("SETTING_PIPENAME", "")
if pipe:
indicators["named_pipes"].append(str(pipe))
# Extrair processos spawn-to
for arch in ["SETTING_SPAWNTO_X86", "SETTING_SPAWNTO_X64"]:
proc = config.get(arch, "")
if proc:
indicators["spawn_processes"].append(str(proc))
# Extrair watermark
indicators["watermark"] = str(config.get("SETTING_WATERMARK", ""))
return indicators
if __name__ == "__main__":
if len(sys.argv) < 2:
print(f"Uso: {sys.argv[0]} <arquivo_beacon_ou_dump>")
sys.exit(1)
config = extract_beacon_config(sys.argv[1])
if config:
indicators = extract_c2_indicators(config)
print(f"\n[+] Indicadores C2 Extraídos:")
print(json.dumps(indicators, indent=2))
Passo 2: Descriptografia Manual XOR da Configuração do Beacon
import struct
def find_and_decrypt_config(data):
"""Localizar e descriptografar manualmente a configuração do beacon."""
# Cobalt Strike 4.x usa 0x2e como chave XOR
xor_keys = [0x2e, 0x69] # v4, v3
for xor_key in xor_keys:
# Procurar pelos bytes mágicos da configuração após XOR
# A configuração começa com 0x0001 (BeaconType) XOR'd com a chave
magic = bytes([0x00 ^ xor_key, 0x01 ^ xor_key,
0x00 ^ xor_key, 0x02 ^ xor_key])
offset = data.find(magic)
if offset == -1:
continue
print(f"[+] Configuração encontrada no offset 0x{offset:x} (chave XOR: 0x{xor_key:02x})")
# Descriptografar o bloco de configuração (tipicamente 4096 bytes)
config_size = 4096
encrypted = data[offset:offset + config_size]
decrypted = bytes([b ^ xor_key for b in encrypted])
# Analisar entradas TLV
entries = parse_tlv(decrypted)
return entries
return None
def parse_tlv(data):
"""Analisar entradas de configuração Tipo-Comprimento-Valor."""
entries = {}
offset = 0
# Mapeamento de tipo de campo TLV
field_names = {
0x0001: "BeaconType",
0x0002: "Port",
0x0003: "SleepTime",
0x0004: "MaxGetSize",
0x0005: "Jitter",
0x0006: "MaxDNS",
0x0007: "Deprecated_PublicKey",
0x0008: "C2Server",
0x0009: "UserAgent",
0x000a: "PostURI",
0x000b: "Malleable_C2_Instructions",
0x000c: "Deprecated_HttpGet_Metadata",
0x000d: "SpawnTo_x86",
0x000e: "SpawnTo_x64",
0x000f: "CryptoScheme",
0x001a: "Watermark",
0x001d: "C2_HostHeader",
0x0024: "PipeName",
0x0025: "Year",
0x0026: "Month",
0x0027: "Day",
0x0036: "ProxyHostname",
}
while offset + 6 <= len(data):
entry_type = struct.unpack(">H", data[offset:offset+2])[0]
entry_len_type = struct.unpack(">H", data[offset+2:offset+4])[0]
entry_len = struct.unpack(">H", data[offset+4:offset+6])[0]
if entry_type == 0:
break
value_start = offset + 6
value_end = value_start + entry_len
value_data = data[value_start:value_end]
field_name = field_names.get(entry_type, f"Unknown_0x{entry_type:04x}")
if entry_len_type == 1: # Curto
value = struct.unpack(">H", value_data[:2])[0]
elif entry_len_type == 2: # Inteiro
value = struct.unpack(">I", value_data[:4])[0]
elif entry_len_type == 3: # String/Blob
value = value_data.rstrip(b'\x00').decode('utf-8', errors='replace')
else:
value = value_data.hex()
entries[field_name] = value
print(f" {field_name}: {value}")
offset = value_end
return entries
Passo 3: Regra YARA para Detecção de Beacon
import yara
cobalt_strike_rule = """
rule CobaltStrike_Beacon_Config {
meta:
description = "Detecta configuração de beacon Cobalt Strike"
author = "Equipe de Análise de Malware"
date = "2025-01-01"
strings:
// Marcador de configuração XOR'd para CS 4.x (chave 0x2e)
$config_v4 = { 2e 2f 2e 2c }
// Marcador de configuração XOR'd para CS 3.x (chave 0x69)
$config_v3 = { 69 68 69 6b }
// Strings comuns de beacon
$str_pipe = "\\\\.\\pipe\\" ascii wide
$str_beacon = "beacon" ascii nocase
$str_sleeptime = "sleeptime" ascii nocase
// Padrão de loader reflexivo
$reflective = { 4D 5A 41 52 55 48 89 E5 }
condition:
($config_v4 or $config_v3) or
(2 of ($str_*) and $reflective)
}
"""
def scan_for_beacons(filepath):
"""Escaneia arquivo com regras YARA para beacons Cobalt Strike."""
rules = yara.compile(source=cobalt_strike_rule)
matches = rules.match(filepath)
for match in matches:
print(f"[+] Correspondência YARA: {match.rule}")
for string_match in match.strings:
offset = string_match.instances[0].offset
print(f" String: {string_match.identifier} no offset 0x{offset:x}")
return matches
Passo 4: Correlação de Tráfego de Rede
from dissect.cobaltstrike.c2 import HttpC2Config
def analyze_c2_profile(beacon_config):
"""Analisar perfil malleable C2 a partir da configuração do beacon."""
print("\n[+] Análise do Perfil Malleable C2")
print("=" * 60)
# Configuração HTTP GET
get_verb = beacon_config.get("SETTING_C2_VERB_GET", "GET")
get_uri = beacon_config.get("SETTING_C2_REQUEST", "")
print(f"\n Requisição HTTP GET:")
print(f" Verbo: {get_verb}")
print(f" URI: {get_uri}")
# Configuração HTTP POST
post_verb = beacon_config.get("SETTING_C2_VERB_POST", "POST")
post_uri = beacon_config.get("SETTING_C2_POSTREQ", "")
print(f"\n Requisição HTTP POST:")
print(f" Verbo: {post_verb}")
print(f" URI: {post_uri}")
# User Agent
ua = beacon_config.get("SETTING_USERAGENT", "")
print(f"\n User-Agent: {ua}")
# Cabeçalho Host
host = beacon_config.get("SETTING_C2_HOSTHEADER", "")
print(f" Cabeçalho Host: {host}")
# Sleep e jitter para padrão de tráfego
sleep_ms = beacon_config.get("SETTING_SLEEPTIME", 60000)
jitter = beacon_config.get("SETTING_JITTER", 0)
print(f"\n Tempo de Sleep: {sleep_ms}ms")
print(f" Jitter: {jitter}%")
# Gerar assinaturas Suricata/Snort
print(f"\n[+] Assinaturas de Rede Sugeridas:")
if ua:
print(f' alert http any any -> any any (msg:\"CS Beacon UA\"; '
f'content:\"{ua}\"; http_user_agent; sid:1000001; rev:1;)')
if get_uri:
print(f' alert http any any -> any any (msg:\"CS Beacon URI\"; '
f'content:\"{get_uri}\"; http_uri; sid:1000002; rev:1;)')
Critérios de Validação
- Configuração do beacon extraída com sucesso de arquivo PE ou dump de memória
- Domínios/IPs de servidores C2 corretamente identificados com porta e protocolo
- Parâmetros do perfil malleable C2 decodificados mostrando transformações HTTP
- Valor do watermark extraído para correlação de atribuição
- Tempo de sleep e valores de jitter correspondentes aos intervalos de beacon observados na rede
- Regras YARA detectam beacon em amostras empacotadas e não empacotadas
- Assinaturas de rede geradas a partir do perfil C2 extraído