Блог

Анализ угроз: современные техники обхода антивирусов при разработке вредоносного ПО

Методы разработки вредоносного ПО: классические и неклассические подходы

Сегодня существует два основных подхода к разработке вредоносного программного обеспечения. Классический метод создания вредоносного ПО предполагает использование языков C или C++, часто в сочетании с ассемблером. Этот подход остается наиболее популярным благодаря предсказуемости получаемых бинарных файлов и легкодоступности низкоуровневых операций.
Второй подход можно назвать неклассическим методом разработки вредоносного ПО. Его суть заключается в намеренном выборе относительно редкого языка программирования, такого как Forth, D, Go, Rust, Nim, Zig или Crystal. Главное преимущество этого подхода в том, что бинарные файлы, получаемые с помощью компиляторов этих языков, часто содержат много "лишнего" кода, который затрудняет как автоматический, так и ручной анализ. В сочетании с последующей обфускацией бинарного файла, этот подход дает отличные результаты для обхода антивирусов.
Выбор языка для разработки вредоносного ПО

Выбор языка для разработки вредоносного ПО

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

Обход статического анализа антивирусов: ключевые методы

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

Техники обхода сигнатурного анализа

Создание сигнатур для антивирусов часто основано на последовательностях подозрительных вызовов функций. Например, если программа в цикле отправляет запросы на сервер, получает в ответ команды, исполняет их в командной строке и отправляет результаты обратно на сервер — антивирус скорее всего примет ПО за реверс-шелл.
Видимость вызываемых WinAPI функций в таблице импортов

Видимость вызываемых WinAPI функций в таблице импортов

Код функций может находиться либо в самом бинарном файле, либо во внешних динамически-подключаемых библиотеках (DLL). В первом случае единственный способ обойти сигнатурный анализ — полностью переписать функции, что требует значительных усилий. Например, для реверс-шелла это означало бы переписывание таких библиотек, как libcurl и openssl.
О сложности переписывания больших библиотек в шуточной форме на примере libcurl и файла easy.c

О сложности переписывания больших библиотек в шуточной форме на примере libcurl и файла easy.c

Во втором случае, когда код функций находится в DLL, задача упрощается. Нужно лишь скрыть информацию о том, какие библиотеки загружаются и какие функции из них вызываются. Для этого злоумышленники чаще всего используют исключительно WinAPI DLL, поскольку это обеспечивает наилучшую переносимость и стабильность работы на разных версиях Windows, что важно при неизвестном устройстве атакуемой инфраструктуры.
Замена функций стандартной библиотеки языка C на WinAPI аналоги

Замена функций стандартной библиотеки языка C на WinAPI аналоги

Сокрытие импортируемых функций

При стандартном подключении DLL через прагму или параметры линковщика, в таблице импортов итогового бинарного файла остается информация об используемых библиотеках и функциях. Анализ таблицы импортов - один из самых простых способов выявить, что ПО является вредоносным.
Видимость библиотеки user32.dll и функции MessageBoxA в таблице импортов структуры Portable Executable (PE)

Видимость библиотеки user32.dll и функции MessageBoxA в таблице импортов структуры Portable Executable (PE)

Чтобы скрыть использование библиотек, злоумышленники применяют отложенную загрузку DLL непосредственно во время выполнения полезной нагрузки ПО. Для этого используются функции LoadLibrary и GetProcAddress. В этом случае таблица импортов не содержит явных упоминаний используемых библиотек, что значительно усложняет статический анализ бинарных файлов.
Базовый пример использование LoadLibrary и GetProcAddress

Базовый пример использование LoadLibrary и GetProcAddress

Шифрование строк для защиты от анализа

Однако, даже при использовании отложенной загрузки DLL, статический анализ может выявить строковые литералы. В том числе, названия библиотек и функций, передаваемые в качестве входящих параметров для LoadLibrary и GetProcAddress. Также могут быть выявлены иные строковые литералы, вероятно содержащиеся в известных сигнатурах. Например, тот или иной IP-адрес, порт, ключевое слово, полный либо частичный путь к файлу или директории, и т.д. Поэтому, современное вредоносное ПО не обходится без шифрования строк.
Про видимость строковых литералов имен входящих параметров LoadLibrary и GetProcAddress

Про видимость строковых литералов имен входящих параметров LoadLibrary и GetProcAddress

Ключевой принцип — шифрование должно происходить до исполнения программы, а дешифрование — во время выполнения. Ключ, необходимый для расшифровки строковых литералов, может:
  1. Храниться в бинарном файле как не зашифрованный строковый литерал.
  2. Получаться из внешнего источника (например, из HTTP-ответа сервера).
  3. Вычисляться во время исполнения программы
Первый вариант почти бесполезен против современных антивирусов. Второй вариант, напротив, наиболее надежный. Однако он не подходит в случае, когда неизвестна атакуемая инфраструктура (настройки сети и ее доступность как таковая). Третий вариант чуть менее надежен, но может быть использован без знаний об атакуемой инфраструктуре.

Современные техники шифрования строк

Для реализации шифрования строк во время компиляции часто используется C++, так как последние версии этого языка поддерживают вычисления на этапе компиляции. В настоящем вредоносном ПО чаще всего встречаются следующие алгоритмы шифрования: AES, DES, RC4 и TEA. Последний особенно популярен благодаря простоте реализации и высокой скорости работы.
Один из наипростейших шифров, использовавшихся во вредоносном ПО (141 строка)

Один из наипростейших шифров, использовавшихся во вредоносном ПО (141 строка)

Самое продвинутое вредоносное ПО всегда использует несколько алгоритмов шифрования, а также разные ключи расшифровки для каждой исходной строки (в C/C++, например, посредством предопределенных макросов компилятора: DATE, FILE, LINE, TIME, COUNTER и др.). Такая техника избыточна для обхода статического анализа, однако является незаменимой при обходе динамического анализа.
Использование шифрования строк во время компиляции с дешифрованием во время исполнения посредством макроса TXT

Использование шифрования строк во время компиляции с дешифрованием во время исполнения посредством макроса TXT

Противодействие динамическому анализу: обнаружение эмуляции

Динамический анализ вредоносного ПО предполагает эмуляцию выполнения программы в контролируемой среде. Современные антивирусы используют продвинутые системы эмуляции, такие как QEMU или Unicorn Engine. Однако даже они не могут полностью воспроизвести поведение программы на реальном оборудовании.
Шуточное сравнение QEMU, Unicorn Engine и FLOSS

Шуточное сравнение QEMU, Unicorn Engine и FLOSS

Обнаружение эмуляционной среды

Один из самых эффективных методов — использование недокументированных инструкций процессора. Злоумышленники находят инструкции, которые доступны на целевой архитектуре, но неизвестны или неправильно реализованы в эмуляторах. Сравнивая состояние системы до и после выполнения такой инструкции с ожидаемым поведением на реальном компьютере, программа может определить, что выполняется в эмуляторе.
Github issue, связанный с неправильной эмуляцией инструкции bswap в Unicorn Engine, использовавшийся раньше для обнаружения эмуляционной среды
Github issue, связанный с неправильной эмуляцией инструкции bswap в Unicorn Engine, использовавшийся раньше для обнаружения эмуляционной среды_1

Github issue, связанный с неправильной эмуляцией инструкции bswap в Unicorn Engine, использовавшийся раньше для обнаружения эмуляционной среды

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

Пример ассемблерного кода для обнаружения эмуляционной среды, который работал более пяти лет

Пример использования этого распознавания эмулятора в языке C++

Пример использования этого распознавания эмулятора в языке C++

Техника API Hammering

Еще один метод обхода динамического анализа — API Hammering. Суть этой техники заключается в использовании функций с большим временем ожидания ввода-вывода (работа с диском, сетью, периферийными устройствами, и т.п.). Множество вызовов таких функций до перехода к полезной нагрузке вредоносного ПО позволяет "переждать" время, отведенное антивирусом на эмуляцию и динамический анализ.
Улучшение примера с обнаружением эмуляционной среды, где бесконечный цикл реализован корректно, через API Hammering.

Улучшение примера с обнаружением эмуляционной среды, где бесконечный цикл реализован корректно, через API Hammering.

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

Обход хуков антивируса

Современные антивирусы устанавливают так называемые хуки на ключевые функции. В их числе и LoadLibrary с GetProcAddress. Суть хука в том, что при вызове функции, на которую он установлен, можно выявить входящие в эту функцию параметры. Это практически сводит на нет шифрование строк, поскольку любая функция WinAPI ждет обычный, не зашифрованный строковый литерал. Чтобы противостоять этому, злоумышленники создают собственные реализации ключевых функций или получают их адреса через структуру PEB (Process Environment Block) и LDR.

Современные подходы к обфускации кода

Обфускация вредоносного ПО — это процесс, направленный на затруднение анализа бинарного файла программы. Можно выделить четыре основных подхода:
  1. Обфускация посредством модификации исходного кода (ей и была посвящена большая часть статьи).
  2. Обфускация посредством модификации итогового бинарного файла, осуществляемая специальной программой-обфускатором, которая рекурсивно заменяет одни инструкции на иные, эквивалентные, но более сложные для анализа (доступ к программе-обфускатору чаще всего предлагается как услуга по подписке).
  3. Обфускация посредством модификации компилятора для генерации запутанного бинарного кода (так можно полностью автоматизировать обфускацию посредством модификации исходного кода)
  4. Комбинированный подход (чаще всего подходов 1 и 2).
Комбинированный подход создает наибольшие трудности как для автоматического анализа антивирусами, так и для ручного исследования вирусными аналитиками.

Заключение

Борьба между создателями вредоносного ПО и специалистами по информационной безопасности продолжает усиливаться. Современные техники обхода антивирусов становятся всё более изощренными, включая сложные методы шифрования, обнаружение эмуляционных сред и продвинутую обфускацию кода.
Для эффективного противодействия угрозам компаниям необходимо постоянно отслеживать новые тенденции в области кибербезопасности, внедрять многоуровневые системы защиты и обучать специалистов современным методам анализа и защиты от вредоносного ПО. Только комплексная кибербезопасность (cybersecurity) и многоуровневая защита позволят противостоять развивающимся угрозам в цифровом мире.
Понимание техник, которые используют злоумышленники, — это первый шаг к созданию эффективной защиты. Регулярное обновление знаний и инструментов анализа в сочетании с проактивной позицией в вопросах безопасности необходимы для поддержания надежной киберзащиты в условиях быстро меняющегося ландшафта угроз.