Engenharia

Detecção do Cobalt Strike com assinaturas de memória

No Elastic Security, abordamos o desafio da detecção de ameaças com vários métodos. Tradicionalmente, nos concentramos em modelos e comportamentos de machine learning. Esses dois métodos são poderosos porque podem detectar malware nunca antes visto. Historicamente, sentimos que as assinaturas são facilmente evitadas, mas também reconhecemos que a facilidade de evasão é apenas um dos muitos fatores a serem considerados. O desempenho e as taxas de falso positivo também são essenciais para mensurar a eficácia de uma técnica de detecção.

As assinaturas, embora incapazes de detectar malware desconhecido, têm taxas de falsos positivos próximas de zero e rótulos associados que ajudam a priorizar alertas. Por exemplo, um alerta para TrickBot ou REvil Ransomware requer ação mais imediata do que uma variante de adware potencialmente indesejada. Mesmo se pudéssemos, hipoteticamente, capturar apenas metade dos malwares conhecidos com assinaturas, isso ainda seria uma grande vitória quando usado em camadas com outras proteções, considerando os outros benefícios. Realisticamente, podemos fazer ainda melhor.

Um obstáculo para a criação de assinaturas que ofereçam um valor duradouro é o uso generalizado de compactadores e carregadores de malware descartáveis. Esses componentes evoluem rapidamente para evitar a detecção de assinaturas; no entanto, a carga útil final do malware é eventualmente descriptografada e executada na memória.

Para contornar a questão dos empacotadores e carregadores, podemos concentrar as estratégias de detecção de assinatura no conteúdo na memória. Isso efetivamente estende a vida útil da assinatura de dias para meses. Neste post, usaremos o Cobalt Strike como um exemplo para utilizar assinaturas na memória. 

Assinatura do Cobalt Strike

Cobalt Strike é um framework popular para conduzir operações de equipe vermelha e simulação de adversário. Presumivelmente, devido à sua facilidade de uso, estabilidade e recursos furtivos, também é uma ferramenta favorita para malfeitores com intenções ainda mais nefastas. Existem várias técnicas para detectar o Beacon, a carga útil do endpoint do Cobalt Strike. Isso inclui a procura de threads sem proteção e, mais recentemente, pipes nomeados integrados. No entanto, devido ao nível de configurabilidade no Beacon, geralmente há maneiras de escapar das estratégias públicas de detecção. Aqui, tentaremos usar assinaturas de memória como uma estratégia de detecção alternativa.

O Beacon é normalmente carregado refletivamente na memória e nunca toca o disco em uma forma diretamente assinável. Além disso, o Beacon pode ser configurado com uma variedade de opções de ofuscação na memória para ocultar sua carga útil. Por exemplo, a opção obfuscate-and-sleep tenta mascarar partes da carga útil do Beacon entre os retornos de chamada para evitar especificamente as varreduras de memória baseadas em assinatura. Teremos de considerar essa opção ao desenvolver assinaturas, mas ainda é fácil assinar o Beacon, mesmo com esses recursos furtivos avançados.

Observação em detalhes

Começaremos obtendo algumas cargas úteis do Beacon com a opção sleep_mask habilitada e desabilitada com as versões mais recentes (hashes na seção de referência). Começando com uma amostra com sleep_mask desabilitado, após a detonação, podemos localizar o Beacon na memória com o Process Hacker, procurando um thread que chame o SleepEx de uma região sem proteção:

A partir daí, podemos salvar a região da memória associada no disco para análise:

A vitória mais fácil seria escolher algumas strings exclusivas dessa região e usá-las como a nossa assinatura. Para demonstrar, escreveremos assinaturas com o yara, uma ferramenta padrão de mercado para esse propósito:

rule cobaltstrike_beacon_strings
{
meta:
    author = "Elastic"
    description = "Identifies strings used in Cobalt Strike Beacon DLL."
strings:
    $a = "%02d/%02d/%02d %02d:%02d:%02d"
    $b = "Started service %s on %s"
    $c = "%s as %s\\%s: %d"
condition:
    2 of them
}

Isso nos daria uma boa base de cobertura, mas podemos fazer melhor observando as amostras com sleep_mask habilitado. Se olharmos na memória onde o cabeçalho MZ/PE normalmente seria encontrado, agora vemos que ele está ofuscado:

Olhando rapidamente, podemos ver muitos bytes repetidos (0x80 neste caso) onde, na verdade, esperaríamos bytes nulos. Isso pode ser uma indicação de que o Beacon está usando uma ofuscação XOR simples de um byte. Para confirmar, podemos usar o CyberChef:

Como você pode ver, a string “This program cannot be run in DOS mode” (Este programa não pode ser executado no modo DOS) aparece após a decodificação, confirmando a nossa teoria. Como o XOR de um único byte é um dos truques mais manjados, o yara oferece suporte para detecção nativa com o modificador xor:

rule cobaltstrike_beacon_xor_strings
{
meta:
    author = "Elastic"
    description = "Identifies XOR'd strings used in Cobalt Strike Beacon DLL."
strings:
    $a = "%02d/%02d/%02d %02d:%02d:%02d" xor(0x01-0xff)
    $b = "Started service %s on %s" xor(0x01-0xff)
    $c = "%s as %s\\%s: %d" xor(0x01-0xff)
condition:
    2 of them
}

Podemos confirmar a detecção das nossas regras do yara até agora fornecendo um PID durante a varredura:

No entanto, ainda não terminamos. Após o teste dessa assinatura em uma amostra com a versão mais recente do Beacon (4.2 no momento em que este post foi escrito), a rotina de ofuscação foi aprimorada. A rotina pode ser localizada seguindo a stack de chamadas conforme mostrado anteriormente. Agora ela usa uma chave XOR de 13 bytes, conforme mostrado no seguinte snippet do IDA Pro:


Felizmente, a opção obfuscate-and-sleep do Beacon apenas ofusca strings e dados, deixando toda a seção de código pronta para assinatura. Há a questão sobre para qual função na seção de código devemos desenvolver uma assinatura, mas é relevante o suficiente para escrevermos um post do blog só sobre isso. Por enquanto, podemos apenas criar uma assinatura na rotina de desofuscação, que deve funcionar bem:

rule cobaltstrike_beacon_4_2_decrypt
{
meta:
    author = "Elastic"
    description = "Identifies deobfuscation routine used in Cobalt Strike Beacon DLL version 4.2."
strings:
    $a_x64 = {4C 8B 53 08 45 8B 0A 45 8B 5A 04 4D 8D 52 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4C 8B 03}
    $a_x86 = {8B 46 04 8B 08 8B 50 04 83 C0 08 89 55 08 89 45 0C 85 C9 75 04 85 D2 74 23 3B CA 73 E6 8B 06 8D 3C 08 33 D2}
condition:
     any of them
}

Podemos validar que podemos detectar o Beacon mesmo quando ele está em seu estado de hibernação furtivo (variantes de 32 e 64 bits):

Para integrar isso em uma detecção mais robusta, podemos verificar regularmente todos os processos do sistema (ou da empresa inteira). Isso pode ser feito com o seguinte one-liner do PowerShell:

powershell -command "Get-Process | ForEach-Object {c:\yara64.exe my_rules.yar $_.ID}"

Resumo

A detecção baseada em assinatura, embora muitas vezes desprezada, é uma estratégia de detecção valiosa, especialmente quando consideramos a varredura na memória. Com apenas um punhado de assinaturas, podemos detectar o Cobalt Strike independentemente da configuração ou dos recursos furtivos habilitados com uma taxa efetiva de falsos positivos igual a zero.

Hashes de referência

7d2c09a06d731a56bca7af2f5d3badef53624f025d77ababe6a14be28540a17a
277c2a0a18d7dc04993b6dc7ce873a086ab267391a9acbbc4a140e9c4658372a
A0788b85266fedd64dab834cb605a31b81fd11a3439dc3a6370bb34e512220e2
2db56e74f43b1a826beff9b577933135791ee44d8e66fa111b9b2af32948235c
3d65d80b1eb8626cf327c046db0c20ba4ed1b588b8c2f1286bc09b8f4da204f2

Saiba mais sobre o Elastic Security

Conheça os poderosos recursos de proteção, detecção e resposta do Elastic Agent. Comece a sua avaliação gratuita de 14 dias (não é necessário cartão de crédito) ou baixe os nossos produtos gratuitamente para a sua implantação no local. E aproveite o nosso treinamento Quick Start para se preparar para o sucesso.