В этой статье будут рассмотрены эффективные методы обхода статического, динамического и эвристического анализа, используемого в новейших антивирусных продуктах.
Автор: Ege Balci
1. Вводное слово
В этой статье будут рассмотрены эффективные методы обхода статического, динамического и эвристического анализа, используемого в новейших антивирусных продуктах. Некоторые техники уже известны широкой публике, но есть и дополнительные трюки, которые являются ключевыми при генерации недетектируемого вредоноса. Объем файла не менее важен при использовании данных методов, и я постарался оптимизировать размер настолько, насколько возможно. В этом документе также будет затронута тема внутреннего устройства антивирусов и операционной системы Windows. Читателю желательно быть знакомым с C/C++ и ассемблером хотя бы на среднем уровне и понимать структуру PE-файла.
2. Введение
Методы реализации техник антидетекта зависит от типа вредоноса. Все методы, описываемые в этой статье, будут работать со всеми типами вредоносов, однако основное внимание уделено составным полезным нагрузкам для meterpreter, поскольку этот командный интерпретатор умеет практически все, что и остальные вредоносные программы. Получение сессии при помощи meterpreter на удаленной машине открывает массу возможностей: расширение привилегий, кражу учетных записей, миграцию между процессами, манипуляцию реестром и другие трюки пост-эксплуатации. Кроме того, вокруг meterpreter собралось мощное и активное сообщество, и этот инструмент популярен среди специалистов по безопасности.
3. Терминология
Детектирование на базе сигнатур:
По традиции антивирусные приложения обнаруживают вредоносные программы в основном при помощи сигнатур. Когда подозрительный файл попадает в антивирусную компанию, начинается анализ специалистами, либо системами динамического анализа. Как только вредонос опознан, полученная сигнатура добавляется в базу данных антивируса.
Статический программный анализ:
Статический программный анализ приложения выполняется без запуска программы. При помощи подобного метода выполняется анализ исходного кода или некоторых форм объектного кода.
Динамический программный анализ:
Динамический программный анализ выполняется в процессе запуска приложений в среде с реальным или виртуальным процессором. Чтобы динамический анализ был эффективным, целевое приложение должно запускаться с достаточным количеством тестовых входных данных для инициации работы различных участков алгоритма.
Песочница:
Песочница представляет собой безопасную среду для разделения запущенных программ, которая обычно используется для запуска нетестированного и недостоверного программного кода из непроверенных источников: производителей, пользователей или веб-сайтов для того, чтобы избежать вреда для операционной системы.
Эвристический анализ:
Эвристический анализ используется многими антивирусными программами для детектирования ранее неизвестных компьютерных вирусов, а также новых вариантов вредоносов, уже существующих в «дикой природе». Данный вид анализа, по сути, представляет собой экспертную систему, которая определяет степень восприимчивости системы по отношению к конкретной угрозе/риску при помощи различных правил принятия решений или весовых методов. Многокритериальный анализ – один из способов «взвешивания», который отличается от статического анализа, опирающегося на доступную информацию/статистику.
Энтропия:
Энтропия представляет собой случайную последовательность, собираемую операционной системой или приложением для использования в криптографических или других целях, где требуются случайные данные. Случайная последовательность часто формируется при помощи аппаратной платформы, например, на базе движения мыши или специальных генераторов случайных чисел. Слабая энтропия может оказать негативное влияние на производительность и безопасность.
4. Распространенные техники
Когда речь заходит об уменьшении вероятности детектирования, первое, что приходит на ум: шифровщики, упаковщики и обфускация кода. Эти инструменты и техники все еще позволяют оставаться незаметным для большинства антивирусных продуктов, но поскольку кибербезопасность развивается семимильными шагами, большинство методов и программ, существующих в дикой природе, устарели и не могут сделать полностью недетектируемый вредонос. С целью понимания алгоритмов работы я дам краткое описание для каждой типа этих техник и утилит;
4.1 Обфускация
Обфускация кода связана с перемешиванием исходного текста бинарного файла без порчи функций. Этот метод затрудняет статический анализ и изменяет хеш-сигнатуры бинарника. Обфускацию можно реализовать при помощи добавления нескольких строк мусорного кода или изменить последовательность выполнения инструкций программным путем. Данная техника позволяет обойти приличное число антивирусов, но конечный успех зависит от глубины обфускации.
4.2 Упаковщики
Упаковщик сжимает исходный файл и объединяет сжатые данные с кодом распаковки в один исполняемый файл. Когда сжатый исполняемый файл запускается, вначале программа распаковки воссоздает первоначальный код из сжатого файла, после чего происходит выполнение. Когда антивирусы сканируют упакованный вредонос, нужно определить алгоритм сжатия и распаковать исполняемый файл. Поскольку упакованные файлы сложнее проанализировать, злоумышленники заинтересованы в незасвеченном упаковщике.
4.3 Крипторы
Крипторы предназначены для шифровки бинарного файла с целью затруднения анализа или реверс-инжиниринга. У шифровщика есть две части: сборщик и стаб. Сборщик шифрует указанный бинарный файл и помещает внутрь стаб, являющийся самой важной частью криптора. При запуске бинарного файла сначала стаб расшифровывает первоначальную версию в память, а затем запускает расшифрованный файл при помощи метода «RunPE» (в большинстве случаев).
Рисунок 1: Схема шифрования инфицированного файла
Рисунок 2: Схема расшифровки бинарного файла
5. Проблема крипторов и упаковщиков
Перед началом изучения эффективным методов, следует упомянуть пару слов о том, какие есть проблемы с широко известными техниками и утилитами. На сегодняшний день антивирусные компании полностью оценили все риски и зачастую помимо поиска сигнатур и вредоносного воздействия ищут признаки крипторов и упаковщиков. По сравнению с вредоносами детектирование крипторов и упаковщиков проще, поскольку последние имеют одинаковые подозрительные алгоритмы, наподобие расшифровки PE-файла и запуска в памяти.
5.1 PE инъекция
Для того чтобы понять схему запуска PE-образа в памяти, необходимо упомянуть о том, как ОС Windows загружает PE-файлы. Обычно при компиляции PE-файла в адрес главного модуля устанавливается значение 0x00400000. Затем обрабатываются указатели полных адресов и рассчитываются инструкций длинных переходов на базе адреса главного модуля. По окончании компиляции в PE-файле создается секция таблицы релокации, которая содержит адреса инструкций, зависящих от базового адреса образа, например, указатели полных адресов и инструкций длинного перехода.
Во время выполнения PE-образа операционная система проверяет доступность предпочтительного адресного пространства. Если данное пространство не доступно, перед стартом системный загрузчик процессов должен подстроить абсолютные адреса в памяти. При помощи секции релокации загрузчик исправляет все инструкции, зависящие от адреса, и запускает приостановленный процесс. Весь этот механизм называется «Рандомизация размещения адресного пространства» (Address Space Layout Randomization; ASLR).
Для того чтобы выполнить PE-образ в памяти, криптору нужно распарсить PE-заголовки и переместить абсолютные адреса. Зачастую там имеется поддельный системный загрузчик, что сразу вызывает подозрение. Когда мы анализируем крипторы, написанные на С или языках высокого уровня, практически в каждом случае наблюдаются API функции «NtUnmapViewOfSection» и «ZwUnmapViewOfSection», которые попросту отключают представление секции от виртуального адресного пространства процесса. Эти функции играют очень важную роль при использовании метода RunPE, который встречается практически у 90% крипторов.
Рисунок 3: Пример использования функции NtUnmapViewOfSection
Естественно, антивирусные продукты не могут помечать каждую программу как вредоносную, которая использует эти API-функции. Однако сам факт наличия данных функций значит многое. Существует небольшой процент крипторов (в основном написанных на ассемблере), которые не используют эти функции и выполняют релокацию вручную. Какое-то время эти крипторы работают эффективно, но рано или поздно наступят последствия из-за попытки подделать системный загрузчик. Другой серьезный минус – при шифровании PE-файла серьезно увеличивается энтропия. Если антивирусный сканер детектирует нетипичный уровень энтропии, вполне возможно PE-файл будет помечен как подозрительный.
Рисунок 4: Результат измерения нетипичной энтропии у PE-файла
6. Идеальный метод
Концепция шифрования вредоносного кода имеет право на жизнь, но функцию дешифровки следует обработать хорошей обфускацией. Когда дело доходит до выполнения дешифрованного кода в памяти, можно обойтись без перемещения абсолютных адресов. Кроме того, внутри вредоноса нужно предусмотреть алгоритм проверки нахождения внутри песочницы. Если проверка выявила, что вредоносный код анализируется антивирусом, функцию дешифровки запускать не следует. Вместо шифрования всего PE-файла, более грамотно шифровать шелл-код или только секцию .text. В этом случае энтропия и размер файла остаются на приемлемом уровне, и не меняются заголовки образа и секции.
Рисунок 5: Общая блок-схема вредоноса
Функция «AV Detect.», показанная на схеме выше, будет детектировать, анализируется ли вредонос динамически внутри песочницы или нет. Если функция обнаруживает признаки антивирусного сканера, то либо вновь вызывается функция main, либо вредонос аварийно завершается. Если же признаки сканирования отсутствуют, будет вызвана функция «Decrypt Shellcode» для расшифровки шелл-кода.
На рисунке ниже показан обратный TCP-шелл-код под meterpreter в «сыром» формате.
Рисунок 6: Шелл-код в сыром формате
Для сохранения энтропии и размера на приемлемом уровне я будут обрабатывать шелл-код шифровщиком на базе алгоритма XOR с использованием мультибайтового ключа. Алгоритм XOR не является стандартом в индустрии, как например RC4 или blowfish, но в нашем случае сильное шифрование не требуется. Антивирусные продукты не будут заниматься дешифровкой. Нам достаточно защиты от статического анализа строк. Кроме того, XOR-шифрование намного быстрее и не использует библиотек, что влияет на итоговый размер вредоноса.
Тот же самый код под meterpreter, зашифрованный алгоритмом XOR с ключом.
Рисунок 7: Зашифрованный шелл-код
Поскольку мы пишем новый кусок вредоноса, хеш-сигнатура не будет известна антивирусным продуктам, и нам не нужно беспокоиться об обнаружении при помощи детектирования, построенного на базе сигнатур. Мы шифруем шелл-код и обфусцируем функции анти-детектирования/отладки и алгоритм дешифровки, что вполне достаточно для обхода статического/эвристического анализа. Нам осталось найти способ обхода динамического анализа, что является наиболее важной частью функции «AV detect». Перед написанием этой части необходимо разобраться, как работают эвристические алгоритмы антивирусных продуктов.
7. Эвристические движки
Эвристические движки основаны на статическом анализе и механизме правил. Главная цель таких движков – детектирование вредоносов нового поколения, которые еще не известны, посредством группировки и оценке угроз/рисков у отдельных фрагментов кода согласно предопределенным критериям. Даже при сканировании простейшей программы, которая выводит на печать «Привет, мир», эвристический движок определяет уровень угрозы и риска. Если оценка превышает пороговый уровень, файл помечается как вредоносный. Эвристические движки – наиболее продвинутая компонента антивирусных продуктов и используют значительное количество правил и критериев. Поскольку антивирусные компании не выпускают документации, описывающей эвристические движки, известны лишь некоторые критерии оценки угроз/рисков. Естественно, здесь может быть много ложных допущений и ошибок.
Вот некоторые известные правила, используемые при оценке угроз:
Присутствие цикла дешифровки.
- Чтение текущего имени компьютера.
- Чтение криптографического машинного GUID.
- Соединение со случайными доменами.
- Чтение даты установки Windows.
- Удаление исполняемых файлов.
- Присутствие потенциального IP-адреса в памяти бинарного файла.
- Модификация настроек прокси-сервера.
- Установка хуков/патчей к запущенному процессу.
- Инжектирование в explorer.
- Инжектирование в удаленный процесс.
- Запрос информации о процессе.
- Установка процесса в режим ошибки для сокрытия сообщения об ошибки.
- Нетипичная энтропия.
- Возможная проверка на присутствие антивирусного движка.
- Присутствие функционала для расширения привилегий.
- Модификация настроек политик приложений.
- Чтение версии BIOS системы/видеокарты.
- Конец PE-заголовка находится внутри нестандартной секции.
- Создание защищенных областей памяти.
- Создание множества процессов.
- Попытка приостановить работу на долгое время.
- Нестандартные секции.
- Чтение Windows Product Id.
- Присутствие функционала для запуска/взаимодействия с драйверами устройств.
- Присутствие функционала для блокировки пользовательского ввода.
8. Дешифровка шелл-кода
Обфускация механизма дешифровки чрезвычайно важна, поскольку большинство эвристических движков в состоянии обнаружить циклы дешифрования внутри PE-файлов. После значительного увеличения случаев, когда программа требовала выкуп, некоторые эвристические движки были почти полностью заточены под детектирование процедур дешифрования. После обнаружения процедуры дешифрования некоторые сканеры дожидались, пока в регистре ECX не окажется значение 0, что в большинстве случаев означает окончание цикла. По достижению окончания цикла дешифрования производится повторный анализ содержимого файла, которое будет представлять собой функцию дешифровки шелл-кода.
Рисунок 8: Функция дешифровки шелл-кода
На рисунке выше показан цикл for, внутри которого выполняются логические операции XOR между байтом шелл-кода и байтом ключа. Ассемблерные блоки наверху и внизу, по сути, не выполняют никаких операций. Эти блоки «накрывают» XOR-операцию случайным набором байтов и переходов. Поскольку мы не пользуемся продвинутые механизмы дешифровки, подобной обфускации будет достаточно для функции «Decrypt Shellcode».
9. Детектирование динамического анализа
В процессе написания механизма детектирования песочницы нам необходимо сделать обфускацию методов. Если эвристический движок обнаружит признаки методов анти реверс-инжиниринга, резко возрастет оценка угрозы со стороны нашего вредоноса.
9.1 Детектирование отладчика
Наш первый механизм детектирования антивирусного продукта будет проверять присутствие отладчика внутри процесса. Для решения этой задачи существует API-функция, которая определяет, отлаживается ли процесс отладчиком уровня пользователя (user-mode debugger). Но мы не будем использовать данную функцию, поскольку большинство антивирусных продуктов мониторят выражения API-вызовов. Скорее всего, после детектирования вызов будет расценен как противодействующий реверс-инжинирингу. Вместо использования API-функции мы будем искать байт «BeingDebuged» в PEB-блоке.
Рисунок 9: Функция для детектирования отладчика
Участок кода на рисунке выше извлекает байт BeingDebuged из PEB-блока. Если отладчик присутствует, проверка повторяется заново, и так до тех пор, пока в стеке не будет переполнение. После переполнения сработает исключение, и процесс будет закрыт. Этот путь - самый простой для выхода из программы. Ручная проверка байта BeingDebuged позволит обойти приличное количество антивирусных продуктов, но в некоторых приложениях предусмотрен подобный случай, и мы должны сделать обфускацию кода, чтобы защититься от статического анализа строк.
Рисунок 10: Функция, разбавленная мусорными инструкциями
Добавление переходов (инструкций JZ) после каждой операции не повлияет на логику работы функции, но добавление мусорных байтов между переходами позволит обойти фильтры статического анализа строк.
9.2 Загрузка поддельной библиотеки
В этом методе мы попытаемся загрузить несуществующую библиотеку во время выполнения приложения. Обычно когда мы пытаемся загрузить несуществующую библиотеку, HISTENCE возвращает NULL, но некоторые механизмы динамического анализа, используемые в антивирусных продуктах, допускают такие случаи и продолжают дальнейшее исследование потока выполнения приложения.
Рисунок 11: Загрузка поддельной библиотеки
9.3 Использование функции GetTickCout()
В этом методе мы будем опираться на тот факт, что время, которое отводится на сканирование, ограничено. В большинстве случаев антивирусные сканеры созданы для конечного пользователя. Соответственно, приложение должно быть как можно более дружелюбно и пригодно для ежедневного использования. Сей факт означает, что сканер не может тратить слишком много времени на сканирование файлов, а должен завершать весь процесс как можно быстрее. Вначале разработчики вредоносов использовали функцию «sleep()» с целью дождаться окончания сканирования, но сейчас этот трюк практически не работает, поскольку каждый антивирусный продукт пропускает данную функцию. Мы будем использовать другой метод, показанный на рисунке ниже, который использует API-функцию «GetThickCount()». Данная функция извлекает количество миллисекунд, прошедших с момента запуска системы (до 49.7 дней). Мы будем использовать эту функцию для получения времени, прошедшее с момента запуска операционной системы, затем остановимся на 1 секунду. После функции sleep мы проверим, был ли этот участок пропущен, при помощи сравнения двух значений, полученных при помощи GetTickCout().
Рисунок 12: Проверка на то, была ли пропущена функция sleep()
9.4 Проверка количества ядер
Этот метод проверяет количество ядер процессора, присутствующего в системе. Поскольку антивирусные продукты не могут занимать слишком много ресурсов, мы можем проверить количество ядер для того, чтобы определить, находимся ли мы в песочнице. Многие антивирусы не поддерживают мульти-ядерную обработку и не могут зарезервировать более одного ядра для песочницы.
Рисунок 13: Проверка количества ядер процессора
9.5 Выделение большого объема памяти
Этот метод также эксплуатирует ограниченное время, используемое при сканировании. Мы выделяем около 100 Мб памяти и заполняем данное пространство пустыми байтами. Затем память освобождается.
Рисунок 14: Выделение, заполнение и освобождение большого объема памяти
Когда память, используемая программой, во время выполнения начинает расти, через некоторое время антивирусные сканеры останавливаются, чтобы не тратить много времени на анализ файла. Этот метод можно использовать несколько раз. Данная техника очень стара и примитивна, но все еще помогает обойти приличное количество сканеров.
9.6 Манипуляция флагом трассировки
Как следует из названия, этот флаг используется во время трассировки программ. Если флаг трассировки установлен, каждая инструкция будет инициировать исключение «SINGLE_STEP». Флагом трассировки можно манипулировать с целью противодействия трассировщиками при помощи кода, показанного на рисунке ниже:
Рисунок 15: Манипуляция флагом трассировки
9.7 Проверка мьютексов
Этот метод из-за своей простоты выглядит очень перспективным. Суть техники заключается в проверке присутствия определенного мьютекса в системе.
Рисунок 16: Проверка мьютекса
Если функция «CreateMutex» не вернет ошибку ERROR_ALREADY_EXISTS, вредонос запускается еще раз, поскольку большинство антивирусных продуктов не позволяют программам, которые подвергаются динамическому анализу, запускать новые процессы или работать с файлами вне песочницы. Если по результатам проверки мьютекса возвращается ошибка ERROR_ALREADY_EXISTS, можно запускать функцию дешифровки. Существуют намного более креативные способы использования мьютексов с целью анти-детектирования.
10. Правильные методы выполнения шелл-кода
Начиная с Windows Vista, компания Microsoft внедрила технологию DEP (Data Execution Prevention; Предотвращение выполнения данных). Эта мера, направленная на укрепление безопасности, помогает предотвратить порчу компьютера путем периодического мониторинга. Мониторинг позволяет поддерживать корректно использование памяти. Если механизм DEP обнаруживает случай некорректного использования памяти вашего компьютера, программа закрывается, и пользователь оповещается. Соответственно, вы не можете просто поместить несколько байт в символьный массив и выполнить свое творчество. Вам нужно выделить область памяти с флагами на чтение, запись и выполнения при помощи API-функций.
Компания Microsoft предусмотрела несколько API-функций для резервирования страниц памяти. Большинство вредоносов для резервирования используют функцию «VirtualAlloc», что, как вы уже догадались, облегчает задачу по детектированию. Использование других функций для манипуляции памятью поможет выполнить тот же самый трюк более незаметно.
Далее я покажу методы выполнения шелл-кода при помощи различных API-функций.
10.1 HeapCreate/HeapAlloc:
ОС Windows также позволяет создать кучи с атрибутами на чтение, запись и выполнение.
Рисунок 17: Копирование шелл-кода в созданную кучу
10.2 LoadLibrary/GetProcAddress
Комбинация WINAPI-функций LoadLibrary и GetProcAddress позволяет использовать другие API-функции. В этом случае не будет прямого вызова функций, связанных с манипуляцией памяти, а сам вредонос, вероятно, будет более незаметен.
Рисунок 18: Пример использования функций LoadLibrary и GetProcAddress
10.3 GetModuleHandle/GetProcAddress
Этот метод вовсе не использует функцию LoadLibrary, а получает обработчик уже загруженного модуля kernel32.dll при помощи функции GetModuleHandle. Данная техника для запуска шелл-кода, возможно, одна из самых незаметных.
Рисунок 19: Пример использования функций GetModuleHandle/GetProcAddress
11. Мультипоточность
PE-файлы, использующие несколько потоков, всегда сложнее анализировать, в том числе и для антивирусных продуктов. При помощи мультипоточного подхода мы можем запускать шелл-код и одновременно продолжать выполнение функции «AV Detect».
Рисунок 20: Пример выполнения шелл-кода в отдельном потоке
В коде, показанном выше, выполнение шелл-кода происходит в отдельном потоке. Параллельно выполнению шелл-кода в бесконечном цикле выполняется функция для обхода антивируса. Такой подход позволяет проверять присутствие песочницы и динамического анализа, что жизненно важно для обхода продвинутых эвристических движков, которые дожидаются запуска шелл-кода.
12. Заключение
В конце статьи хотелось вы поделиться некоторыми мыслями относительно компиляции вредоносов. При компиляции исходных текстов необходимо включить защиту стека и удалить символы с целью затруднения реверс-инжиниринга и уменьшение размера конечного исполняемого файла. Рекомендуется компилировать в Visual Studio из-за присутствия ассемблерных вставок.
Если воспользоваться одновременно всеми методами, продемонстрированными в этой статье, сгенерированный вредонос сможет обойти 35 наиболее продвинутых антивирусных продукта.
Рисунок 21: Результаты проверки вредоноса
Источник:
Вы должны зарегистрироваться, чтобы увидеть внешние ссылки
Часть два сейчас будет ! :)