05 - YARA e Caracterização de Famílias de Malware
video: https://youtu.be/... no frontmatter da nota.YARA e Caracterização de Famílias de Malware
Nota 05 do capítulo 25, que fecha a esteira convertendo os achados das notas anteriores em regras de detecção e caracterização.
O foco aqui é a escrita profissional de regras YARA para identificar e classificar famílias, complementando a nota PRT.5 do framework CTH, que cobre implantação e ciclo de vida.
Dialoga com a triagem da nota 01, com a estática da nota 02 e com o ciclo de vida de IOCs do capítulo 03.
1. Contexto e Posicionamento na Biblioteca
YARA é a linguagem padrão da indústria para descrever padrões em arquivos e memória, e no contexto deste capítulo ela é o destino natural da esteira de análise: a triagem entregou tipo e packer, a estática entregou strings, seções e âncoras de bytes, a dinâmica entregou comportamento confirmado, e a reversão entregou o formato de configuração da família. A regra YARA é a síntese desses achados em um objeto testável, versionável e executável em dezenas de pontos, de gateways de e-mail a EDRs e sandboxes. Uma família de malware é caracterizada profissionalmente quando a organização consegue responder a duas perguntas com regras: isto é da família X, e isto é uma variante nova da família X.
A diferença entre esta nota e o PRT.5 do framework CTH é de profundidade versus operação. O PRT.5 cobre o repositório, o SLA de produção e os pontos de execução; aqui o foco é a engenharia da regra em si, da sintaxe aos módulos, das técnicas de especificidade à gestão do acervo. As duas notas compartilham princípios, e o leitor deve tratá-las como par: esta nota ensina a escrever a regra, o PRT.5 ensina a operá-la em escala.
2. Sintaxe Essencial: Regra, Strings e Condição
Uma regra YARA tem três blocos. O bloco meta carrega metadados de governança, o bloco strings declara padrões e o bloco condition define quando a regra casa. Os padrões aceitam três famílias: strings de texto com modificadores, sequências hexadecimais com curingas e expressões regulares. A tabela abaixo resume os elementos de sintaxe que concentram quase todo o uso prático.
| Elemento | Exemplo | Uso |
|---|---|---|
| String de texto | $ua = "Mozilla/5.0 Sombra" ascii | Marcadores textuais do binário |
| Modificadores | ascii wide nocase fullword | wide cobre UTF-16; fullword evita casar dentro de palavras |
| Hex com curingas | $magic = | Âncoras de bytes com bytes variáveis |
| Jump hex | $pad = | Intervalo de bytes entre âncoras fixas |
| Regex | $mtx = /Global\Sombra-Mtx-[0-9]{4}/ | Padrões textuais paramétricos |
| Condições de conjunto | 2 of ($s_*), any of them, none of them | Convergência de evidências sem enumerar |
| Atributos de arquivo | filesize < 3MB, uint16(0) == 0x5A4D | Filtragem barata de candidatos antes das strings |
A condição é o coração da especificidade, e a prática profissional exige convergência: um único marcador fraco casa com milhares de binários legítimos, enquanto a combinação de dois ou três marcadores independentes, escolhidos por serem raros no corpus limpo, reduz o falso positivo a níveis operáveis. A ancoragem no header PE com uint16(0) == 0x5A4D e no filesize deve vir sempre primeiro na condição, porque esses testes custam microssegundos e descartam a maior parte do volume antes que o motor gaste tempo com strings.
3. Módulos: pe, elf, math e hash
Os módulos estendem o YARA de busca de bytes para inspeção estrutural, e quatro deles concentram o uso em caracterização de malware. O módulo pe expõe a estrutura do executável Windows: número de seções, timestamp de compilação, tabela de imports e exports, características de seção e recursos. O módulo elf faz o equivalente para binários Linux, cobrindo type, machine, sections e entry point, essencial em campanhas híbridas que entregam loaders para os dois sistemas. O módulo math traz cálculo de entropia, percentual de bytes e funções matemáticas sobre intervalos do arquivo, e o módulo hash calcula md5, sha1 e sha256 sobre trechos delimitados, útil para verificar hashes de blobs embutidos sem extrair o blob.
As expressões abaixo ilustram os padrões de uso mais produtivos, todos aplicáveis à amostra de referência do capítulo.
pe.number_of_sections >= 5 and pe.sections[3].name == ".sxdata"
pe.imports("kernel32.dll", "VirtualAllocEx")
pe.timestamp == 1737000000
math.entropy(pe.sections[3].raw_data_offset, pe.sections[3].raw_data_size) > 7.5
hash.sha256(0x84000, 0x1F8) == "a3f9c2e17b8d4e05f6a1c9b2d8e7f405a6b3c2d1e0f9a8b7c6d5e4f3a2b1c0d9"
A entropia por seção via módulo math automatiza a leitura manual da nota 02: seções cifradas acima de 7,5 viram critério de regra em vez de observação de relatório. O hash de intervalo do módulo hash é particularmente valioso para payloads embutidos, pois a regra casa com a localização e o conteúdo do blob sem depender do container, e sobrevive a variações do loader externo. O cuidado obrigatório é de performance: entropia e hash sobre intervalos grandes custam tempo por arquivo, e devem vir depois dos filtros baratos na condição.
4. Regras Robustas: Convergência, Curingas e Estabilidade
A robustez de uma regra nasce de três decisões de design. A primeira é a escolha de marcadores estáveis, que sobrevivem a recompilações: o formato do bloco de configuração, o magic bytes do config, o padrão de mutex e o user-agent próprio da família mudam raramente, enquanto nomes de arquivo, IPs e strings de campanha mudam por build. A segunda é a convergência de evidências, tipicamente 2 of ($s_*) sobre um grupo de marcadores independentes, que tolera a ausência de um marcador quando o autor altera uma string. A terceira é o uso disciplinado de curingas e jumps na notação hex, que absorve variação local sem abrir mão das âncoras fixas.
$cfg_magic = { C6 5A [2] 90 1B } // ainda casa se dois bytes mudarem
$stack_url = { 68 ?? ?? ?? ?? E8 [6] C3 } // padrao de stack string em x86
$s_api1 = "LoadLibraryA" ascii
$s_api2 = "GetProcAddress" ascii
$s_mutex = "Sombra-Mtx" ascii nocase
condition:
$cfg_magic and $stack_url and any of ($s_*)
O equilíbrio entre flexibilidade e especificidade é a habilidade central do escritor de regras. Curingas em excesso produzem regras que casam com ferramentas legítimas que usam os mesmos padrões de código; âncoras em excesso produzem regras que só casam com a amostra de origem, problema de sobreajuste que detecta exatamente um arquivo e nada mais. O teste contra os dois lados, corpus malicioso da família e corpus limpo do parque, é o único medidor honesto desse equilíbrio, e nenhuma regra deve ser promovida sem os dois resultados registrados no meta.
5. Performance e Evitação de Falsos Positivos
Performance e precisão são o mesmo problema visto de dois lados, porque regras lentas saem de produção e regras imprecisas saturam a fila de triagem. As regras de ouro de performance estão listadas abaixo, e todas se verificam com o -p de timeout por regra e com o -w de avisos do próprio YARA.
- Filtrar por header e tamanho antes de qualquer string, na ordem da condição, pois o curto-circuito lógico evita trabalho inútil.
- Preferir strings literais a regex, e regex a laços explícitos, pois cada degrau da hierarquia custa ordens de magnitude de tempo.
- Evitar
for any i in (1..#a)com intervalos grandes e condições matemáticas pesadas sem necessidade. - Delimitar entropia e hash a seções ou blobs específicos, jamais ao arquivo inteiro em regras que rodam contra volumes de produção.
- Testar a regra completa com
yara -p 5para garantir que nenhuma regra individual exceda o orçamento de tempo por arquivo.
A evitação de falso positivo começa no design, com marcadores raros no corpus limpo, e se completa no teste sistemático: a regra roda contra o corpus interno de arquivos legítimos, contra instaladores empacotados, contra binários de sistema e contra a família conhecida. Os falsos positivos descobertos documentam-se no meta, com o padrão que causou o casamento indevido, e a regra é ajustada antes da promoção. Esse ciclo espelha o DDLC do capítulo 18, e a taxa de falso positivo por regra é métrica de primeira classe no repositório, não anedota.
6. Caracterização de Famílias via Regras
A caracterização de família organiza o repositório em três camadas complementares. A camada de família casa com o que é estável entre builds e campanhas, o magic do config, o padrão de mutex, a estrutura de seções, e responde à pergunta de identidade: isto é SombraStealer. A camada de variante usa marcadores de versão, o Build ID decifrado pela nota 04, a presença de campos novos na config, e responde à pergunta de série: isto é SombraStealer 2.4.1 ou posterior. A camada de capacidade casa com comportamentos embutidos no binário, keylogger, clipper, módulo de exfiltração, e responde à pergunta de risco independente de família: isto carrega capacidade X.
| Camada | Marcadores típicos | Pergunta respondida | Vida útil |
|---|---|---|---|
| Família | Magic do config, mutex, user-agent próprio | É da família? | Longa, sobrevive a builds |
| Variante | Build ID, campos novos, timestamp próximo | É da versão Y? | Média, gira por release |
| Capacidade | Padrões de APIs e rotinas | O que ela faz? | Longa, atravessa famílias |
Essa organização conecta diretamente com o retrohunt e o monitoramento do VirusTotal mencionados no PRT.5: a regra de família em retrohunt recupera o histórico completo da família na plataforma, inclusive amostras anteriores à campanha atual, e a regra de variante em monitoramento alerta quando o novo build da família aparece no ecossistema. As amostras recuperadas voltam à triagem da nota 01 como novos casos, e os configs extraídos pela nota 04 alimentam a série temporal de infraestrutura, fechando o ciclo entre detecção e inteligência.
7. Gestão de Repositório de Regras
O repositório de regras é ativo versionado, com estrutura de diretórios por camada e por estado, indexado por um arquivo que inclui os conjuntos válidos para cada ponto de execução. O fluxo de estados evolui a regra de rascunho a produção com critérios explícitos, e a aposentadoria é formal, com registro do motivo, porque regras de campanhas encerradas acumulando falso positivo anos depois são a falha mais comum de acervos sem gestão. O esqueleto abaixo ilustra a organização mínima.
repositorio-yara/
familias/
sombrastealer.yar
cordisloader.yar
variantes/
sombrastealer_241.yar
capacidades/
cap_injecao_processo.yar
cap_clipper.yar
teste/
corpus_malicioso/
corpus_limpo/
index.yar
A integração contínua valida cada commit com três verificações: sintaxe com yara -w ou com o YARA-X, o rewrite em Rust do próprio VirusTotal que reduz drasticamente o tempo de execução em grandes corpora; execução contra o corpus de teste, exigindo casamento nas amostras da família e zero casamento no corpus limpo; e conferência de metadados obrigatórios, autor, data, origem, confiança e estado. A ferramenta yarGen auxilia a bootstrap de regras a partir de amostras, sugerindo strings raras automaticamente, e o resultado deve ser tratado como rascunho a revisar, jamais como regra pronta, porque heurísticas automáticas não substituem a escolha de marcadores estáveis da seção 4.
8. Exemplo: Regra Completa Comentada para a Família SombraStealer
A regra abaixo consolida o caso fictício do capítulo, combinando a âncora do bloco de configuração vista na nota 02, o mutex confirmado na nota 03, a estrutura de seções e a entropia da seção cifrada, com cada linha comentada justificando a decisão de design.
import "pe"
import "math"
/*
Familia: SombraStealer (stealer ficticio do caso LDR-2026-0142)
Alcance: loaders da familia com bloco de config na forma padrao.
Origem: notas 02 e 04 do capitulo 25; retrohunt correlacionou 11 amostras.
Estado: producao; FP zero em corpus limpo de 250 mil arquivos.
*/
rule SombraStealer_Familia
{
meta:
author = "Equipe de CTI"
date = "2026-01-21"
description = "Familia SombraStealer: magic de config, mutex e secao cifrada"
family = "SombraStealer"
confidence = "alta"
status = "producao"
tlp = "amber-strict"
fp_notes = "Instaladores UPX legítimos não casam: exigem magic de config"
strings:
// Ancora estavel: magic de 6 bytes no inicio do bloco de config,
// presente em todos os builds 2.x conhecidos (nota 04)
$cfg_magic = { C6 5A ?? ?? 90 1B }
// Identidade da familia: prefixo do mutex confirmado em runtime (nota 03)
$mutex = "Sombra-Mtx-" ascii
// User-agent proprio do builder, estavel entre campanhas (nota 02)
$ua = "Sombra/2." ascii
// Estrutura de stack string usada pela rotina de config (nota 04)
$stack_cfg = { 68 ?? ?? ?? ?? E8 [5-8] 83 C4 04 C3 }
condition:
// Filtros baratos primeiro: executavel PE de tamanho plausivel
uint16(0) == 0x5A4D and filesize < 3MB and
// Assinatura estrutural: secao nao padrao com alta entropia,
// padrao recorrente nos builds analisados
pe.number_of_sections >= 4 and
math.entropy(pe.sections[pe.number_of_sections - 2].raw_data_offset,
pe.sections[pe.number_of_sections - 2].raw_data_size) > 7.5 and
// Convergencia de evidencias: config + qualquer identidade textual
$cfg_magic and 1 of ($mutex, $ua, $stack_cfg)
}
Três detalhes da regra merecem leitura atenta. O limiar 1 of sobre as identidades textuais mantém a detecção quando o autor troca o mutex, porque o magic de config e a estrutura de stack string permanecem; a entropia é calculada apenas sobre a penúltima seção, delimitada e barata, em vez do arquivo inteiro; e o campo fp_notes registra a validação contra instaladores UPX, transformando o teste de falsos positivos em documentação permanente. A variante 2.4.1 teria regra própria na camada de variantes, ancorada no Build ID decifrado pela nota 04, herdando a condição da regra de família.
9. Erros Comuns e Pontos de Atenção
O erro fundacional é escrever contra a amostra de origem e validar só contra ela, produzindo a regra que casa com um único arquivo e nada mais. O erro simétrico é escrever genérico demais, casando com instaladores, jogos e binários de sistema, e desacreditar o canal de alertas na primeira semana. Ambos se resolvem com o mesmo remédio: corpus de teste dos dois lados, manutenção obrigatória e revisão periódica com aposentadoria formal, conforme a gestão da seção 7.
Completam a lista os itens abaixo.
- Esquecer o modificador
widee perder strings UTF-16, metade do texto em binários Windows, produzindo regra cega a marcadores óbvios. - Usar entropia do arquivo inteiro como condição principal, critério lento e impreciso que casa com compactadores legítimos em volume.
- Deixar regex ampla e sem âncoras na condição, transformando a regra no gargalo do scan de produção.
- Nomear strings e regras sem convenção, impossibilitando a leitura de condições como
2 of ($s_*)por outros analistas. - Promover regra direto ao scan crítico sem estado de teste, descobrindo falso positivo em alerta real de produção.
- Não alimentar o retrohunt com a regra de família aprovada, desperdiçando o retorno histórico que vincularia campanhas antigas.
- Duplicar regras em diretórios paralelos sem retirar a anterior, gerando alertas em dobro e métricas contaminadas.
10. Integração com o Restante da Biblioteca
A regra de família é a tradução operacional de tudo que o capítulo produziu, e sua implantação em produção segue o PRT.5, com repositório versionado, SLA e pontos de execução no PRT.1, PRT.3 e PRT.4. O capítulo 18 recebe as regras como detection as code, com CI validando sintaxe e corpus a cada commit, e o SIM.4 usa as amostras da família como material de teste de detecção de IOCs, garantindo que a regra dispara onde e quando deve.
No ciclo de inteligência, o retrohunt com a regra de família alimenta o CTI.4 com amostras históricas que voltam à triagem da nota 01, e os configs extraídos pela nota 04 sustentam o agrupamento de campanhas e a análise de infraestrutura do capítulo 05. A caça orientada a IOC do TH.10 recebe o repositório como fonte de padrões de bytes para varredura de memória e de compartilhamentos. O capítulo fecha com o princípio que atravessa as cinco notas: cada camada da análise de artefatos existe para alimentar a seguinte, e o YARA é o ponto onde a análise deixa de ser caso individual e se torna capacidade permanente da organização.