2. Написание правил YARA

Правила YARA достаточно просто писать. Синтаксис правил напоминает язык программирования C. Вот самое простое правило, которое можно написать для YARA и которое абсолютно ничего не делает:

rule dummy
{
        condition:
                false
}

Каждое правило в YARA начинается с ключевого слова rule, за которым следует идентификатор правила. Идентификаторы должны соответствовать лексическим соглашениям языка программирования C, они могут содержать любые буквенно-цифровые символы и символы подчеркивания, но при этом первый символ не может быть цифровым. Идентификаторы правил чувствительны к регистру и не могут превышать длину в 128 символов. Следующие ключевые слова зарезервированы и не могут использоваться в качестве идентификатора:

all, and, any, ascii, at, condition, contains, entrypoint, false, filesize, fullword, for, global, in, import, include, int8, int16, int32, int8be, int16be, int32be, matches, meta, nocase, not, or, of private, rule, strings, them, true, uint8, uint16 uint32, uint8be, uint16be, uint32be, wide, xor

Правила обычно состоят из двух разделов: определение строк (strings) и условие (condition). Раздел trings может быть опущен, если правило не зависит от какой-либо строки, раздел condition должен присутствовать в любом правиле. В разделе strings определяются строки, которые будут частью правила. Каждая строка имеет идентификатор, состоящий из символа $, за которым следует последовательность буквенно-цифровых символов и символов подчеркивания, эти идентификаторы могут использоваться в разделе condition для ссылки на соответствующую строку. Строки могут быть определены в текстовой или шестнадцатеричной форме, как показано в следующем примере:

rule ExampleRule
{
        strings:
                $my_text_string = "text here"
                $my_hex_string = {E2 34 A1 C8 23 FB}
        condition:
                $my_text_string or $my_hex_string
}

Текстовые строки заключаются в двойные кавычки, как и в языке C. Шестнадцатеричные строки заключены в фигурные скобки, и они состоят из последовательности шестнадцатеричных чисел, которые могут быть записаны непрерывно или разделяться пробелами.

В шестнадцатеричных строках не допускается использование десятичных чисел.

В разделе condition содержится логика правила. Этот раздел должен содержать логическое выражение, которое показывает, при каких обстоятельствах файл или процесс удовлетворяет правилу или нет. Как правило, условие будет ссылаться на ранее определенные строки с помощью их идентификаторов. В этом контексте идентификатор строки действует как логическая переменная, которая вычисляется как true, если строка была найдена в памяти файла или процесса, или false, в противном случае.

2.1. Комментарии

Вы можете добавлять комментарии к своим правилам YARA так же, как если бы это был исходный файл C, как однострочный, так и многострочный стиль C комментарии поддерживаются.

/*
        это многострочный комментарий ...
*/
rule CommentExample // ... это однострочный комментарийt
{
        condition:
                false // просто фиктивное правило
}

2.2. Строки

В YARA применяется три типа строк: шестнадцатеричные строки, текстовые строки и регулярные выражения. Шестнадцатеричные строки используются для определения последовательности байтов, в то время как текстовые строки и регулярные выражения используются для определения части текста, содержащегося в файле или процессе. При этом текстовые строки и регулярные выражения могут также использоваться для представления байтов посредством escape-последовательностей, как будет показано ниже.

2.2.1. Шестнадцатеричные строки

Шестнадцатеричные строки допускают три специальные конструкции, которые делают их более гибкими: подстановочные знаки, переходы и альтернативы. Подстановочные знаки – это просто заполнители, которые вы можете поместить в строку, указывающую, что некоторые байты неизвестны, и они должны соответствовать чему-либо. Символ-заполнитель – знак вопроса (?). Вот пример шестнадцатеричной строки с подстановочными знаками:

rule WildcardExample
{
        strings:
                $hex_string = {E2 34 ?? C8 A? FB}
        condition:
                $hex_string
}

Как показано в примере, подстановочным знаком можно определить не только байт целиком, но и его часть (старшую или младшую тетраду).

Подстановочные знаки полезны при определении строк, содержимое которых может изменяться, но при этом размер их известен, однако это не всегда так. В некоторых случаях может потребоваться определить строки с фрагментами переменного содержимого и длины. В таких ситуациях вы можете использовать переходы вместо подстановочных знаков:

rule JumpExample
{
        strings:
                $hex_string = {F4 23 [4-6] 62 B4}
        condition:
                $hex_string
}

В приведенном выше примере у нас есть пара чисел, заключенный в квадратные скобки и разделенных дефисом, это переход. Он показывает, что любая произвольная последовательность от 4 до 6 байт может занимать позицию перехода. Любая из следующих строк будет соответствовать шаблону:

F4 23 01 02 03 04 62 B4
F4 23 00 00 00 00 00 62 B4
F4 23 15 82 A3 04 45 22 62 B4

Любой переход [X-Y] должен удовлетворять условию 0 <= X <= Y. В предыдущих версиях YARA и X, и Y могли принимать значения не более 256, но начиная с YARA 2.0 для X и Y это ограничение снято.

Например:

FE 39 45 [0-8] 89 00
FE 39 45 [23-45] 89 00
FE 39 45 [1000-2000] 89 00

Такая запись является недопустимой:

FE 39 45 [10-7] 89 00

Если нижняя и верхняя границы равны, вы можете написать одно число, заключенное в скобки, например:

FE 39 45 [6] 89 00

Приведенная выше строка эквивалентна обоим из них:

FE 39 45 [6-6] 89 00
FE 39 45 ?? ?? ?? ?? ?? ?? 89 00

Начиная с YARA 2.0 вы также можете использовать неограниченные переходы:

FE 39 45 [10-] 89 00
FE 39 45 [-] 89 00

Первый означает [10-бесконечно], второй означает [0-бесконечно].

Есть также ситуации, в которых вы можете предоставить различные альтернативы для данного фрагмента шестнадцатеричной строки. В таких ситуациях можно использовать синтаксис, напоминающий регулярное выражение:

rule AlternativesExample1
{
        strings:
                $hex_string = {F4 23 ( 62 B4 | 56 ) 45}
        condition:
                $hex_string
}

Это правило будет соответствовать любому файлу, содержащему F42362B445 или F4235645.

Можно выразить и более двух альтернатив. Фактически, нет никаких ограничений ни на количество альтернативных последовательностей, ни на их размер.

rule AlternativesExample2
{
        strings:
                $hex_string = { F4 23 ( 62 B4 | 56 | 45 ?? 67 ) 45 }
        condition:
                $hex_string
}

Как можно увидеть в приведенном выше примере, строки, содержащие подстановочные символы можно использовать в рамках альтернативных последовательностей.

2.2.2. Текстовые строки

Как было показано ранее, текстовые строки обычно определяются следующим образом:

rule TextExample
{
        strings:
                $text_string = "foobar"
        condition:
                $text_string
}

Это самый простой случай: строка в кодировке ASCII с учетом регистра. Однако текстовые строки могут сопровождаться некоторыми полезными модификаторами, которые изменяют способ интерпретации строки. Эти модификаторы добавляются в конце строки, разделенные пробелами, как будет показано ниже.

Текстовые строки могут также содержать следующее подмножество escape-последовательностей, доступных на языке Си:

  • \" - Двойная кавычка
  • \\ - Обратный слэш
  • \t - Горизонтальная табуляция
  • \n - Новая строка
  • \xdd - Любой байт в шестнадцатеричной нотации

2.2.2.1. Регистро-независимые строки

Текстовые строки в YARA чувствительны к регистру по умолчанию, однако вы можете превратить свою строку в режим без учета регистра, добавив модификатор nocase в конце определения строки в той же строке:

rule CaseInsensitiveTextExample
{
        strings:
                $text_string = "foobar" nocase
        condition:
                $text_string
}

С модификатором nocase строка foobar будет соответствовать Foobar, FOOBAR и fOoBaR. Этот модификатор может использоваться совместно с любым другим модификатором.

2.2.2.2. Расширенные строки

Модификатор wide может использоваться для поиска строк, закодированных двумя байтами на символ, что типично для многих исполняемых бинарных файлов. В приведенном ниже примере строка “Borland” кодируется как два байта на символ:

rule WideCharTextExample1
{
        strings:
                $wide_string = "Borland" wide
        condition:
                $wide_string
}

Однако необходимо иметь в виду, что этот модификатор просто чередует коды ASCII-символов в строке с нулями, он не поддерживает строки UTF-16, содержащие неанглийские символы.

Если вы хотите найти строки в обоих форматах (ASCII и расширенном), вы можете использовать модификатор ascii в сочетании с wide, независимо от того, в каком порядке они появляются.

rule WideCharTextExample2
{
        strings:
                $wide_and_ascii_string = "Borland" wide ascii
        condition:
                $wide_and_ascii_string
}

Модификатор ascii может быть использован отдельно, без сопутствующего модификатора wide, при этом писать его не обязательно, так как в отсутствие модификатора wide строка по умолчанию считается ASCII.

2.2.2.3. XOR-строки

Модификатор xor может использоваться для поиска строк, к каждому байту которой применена операция “исключающее ИЛИ” (сложение по модулю 2) с каким-либо произвольным байтом.

Следующее правило будет искать строки, полученные при применении к строке "This program cannot" операции “исключающее ИЛИ” с любым произвольным байтом:

rule XorExample1
{
        strings:
                $xor_string = "This program cannot" xor
        condition:
                $xor_string
}

Приведенное выше правило логически эквивалентно правилу:

rule XorExample2
{
        strings:
                $xor_string_00 = "This program cannot"
                $xor_string_01 = "Uihr!qsnfs`l!b`oonu"
                $xor_string_02 = "Vjkq\"rpmepco\"acllmv"
                // Повторить для каждого байта операции xor
        condition:
                any of them
}

Вы также можете комбинировать xor модификатор с wide, ascii и nocase модификаторами. Например, для поиска расширенной и ASCII-версии строки после применения к ней “исключающего ИЛИ” следует использовать:

rule XorExample3
{
        strings:
                $xor_string = "This program cannot" xor wide ascii
        condition:
                $xor_string
}

Модификатор xor применяется после каждого другого модификатора. Это означает, что использование xor и wide вместе приводит к применению xor к чередующимся нулевым байтам. Например, следующие два правила логически эквивалентны:

rule XorExample3
{
        strings:
                $xor_string = "This program cannot" xor wide
        condition:
                $xor_string
}

rule XorExample4
{
        strings:
                $xor_string_00 = "T\x00h\x00i\x00s\x00 \x00p\x00r\x00o\x00g\x00r\x00a\x00m\x00 \x00c\x00a\x00n\x00n\x00o\x00t\x00"
                $xor_string_01 = "U\x01i\x01h\x01r\x01!\x01q\x01s\x01n\x01f\x01s\x01`\x01l\x01!\x01b\x01`\x01o\x01o\x01n\x01u\x01"
                $xor_string_02 = "V\x02j\x02k\x02q\x02\"\x02r\x02p\x02m\x02e\x02p\x02c\x02o\x02\"\x02a\x02c\x02l\x02l\x02m\x02v\x02"
                // Повторить для каждого байта операции xor
        condition:
                any of them
}

2.2.2.4. Поиск полных слов

Другим модификатором, который может быть применен к текстовым строкам, является fullword. Этот модификатор гарантирует, что строка будет соответствовать, только если она появляется в файле (или процессе), разделенном не буквенно-цифровыми символами. Например, строка domain, если она определена как полное слово, не соответствует www.mydomain.com, но при этом соответствует www.my-domain.com и www.domain.com.

2.2.3. Регулярные выражения

Регулярные выражения являются одной из самых мощных функций YARA. Они определяются так же, как и текстовые строки, но заключаются в косые черты вместо двойных кавычек, как в языке программирования Perl.

rule RegExpExample1
{
        strings:
                $re1 = /md5: [0-9a-fA-F]{32}/
                $re2 = /state: (on|off)/
        condition:
                $re1 and $re2
}

Регулярные выражения могут также сопровождаться модификаторами nocase, ascii, wide и fullword, как и в текстовых строках. Семантика этих модификаторов одинакова в обоих случаях.

В предыдущих версиях YARA для сопоставления регулярных выражений использовались внешние библиотеки, такие как PCRE и RE2, но начиная с версии 2.0 YARA использует собственный механизм регулярных выражений. Он реализует большинство функций, входящих в PCRE, за исключением некоторых из них, таких как группы захвата, классы символов POSIX и обратные ссылки.

Регулярные выражения YARA распознают следующие метасимволы:

  • \ - Экранирует следующие метасимволы
  • ^ - Показывает начало файла
  • $ - Показывает конец файла
  • | - Выбор альтернатив
  • () - Группирование
  • [] - Класс символов

Также могут использоваться следующие квантификаторы:

  • * - 0 или более раз
  • + - 1 или более раз
  • ? - 0 или 1 раз
  • {n} - Ровно n раз
  • {n,} - Не менее n раз
  • {,m} - Не более m раз
  • {n,m} - От n до m раз

Все эти квантификаторы имеют “ленивый” вариант работы, который обозначается знаком вопроса ?:

  • *? - 0 или более раз в “ленивом” режиме
  • +? - 1 или более раз в “ленивом” режиме
  • ?? - 0 или 1 раз в “ленивом” режиме
  • {n}? - Ровно n раз в “ленивом” режиме
  • {n,}? - Не менее n раз в “ленивом” режиме
  • {,m}? - Не более m раз в “ленивом” режиме
  • {n,m}? - От n до m раз в “ленивом” режиме

Могут использоваться следующие escape-последовательности:

  • \t - Tab (HT, TAB)
  • \n - New line (LF, NL)
  • \r - Return (CR)
  • \f - Form feed (FF)
  • \a - Alarm bell
  • \xNN - Символ, порядковым номером которого является данное шестнадцатеричное число

Классы символов:

  • \w - Словарные символы (буквенно-цифровые и “_”)
  • \W - Не словарные символы
  • \s - Пробел
  • \S - Не пробельные символы
  • \d - Символы десятичных цифр
  • \D - Не цифровые символы

Начиная с версии 3.3.0 также возможно применение:

  • \b - Граница слова
  • \B - Совпадает на границе слова

2.3. Условия

Условия - это не что иное, как логические выражения, которые можно найти во всех языках программирования, например оператор if. Они могут содержать типичные булевы операторы and, or, и not, и реляционные операторы >=, <=, <, >, == и !=. Кроме того, арифметические операторы (+, -, *, \, %) и побитовые операторы (&, |, <<, >>, ~, ^) могут использоваться для числовых выражений.

Строковые идентификаторы могут также использоваться в условии, действуя как булевы переменные, значение которых зависит от наличия или отсутствия связанной строки в файле.

rule Example
{
        strings:
                $a = "text1"
                $b = "text2"
                $c = "text3"
                $d = "text4"
        condition:
                ($a or $b) and ($c or $d)
}

2.3.1. Подсчет строк

Иногда нам нужно знать не только, присутствует ли определенная строка или нет, но и сколько раз строка появляется в файле или памяти процесса. Число вхождений каждой строки представлено переменной, имя которой строковый идентификатор, но с символом # вместо символа $. Например:

rule CountExample
{
        strings:
                $a = "dummy1"
                $b = "dummy2"
        condition:
                #a == 6 and #b > 10
}

Это правило соответствует любому файлу или процессу, содержащему строку $a ровно шесть раз и более десяти вхождений строки $b.

2.3.2. Смещение строк или виртуальный адрес

В большинстве случаев, когда строковый идентификатор используется в условии, мы хотим знать, находится ли связанная строка где-либо в файле или памяти процесса, но иногда нам нужно знать, находится ли строка в некотором определенном смещении в файле или в некотором виртуальном адресе в адресном пространстве процесса. В таких ситуациях оператор at- это то, что нам нужно. Этот оператор используется, как показано в следующем примере:

rule AtExample
{
        strings:
                $a = "dummy1"
                $b = "dummy2"
        condition:
                $a at 100 and $b at 200
}

Выражение $a at 100 в приведенном выше примере истинно только в том случае, если строка $a находится со смещением 100 в файле (или по виртуальному адресу 100, если применяется к запущенному процессу). Строка $b должна находится по смещению 200. Обратите внимание, что оба смещения являются десятичными, однако шестнадцатеричные числа также можно использовать, добавив префикс 0x перед числом, как в языке программирования C, что очень удобно при написании виртуальных адресов. Также обратите внимание на более высокий приоритет оператора at над and.

В то время как оператор at позволяет искать строку с некоторым фиксированным смещением в файле или виртуальном адресе в пространстве памяти процесса, оператор in позволяет искать строку в диапазоне смещений или адресов.

rule InExample
{
        strings:
                $a = "dummy1"
                $b = "dummy2"
        condition:
                $a in (0..100) and $b in (100..filesize)
}

В приведенном выше примере строка $a должна быть найдена со смещением от 0 до 100, а строка $b - со смещением от 100 до конца файла. Опять же, по умолчанию, числа десятичные.

Вы также можете получить смещение или виртуальный адрес i-го вхождения строки $a с помощью @a[i]. Первый индекс - единица, поэтому первое вхождение будет @a[1] второе @a[2] и так далее. Если указать индекс, превышающий число вхождений строки, результатом будет значение NaN (Not A Number).

2.3.3. Длина совпадений

Для многих регулярных выражений и шестнадцатеричных строк, содержащих переходы, длина совпадений является переменной. Если у вас есть регулярное выражение /fo*/ строки "fo", "foo" и "fooo" могут быть совпадениями, при этом все они разной длины.

Вы можете использовать длину совпадений как часть вашего условия с помощью символа ! перед строковым идентификатором, также как используется символ @ для смещения. !a[1] - длина первого совпадения $a, !a[2] - длина второго совпадения и так далее. является сокращенной формой !a[1].

2.3.4. Размер файла

Строковые идентификаторы не являются единственными переменными, которые могут отображаться в условии (на самом деле, правила могут быть определены без определения строки, как будет показано ниже), есть и другие специальные переменные, которые могут быть использованы. Одна из этих специальных переменных - переменная filesize, которая содержит, как указывает ее имя, размер сканируемого файла. Размер выражается в байтах.

rule FileSizeExample
{
        condition:
                filesize > 200KB
}

Предыдущий пример также демонстрирует использование постфикса KB. Этот постфикс при присоединении к числовой константе автоматически умножает значение константы на 1024. Постфикс MB можно использовать для умножения значения на 2^20. Оба постфикса можно использовать только с десятичными константами.

Использование filesize имеет смысл только тогда, когда правило применяется к файлу. Если правило применяется к запущенному процессу, оно всегда будет получать на выходе значение false, потому что filesize в данном случае не имеет смысла.

2.3.5. Точка входа исполняемого файла

Другой специальной переменной, которая может использоваться в правиле, является entrypoint. Если файл является Portable Executable (PE) или Executable and Linkable Format (ELF), эта переменная содержит смещение точки входа исполняемого файла в случае сканирования файла. Если мы сканируем запущенный процесс, точка входа будет содержать виртуальный адрес точки входа основного исполняемого файла. Обычно эта переменная используется для поиска некоторого шаблона в точке входа для обнаружения упаковщиков или простых файловых инфекторов.

rule EntryPointExample1
{
        strings:
                $a = { E8 00 00 00 00 }
        condition:
                $a at entrypoint
}

rule EntryPointExample2
{
        strings:
                $a = { 9C 50 66 A1 ?? ?? ?? 00 66 A9 ?? ?? 58 0F 85 }
        condition:
                $a in (entrypoint..entrypoint + 10)
}

Наличие переменной entrypoint в правиле означает, что только файлы PE или ELF могут удовлетворять этому правилу. Если файл не является PE или ELF, любое правило, использующее эту переменную, получает значение false.

Warning

Переменная entrypoint устарела, вы должны использовать эквивалентную переменную pe.entry_point из модуля PE. Начиная с YARA 3.0 при использовании entrypoint вы получите предупреждение. Данная переменная будет удалена из последующих версий.

2.3.6. Доступ к данным на заданной позиции

Есть много ситуаций, в которых вы можете записать условия, которые зависят от данных, хранящихся по определенному смещению в файле или по виртуальному адресу процесса, в зависимости от того, сканируем мы файл или запущенный процесс. В таких случаях можно использовать одну из следующих функций для чтения данных из файла с заданным смещением:

int8(смещение или виртуальный адрес)
int16(смещение или виртуальный адрес)
nt32(смещение или виртуальный адрес)

uint8(смещение или виртуальный адрес)
uint16(смещение или виртуальный адрес)
uint32(смещение или виртуальный адрес)

int8be(смещение или виртуальный адрес)
int16be(смещение или виртуальный адрес)
int32be(смещение или виртуальный адрес)

uint8be(смещение или виртуальный адрес)
uint16be(смещение или виртуальный адрес)
uint32be(смещение или виртуальный адрес)

Функции intXX считывают 8, 16 и 32-разрядные целые числа со знаком по указанному смещению или виртуальному адресу, а функции uintXX - целые числа без знака. Как 16, так и 32-разрядные целые числа считываются в little-endian формате. Если вы хотите прочитать целое число в big-endian формате, используйте соответствующую функцию, заканчивающуюся на be. В качестве значения смещения или виртуального адреса может быть любое выражение, возвращающее целое число без знака, включая возвращаемое значение одной из функций uintXX. В качестве примера рассмотрим правило для определения PE-файлов:

rule IsPE
{
        condition:
                // MZ-сигнатура по смещению 0 и ...
                uint16(0) == 0x5A4D and
                // ... PE-сигнатура по смещению 0x3C в MZ-заголовке
                uint32(uint32(0x3C)) == 0x00004550
}

2.3.7. Наборы строк

Есть обстоятельства, в которых надо указать, что файл должен содержать определенное количество строк из заданного набора. Не все строки из наборе должны присутствовать в файле, но, по крайней мере некоторые из них должны содержаться в файле. В этих ситуациях можно использовать оператор of.

rule OfExample1
{
        strings:
                $a = "dummy1"
                $b = "dummy2"
                $c = "dummy3"
        condition:
                2 of ($a,$b,$c)
}

Это правило требует, чтобы по крайней мере две строки из набора ($a, $b, $c) присутствовали в файле, но не имеет значения, какие две из них. Конечно, при использовании этого оператора, число до оператора должно быть меньше или равно количеству строк в наборе.

Элементы набора могут быть явно перечислены, как в предыдущем примере, или могут быть указаны с помощью подстановочных символов. Например:

rule OfExample2
{
        strings:
                $foo1 = "foo1"
                $foo2 = "foo2"
                $foo3 = "foo3"
        condition:
                2 of ($foo*) // эквивалент для выражения 2 of ($foo1,$foo2,$foo3)
}

rule OfExample3
{
        strings:
                $foo1 = "foo1"
                $foo2 = "foo2"
                $bar1 = "bar1"
                $bar2 = "bar2"
        condition:
                3 of ($foo*,$bar1,$bar2)
}

Вы даже можете использовать ($*) для ссылки на все строки в правиле или написать эквивалентное ключевое слово them для большей наглядности.

rule OfExample4
{
        strings:
                $a = "dummy1"
                $b = "dummy2"
                $c = "dummy3"
        condition:
                1 of them // эквивалент для выражения 1 of ($*)
}

Во всех приведенных выше примерах число строк задается числовой константой, но может использоваться любое выражение, возвращающее числовое значение. Также могут быть использованы ключевые слова any и all.

all of them       // все строки в правиле
any of them       // любая строка в правиле
all of ($a*)      // все строки, начинающиеся с $a
any of ($a,$b,$c) // любая строка из $a, $b или $c
1 of ($*)         // то же самое, что и "any of them"

2.3.8. Применение одного и того же условия к нескольким строкам

Есть еще один оператор, который очень похож на оператор of, но более эффективный. Это оператор for...of. Синтаксис данного оператора:

for expression of string_set : ( boolean_expression )

И его смысл таков: из строк в string_set по крайней мере expression из них должно удовлетворять условию boolean_expression.

Другими словами: boolean_expression вычисляется для каждой строки из string_set и должно быть хотя бы expression строк, для которых boolean_expression равно True.

Конечно, boolean_expression может быть любым логическим выражением, принятым в разделе condition правила, за исключением одной важной детали: здесь вы можете (и должны) использовать знак доллара ($) в качестве заполнителя для анализируемой строки.

Посмотрите на следующее выражение:

for any of ($a, $b, $c) : ($ at entrypoint)

Символ $ в булевом выражении не привязан к какой-либо конкретной строке, он будет сначала привязан к строке $a, затем к $b, после чего к $c в трех последовательных вычислениях значения выражения ($ at entrypoint).

Если внимательно посмотреть то видно, что оператор of является частным случаем for...of. Следующие два выражения являются одинаковыми:

any of ($a,$b,$c)
for any of ($a,$b,$c) : ($)

Можно также использовать символы # и @ для ссылки на число вхождений и первое смещение каждой строки соответственно.

for all of them : (# > 3)
for all of ($a*) : (@ > @b)

2.3.9. Использование анонимных строк с of и for…of

При использовании операторов of и for...of, за которыми следует them, присвоение каждой строке отдельного идентификатора, обычно является лишним. Поскольку мы не ссылаемся на какую-либо строку отдельно, нам не нужно предоставлять уникальный идентификатор для каждой из них. В таких ситуациях можно объявить анонимные строки с идентификаторами, состоящими только из символа $, как в следующем примере:

rule AnonymousStrings
{
        strings:
                $ = "dummy1"
                $ = "dummy2"
        condition:
                1 of them
}

2.3.10. Перебор строковых вхождений

Как было показано в п. 2.3.2 (Смещение строк или виртуальный адрес), смещения или виртуальные адреса, где строка появляется в адресном пространстве файла или процесса, могут быть доступны с помощью синтаксиса: @a[i], где i - индекс, указывающий, на какое вхождение строки $a вы ссылаетесь.

Иногда необходимо перебирать некоторые из этих смещений и убедиться, что они удовлетворяют заданному условию. Например:

rule Occurrences
{
        strings:
                $a = "dummy1"
                $b = "dummy2"
        condition:
                for all i in (1,2,3) : ( @a[i] + 10 == @b[i] )
}

Показанное выше правило гласит, что первые три вхождения $b должны быть на расстоянии 10 байт от первых трех вхождений $a.

То же самое условие можно записать и таким образом:

for all i in (1..3) : (@a[i] + 10 == @b[i])

Обратите внимание, что мы используем ряд (1..3) вместо перечисления значений индекса (1,2,3). Однако, не обязательно использовать константы для указания границ диапазона, можно также использовать и выражения, как в следующем примере:

for all i in (1..#a) : (@a[i] < 100)

В этом случае мы перебираем каждое вхождение строки $a (помните, что #a представляет количество вхождений $a). Это правило определяет, что каждое вхождение строки $a должно находиться в пределах первых 100 байт файла.

Если вы хотите выразить, что только некоторые вхождения строки должны удовлетворять условию, то в данном случае применяется та же логика, что и в операторе for...of:

for any i in (1..#a) : (@a[i] < 100)
for 2 i in (1..#a) : (@a[i] < 100)

Таким образом, синтаксис этого оператора:

for expression identifier in indexes : (boolean_expression)

2.3.11. Ссылки на другие правила

При написании условий для правил можно также ссылаться на ранее определенное правило способом, напоминающим вызов функции в традиционных языках программирования. Таким образом, можно создавать правила, которые зависят от других. Например:

rule Rule1
{
        strings:
                $a = "dummy1"

        condition:
                $a
}

rule Rule2
{
        strings:
                $a = "dummy2"

        condition:
                $a and Rule1
}

Как видно из примера, файл будет удовлетворять правилу Rule2, только если он содержит строку "dummy2" и удовлетворяет правилу Rule1. Обратите внимание, что правило необходимо определить строго до того, как оно будет вызвано.

2.4. Еще о правилах

Есть некоторые аспекты правил YARA, которые ранее не были рассмотрены, но очень важны. Это глобальные правила, частные правила, теги и метаданные.

2.4.1. Глобальные правила

Глобальные правила дают вам возможность налагать ограничения во всех ваших правилах сразу. Например, предположим, что вы хотите, чтобы все ваши правила игнорировали те файлы, которые превышают определенный размер. Вы могли бы править все правила, внося необходимые изменения в их условия, или просто написать глобальное правило, подобное этому:

global rule SizeLimit
{
        condition:
                filesize < 2MB
}

Вы можете определить столько глобальных правил, сколько необходимо, они будут проверяться перед остальными правилами, которые, в свою очередь, будут проверяться только в том случае, если все глобальные правила будут выполнены.

2.4.2. Приватные правила

Приватные правила - очень простая концепция. Это правила, которые не сообщают YARA, когда они выполняются при проверке файла. Правила, которые не выдают результат явно, могут показаться на первый взгляд бесполезными, но когда они смешиваются с возможностью ссылаться на одно правило из другого (п. 2.3.11 Ссылки на другие правила)_, они становятся полезными. Приватные правила могут служить блоками для других правил и в то же время предотвращать загромождение вывода YARA нерелевантной информацией.

Чтобы объявить правило как приватное, просто добавьте ключевое слово private перед объявлением правила.

private rule PrivateRuleExample
{
...
}

Вы можете применить к правилу как модификатор private, так и global, в результате чего о выполнении глобального правила не будет сообщено YARA, но при этом оно будет выполнено.

2.4.3. Тэги правил

Еще одной полезной особенностью YARA является возможность добавления тегов в правила. Эти теги можно использовать позже для фильтрации вывода YARA и показывать вывод только тех правил, которые вас интересуют. В правило можно добавить любое количество тегов, которые объявляются после идентификатора правила, как показано ниже:

rule TagsExample1 : Foo Bar Baz
{
...
}

rule TagsExample2 : Bar
{
...
}

Теги должны соответствовать одному и тому же лексическому соглашению для написания идентификаторов правил, поэтому допускаются только буквенно-цифровые символы и подчеркивания, а тег не может начинаться с цифры. Они также чувствительны к регистру.

При использовании YARA вы можете выводить результаты только тех правил, которые помечены тегом или тегами.

2.4.4. Метаданные

Помимо разделов, в которых определены строки и условия, правила могут также иметь раздел метаданных, где можно разместить дополнительную информацию о правиле. Раздел метаданных определяется ключевым словом meta и содержит пары идентификатор/значение, как в следующем примере:

rule MetadataExample
{
        meta:
                my_identifier_1 = "Some string data"
                my_identifier_2 = 24
                my_identifier_3 = true
        strings:
                $my_text_string = "text here"
                $my_hex_string = { E2 34 A1 C8 23 FB }
        condition:
                $my_text_string or $my_hex_string
}

Как видно из примера, за идентификаторами метаданных всегда следует знак равенства и присвоенное им значение. Присвоенные значения могут быть строками, числами, или одним из логических значений true или false. Обратите внимание, что пары идентификатор/значение, определенные в разделе метаданные, не могут использоваться в разделе condition, их единственной целью является хранение дополнительной информации о правиле.

2.5. Использование модулей

Модули - это расширения базовой функциональности YARA. Некоторые модули, такие как модули PE или Cuckoo, официально распространяются с YARA, а дополнительные могут быть созданы третьими лицами или даже вами самостоятельно, как описано в Главе 4.

Первым шагом к использованию модуля является его импорт с помощью оператора import. Этот оператор должен быть помещен вне любого определения правила и сопровождаться именем модуля, заключенным в двойные кавычки:

import "pe"
import "cuckoo"

После импорта модуля вы можете использовать его функции или переменные, используя <имя модуля>. в качестве префикса к любой переменной или функции, экспортируемые модулем. Например:

pe.entry_point == 0x1000
cuckoo.http_request(/someregexp/)

2.6. Неопределенные значения

Модули часто оставляют переменные в неопределенном состоянии, например, когда переменная не имеет смысла в текущем контексте (например, pe.entry_point при сканировании файла, отличного от PE-файла). YARA обрабатывает неопределенные значения таким образом, чтобы правило не потеряло свой смысл. Взгляните на это правило:

import "pe"

rule Test
{
        strings:
                $a = "some string"

        condition:
                $a and pe.entry_point == 0x1000
}

Если сканируемый файл не является PE-файлом, вы не ожидаете, что это правило будет соответствовать файлу, даже если он содержит строку, потому что оба условия (наличие строки и правильное значение для точки входа) должны быть выполнены. Однако, если условие изменено на:

$a or pe.entry_point == 0x1000

В этом случае вы ожидаете, что правило будет соответствовать файлу, если файл содержит строку, даже если это не PE-файл. Именно так ведет себя Яра.

Логика проста: любая арифметическая или логическая операция, а также операция сравнения приведет к неопределенному значению, если один из ее операндов не определен, за исключением операции OR, где неопределенный операнд интерпретируется как false.

2.7. Внешние переменные

Внешние переменные позволяют определить правила, которые зависят от значений, предоставляемых извне. Например, можно написать следующее правило:

rule ExternalVariableExample1
{
        condition:
                ext_var == 10
}

В данном случае ext_var - это внешняя переменная, значение которой присваивается во время выполнения (см. опцию -d командной строки и параметр externals методов compile и match в yara-python). Внешние переменные могут быть целочисленными, строковыми или булевыми, их тип зависит от присвоенного им значения. Целочисленная переменная может заменить любую целочисленную константу в условии, а булевы переменные могут занять место булевых выражений. Например:

rule ExternalVariableExample2
{
        condition:
                bool_ext_var or filesize < int_ext_var
}

Внешние переменные строкового типа могут использоваться с операторами: `contains и matches. Оператор contains возвращает true, если строка содержит указанную подстроку. Оператор matches возвращает true, если строка соответствует заданному регулярному выражению.

rule ExternalVariableExample3
{
        condition:
                string_ext_var contains "text"
}

rule ExternalVariableExample4
{
        condition:
                string_ext_var matches /[a-z]+/
}

Модификаторы регулярных выражений можно использовать вместе с оператором matches, например, если требуется, чтобы регулярное выражение из предыдущего примера не учитывало регистр, можно использовать /[a-z]+/i. Можно также использовать модификатор s для однострочного режима, в этом режиме точка соответствует всем символам, включая разрывы строк. При этом, оба модификатора могут использоваться одновременно, как в следующем примере:

rule ExternalVariableExample5
{
        condition:
                /* выбираем однострочный режим без учета регистра */
                string_ext_var matches /[a-z]+/is
}

Необходимо иметь в виду, что каждая внешняя переменная, используемая в правилах, должна быть определена во время выполнения либо с помощью опции -d командной строки, либо путем предоставления параметра externals соответствующему методу в yara-python.

2.8. Включаемые файлы

Чтобы обеспечить более гибкую организацию файлов правил, YARA предоставляет директиву include. Эта директива работает аналогично директиве препроцессора #include в программах C, которая вставляет содержимое указанного исходного файла в текущий файл во время компиляции. Следующий пример будет включать в себя содержимое файла other.yar в текущий файл:

include "other.yar"

Базовый путь при поиске файла в директиве include будет каталогом, в котором находится текущий файл. По этой причине файл other.yar в предыдущем примере должен находиться в той же директории текущего файла. Однако, вы также можете указать относительные пути:

include "./includes/other.yar"
include "../includes/other.yar"

Или использовать абсолютные пути:

include "/home/plusvic/yara/includes/other.yar"

В Windows, при указании путей, принимается как прямой, так и обратный слэш, но при этом не забывайте указывать букву диска:

include "c:/yara/includes/other.yar"
include "c:\\yara\\includes\\other.yar"