Измени один байт в файле — и хеш станет другим. YARA ищет не точную копию, а набор признаков, поэтому лучше подходит для обнаружения разных образцов вредоноса. Разберёмся, как устроены правила и модули YARA, напишем собственные сигнатуры и применим их для поиска аномальных файлов.

Что такое YARA

YARA — инструмент для поиска и классификации вредоноса на основе специальных правил. Движок применяет их к процессам и файлам. Создатель Виктор Альварес разработал YARA во время работы в VirusTotal. Сегодня это стандарт де факто для описания сигнатур вредоносных программ.

Понимание его внутреннего устройства показывает, как работают антивирусные сигнатуры и почему они выглядят именно так. Движок расширяется за счёт пользовательских модулей на C, позволяя добавлять произвольную логику анализа.

На примере YARA видна эволюция антивирусных технологий: от простых скан-строк эпохи DOS до современных составных правил с вложенными условиями и данными от нативных модулей.

Правила

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

Пример правила, которое детектирует применение функций WinAPI для расчета MD5-хеша:

rule MD5_API {
    meta:
        author = '_pusher_'
        description = 'Looks for MD5 API'
        date = '2016-07'
    strings:
        $advapi32 = 'advapi32.dll' wide ascii nocase
        $cryptdll = 'cryptdll.dll' wide ascii nocase
        $MD5Init = 'MD5Init' wide ascii
        $MD5Update = 'MD5Update' wide ascii
        $MD5Final = 'MD5Final' wide ascii
    condition:
        ($advapi32 or $cryptdll) and ($MD5Init and $MD5Update and $MD5Final)
}

Правило ищет строки с модификаторами wide и ascii nocase — вариации с любым регистром в однобайтовом (ASCII) или двухбайтовом (wide) представлении. Затем проверяет условие: наличие хотя бы одной из двух DLL и всех указанных API.

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

Сила YARA — в гибкости описания паттернов. Секция strings поддерживает три типа сигнатур: шестнадцатеричные строки, текстовые строки и регулярные выражения. Каждый тип имеет конструкции и модификаторы для описания как точных, так и нечётких совпадений.

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

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

rule RawBytes {
    strings:
        $hex = { E2 34 A1 C8 23 FB }
    condition:
        $hex
}

Четыре специальные конструкции превращают примитивный поиск в полноценный язык описания бинарных паттернов: wildcard, отрицание, переходы и альтернативы. Без них описание сигнатур байт-кода было бы невозможным, потому что даже минимальные различия в сборке приводят к изменению конкретных байтов.

Wildcard (подстановочные символы)

Символ ? заменяет неизвестный полубайт. Это работает на уровне полубайтов — это позволяет зафиксировать старшую или младшую половину байта, оставив другую произвольной. Такая гранулярность нужна, например, при описании опкодов x86, где старший полубайт кодирует операцию, а младший — регистр.

$hex = { E2 34 ?? C8 A? FB }

Здесь ?? — любой байт целиком (от 00 до FF), а A? — старший полубайт равен A, младший — любой (от A0 до AF).

Инструкции x86 часто содержат переменные поля, и wildcard позволяет абстрагироваться от конкретных регистров или смещений. Например, инструкция mov 32-битного регистра кодируется байтами B8—BF в зависимости от целевого регистра, а call — байтом E8 с четырьмя байтами относительного адреса.

( B8 | B9 | BA | BB | BC | BD | BE | BF ) ?? ?? ?? ??

Для call достаточно шаблона E8 ?? ?? ?? ??, где четыре байта wildcard заменяют относительный адрес.

NOT (отрицание)

Оператор ~ перед байтом означает «любое значение, кроме указанного». Если wildcard — это «мне всё равно, какой байт», то отрицание — это «мне всё равно, какой байт, лишь бы не вот этот». Комбинация позволяет исключать конкретные нежелательные варианты без необходимости перечислять все допустимые альтернативы.

$hex = { F4 23 ~00 62 B4 }
$hex2 = { F4 23 ~?0 62 B4 }

~00 — любой байт, кроме нулевого, а ~0? — любой байт, у которого старший полубайт не равен 0.

Полезно для исключения конкретных вариантов инструкций или данных. Например, можно описать jmp short с ненулевым смещением (EB ~00) или последовательность, где определённое поле не должно быть пустым.

Jumps (переходы)

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

$hex = { F4 23 [4-6] 62 B4 }

Это означает: между F4 23 и 62 B4 может быть от 4 до 6 произвольных байтов.

Допустимые формы:

  • [4-6] — от 4 до 6 байт
  • [10-] — от 10 байт до бесконечности
  • [-] — от 0 байт до бесконечности

Переходы критичны в нескольких типичных сценариях. Компилятор может вставлять выравнивание и инструкции NOP между функциями, и их количество непредсказуемо. Между двумя ключевыми инструкциями в функции часто находится переменное количество промежуточного кода — разные флаги оптимизации генерируют разное количество инструкций. Данные в структурах файловых форматов могут иметь поля переменной длины, и переход позволяет «перепрыгнуть» их, привязавшись к маркерам начала и конца.

Alternatives (альтернативы)

Конструкция ( ... | ... ) задаёт несколько допустимых вариантов для фрагмента. Синтаксис заимствован из регулярных выражений: варианты перечисляются через | внутри круглых скобок.

$hex = { F4 23 ( 62 B4 | 56 | 45 ?? 67 ) 45 }