
Руководство по написанию быстрых и эффективных по памяти YARA-правил
Написание эффективных YARA-правил необходимо для поддержания быстрого и точного сканирования. Это руководство содержит ключевые принципы и лучшие практики, которые помогут вам оптимизировать правила, снизить ненужные вычисления и избежать типичных ошибок. Оно включает наработки экспертов индустрии, в том числе Виктора М. Альвареса (Victor M. Alvarez), WXS, а также вклад сообщества YARA.
В этом разделе приведено краткое резюме лучших практик производительности YARA. За подробными объяснениями и примерами обращайтесь к полному руководству ниже.
"Представляйте YARA как двухэтапный процесс: сначала поиск всех шаблонов, перечисленных в строках, а затем оценка условий. Нельзя компенсировать плохо подобранные строки хорошо составленными условиями."
— Уэсли Шилдс (Wesley Shields)
При сканировании файла YARA выполняет четыре основных шага:
YARA сначала ищет строки, поэтому выбор строк является самым важным фактором эффективности правила.
✅ Лучшие практики для строк:
\x00\x00\x00\x00 встречается слишком часто.nocase с осторожностью — это порождает экспоненциально больше вариантов поиска.YARA вычисляет условия последовательно и останавливается при первой неудаче.
✅ Лучшие практики для условий:
filesize < X) перед дорогостоящими условиями.for all i in (1..filesize) неэффективно).@) вместо регулярных выражений для проверки последовательностей.⚠ Примечание: Условия с регулярными выражениями не поддерживают сокращённые вычисления и всегда оцениваются последними.
Такие модули, как pe, elf или magic, должны разобрать весь файл перед оценкой, что увеличивает время сканирования.
✅ Альтернативы:
pe.is_pe используйте uint16(0) == 0x5A4D для определения PE-файлов.Чрезмерное количество совпадений замедляет сканирование и может вызывать ошибки "too many matches" (слишком много совпадений).
✅ Исправление неэффективных совпадений:
.*, .+ или {x,} без верхней границы.@herrcore создал полезный видеоурок, охватывающий темы, обсуждаемые в этом руководстве по производительности.
Introduction Into YARA - Writing Efficient YARA Rules
Чтобы лучше понять, что и где можно оптимизировать в производительности YARA, полезно разобраться в процессе сканирования. Он в основном разделён на 4 шага, которые будут объяснены очень упрощённо на примере этого правила:
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
}
Этот шаг выполняется до фактического сканирования. YARA ищет так называемые атомы в строках поиска, чтобы передать их автомату Ахо — Корасик. Подробности объяснены в главе атом, но пока достаточно знать, что их максимальная длина составляет 4 байта, и YARA выбирает их довольно умно, чтобы избежать слишком большого количества совпадений. В нашем примере YARA может выбрать следующие 4 атома:
<?phGETPOSTsser (из assert)Здесь сканирование началось. Шаги 2–4 выполняются для всех файлов. YARA ищет в каждом файле 4 атома, определённые выше, с помощью префиксного дерева, называемого автоматом Ахо — Корасик. Все совпадения передаются движку байт-кода.
Если, например, есть совпадение по sser, YARA проверит, предшествовала ли ему буква a, и продолжит с t. Если это так, он перейдёт к регулярному выражению [\t ]{0,100}\(. Благодаря такому умному подходу YARA не обрабатывает медленным регулярным выражением все файлы целиком, а выбирает только определённые части для более детального изучения.
После завершения сопоставления с шаблонами проверяются условия.
В YARA есть ещё один механизм оптимизации: ресурсоёмкая проверка math.entropy из нашего примера выполняется только в том случае, если выполнены 4 предыдущих условия. Подробнее это объясняется в главе Условия и сокращённые вычисления
Если условия выполнены, сообщается о совпадении. Сканирование продолжается со следующим файлом на шаге 2.
YARA извлекает из строк короткие подстроки длиной до 4 байт, которые называются "атомами". Эти атомы могут быть извлечены из любого места строки, и YARA ищет эти атомы при сканировании файла; если он находит один из атомов, то проверяет, действительно ли строка совпадает.
Например, рассмотрим такие строки:
/abc.*cde/
=> возможные атомы — abc и cde, можно использовать любой из них. В настоящее время предпочтителен атом abc, поскольку они имеют одинаковое качество, и это первый из двух.
/(one|two)three/
=> возможные атомы — one, two, thre и hree; можно искать только thre (или hree) либо оба one и two. Атом thre предпочтительнее, потому что он приведёт к меньшему числу потенциальных совпадений, чем one и two (они короче), и не содержит двойной e (чем уникальнее буква, тем лучше).
YARA прикладывает все усилия, чтобы выбрать лучшие атомы из каждой строки, например:
{ 00 00 00 00 [1-4] 01 02 03 04 }
=> здесь YARA использует атом 01 02 03 04, потому что 00 00 00 00 слишком распространён
{ 01 02 [1-4] 01 02 03 04 }
=> 01 02 03 04 предпочтительнее, чем 01 02, потому что он длиннее
Итак, важный момент: строки должны содержать хорошие атомы. Вот плохие строки, потому что они содержат либо слишком короткие, либо слишком распространённые атомы:
{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)/
Худшие строки — те, которые вообще не содержат атомов, например:
/\w.*\d/
/[0-9]+\n/
Эти регулярные выражения не содержат ни одной фиксированной подстроки, которую можно было бы использовать в качестве атома, поэтому их необходимо проверять на каждом смещении файла, чтобы увидеть, есть ли там совпадение.