
Um guia sobre como escrever regras YARA rápidas e com baixo consumo de memória
Escrever regras YARA eficientes é essencial para manter um desempenho de varredura rápido e preciso. Este guia fornece princípios-chave e melhores práticas para ajudar você a otimizar suas regras, reduzir computação desnecessária e evitar armadilhas comuns. Ele incorpora insights de especialistas do setor, incluindo Victor M. Alvarez, WXS, e contribuições da comunidade YARA.
Esta seção fornece um resumo conciso das melhores práticas de desempenho do YARA. Para explicações detalhadas e exemplos, consulte o guia completo abaixo.
"Pense no YARA como um processo de duas etapas: primeiro, buscar todos os padrões listados nas strings e, segundo, avaliar as condições. Você não pode usar condições bem formadas para compensar strings mal escolhidas."
— Wesley Shields
O YARA segue quatro etapas principais ao varrer um arquivo:
O YARA busca strings primeiro, tornando a seleção de strings o fator mais importante para a eficiência da regra.
✅ Melhores Práticas para Strings:
\x00\x00\x00\x00 aparece com frequência demais.nocase com cuidado – Ele gera exponencialmente mais variações de busca.O YARA avalia condições sequencialmente e para na primeira falha.
✅ Melhores Práticas para Condições:
filesize < X) antes de condições caras.for all i in (1..filesize) é ineficiente).@) em vez de regex para verificações de sequência.⚠ Nota: Condições com regex não usam curto-circuito e são sempre avaliadas por último.
Módulos como pe, elf ou magic precisam analisar o arquivo inteiro antes da avaliação, aumentando o tempo de varredura.
✅ Alternativas:
pe.is_pe, use uint16(0) == 0x5A4D para identificar arquivos PE.Correspondências excessivas tornam a varredura mais lenta e podem acionar erros de "too many matches".
✅ Corrigindo Correspondências Ineficientes:
.*, .+ ou {x,} sem limite superior.@herrcore criou um tutorial em vídeo útil cobrindo os tópicos discutidos neste guia de desempenho.
Introduction Into YARA - Writing Efficient YARA Rules
Para entender melhor o que e onde o desempenho do YARA pode ser otimizado, é útil compreender o processo de varredura. Ele é basicamente dividido em 4 etapas, que serão explicadas de forma bem simplificada usando esta regra de exemplo:
import "math"
rule example_php_webshell_rule
{
meta:
description = "Just an example php webshell rule"
date = "2021/02/16"
strings:
$php_tag = "<?php"
$input1 = "GET"
$input2 = "POST"
$payload = /assert[\t ]{0,100}\(/
condition:
filesize < 20KB and
$php_tag and
$payload and
any of ( $input* ) and
math.entropy(500, filesize-500) >= 5
}
Esta etapa ocorre antes da varredura em si. O YARA procura os chamados atoms nas strings de busca para alimentar o autômato Aho-Corasick. Os detalhes são explicados no capítulo átomo, mas por enquanto basta saber que eles têm no máximo 4 bytes e o YARA os escolhe de forma bastante inteligente para evitar muitas correspondências. Em nosso exemplo, o YARA pode escolher os seguintes 4 átomos:
<?phGETPOSTsser (de assert)Aqui a varredura começa. As etapas 2.-4. serão executadas em todos os arquivos. O YARA procurará em cada arquivo os 4 átomos definidos acima usando uma árvore de prefixos chamada autômato Aho-Corasick. Quaisquer correspondências são repassadas ao mecanismo de bytecode.
Se houver, por exemplo, uma correspondência em sser, o YARA verificará se ela foi precedida por um a e continua com um t. Se isso for verdade, ele prosseguirá com a regex [\t ]{0,100}\(. Com essa abordagem inteligente, o YARA evita percorrer os arquivos inteiros com um mecanismo de regex lento e apenas seleciona determinadas partes para examinar mais de perto.
Após toda a correspondência de padrões ser concluída, as condições são verificadas.
O YARA possui outro mecanismo de otimização para executar a verificação de math.entropy, que exige muita CPU, da nossa regra de exemplo somente se as 4 condições anteriores forem atendidas. Explicado em mais detalhes no capítulo Condições e Avaliação de Curto-Circuito
Se as condições forem atendidas, uma correspondência é relatada. A varredura continua com o próximo arquivo na etapa 2.
O YARA extrai das strings substrings curtas de até 4 bytes, chamadas de "átomos". Esses átomos podem ser extraídos de qualquer lugar dentro da string, e o YARA procura por esses átomos durante a varredura do arquivo; se encontrar um dos átomos, ele verifica se a string realmente corresponde.
Por exemplo, considere estas strings:
/abc.*cde/
=> os átomos possíveis são abc e cde, e qualquer um deles pode ser usado. O átomo abc é atualmente preferido porque ambos têm a mesma qualidade e ele é o primeiro dos dois.
/(one|two)three/
=> os átomos possíveis são one, two, thre e hree; podemos procurar por thre (ou hree) isoladamente, ou por one e two juntos. O átomo thre é preferido porque levará a menos correspondências potenciais do que one e two (que são mais curtos) e não contém e duplo (quanto mais única a letra, melhor).
O YARA faz o melhor esforço para selecionar os melhores átomos de cada string, por exemplo:
{ 00 00 00 00 [1-4] 01 02 03 04 }
=> aqui o YARA usa o átomo 01 02 03 04, porque 00 00 00 00 é comum demais
{ 01 02 [1-4] 01 02 03 04 }
=> 01 02 03 04 é preferido em relação a 01 02 porque é mais longo
Portanto, o ponto importante é que as strings devem conter bons átomos. Estas são strings ruins porque contêm átomos muito curtos ou comuns demais:
{00 00 00 00 [1-2] FF FF [1-2] 00 00 00 00}
{AB [1-2] 03 21 [1-2] 01 02}
/a.*b/
/a(c|d)/
As piores strings são aquelas que não contêm nenhum átomo, como:
/\w.*\d/
/[0-9]+\n/
Essa expressão regular não contém nenhuma substring fixa que possa ser usada como átomo, portanto ela deve ser avaliada em cada offset do arquivo para verificar se corresponde ali.