June 13, 2020

Эмуляция, антиэмуляция, детект и крипторы

Всем привет.

Как-то давно, месяца три назад, решил написать статейку в Inception #4 - вышел!

Но т.к. статья была направлена больше на новичков и получилось как-то скомкано всё, а может и по другой какой-то причине, статья не попала в журнал.

Но т.к. я потратил несколько часов на её написание, пусть будет тут. Может кому-то окажется полезным.

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

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

Для кого-же эта статья и что вы можете узнать в случае её прочтения:

  • Статья имеет практическое направление. Мы на примере разберём исходники, которые детектят антивирусы. Попробуем разобраться на что именно идёт детект, как этот детект убрать и т.д.
  • Статья может-быть полезна новичкам, т.к. мы не только в теории, но и на практике разберём способы скрытия кода от эмулятора, даже напишем простенький протектор, хотя это и не цель этой статьи.
  • Статья имеет исследовательское направление, посмотрим какие антивирусы отвалятся, а какие нет. Ну и сделаем выводы небольшие.
  • Все приёмы которые будут написаны здесь, можно использовать на любых языках программирования, также постараюсь расписать всё доступным для любого человека языком (даже не программиста). Все примеры-же будут на «классическом языке си», даже без ООП. Т.к. этот язык очень подходит для таких задач и без всяких наворотов. :)

Итак приступим:

Часть первая. Введение и теория:

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

1. Что такое сигнатурный детект и принцип действия:

Тут всё и просто, и сложно. Сигнатура может-быть по определённой контрольной сумме, например антивирус считает контрольную сумму файла (CRC32, MD5 и т.д.), и в случае если такая сумма совпала в базе. То выдаётся детект.

Это называется «точечный детект» и используется часто в «облаках», что позволяет быстро выявить угрозу и нейтрализовать её.

Но а действительно удобно, та-же автоматика может задетектить угрозу по каким-то признакам и быстро добавить контрольную сумму в базу.

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

В своё время мы даже делали исследование в журнале «Хакер», какие антивирусы очень-уж страдают таким детектом, забывая про другие сигнатуры и эвристику. Если интересно, то вот ссылка на ту статью: X-тест на октябрьском журнале "Хакер"

Нельзя не сказать, что могут-быть и «интеллектуальные сигнатуры», которые так просто не обойти, вообще по моему мнению такие сигнатуры как-бы и должны-быть в основном, а не по контрольной сумме.

Многие антивирусы, а особенно такие как нод, могут делать детект по каким-то кускам кода. Например, может-быть детект на какую-то определённую API в коде, или процедуру. Часто встречал, что антивирусы реагируют на определённые параметры в функции или API.

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

Также необходимо сказать, для тех-кто любит так-называемые стабовые крипторы (об этом ниже), антивирусы без труда добавляют эти стабы в свои базы и все ваши криптованные файлы, как говорит один мой знакомый, «Идут по пи***», через достаточно короткий промежуток времени !

Ну на этом пока закончим, т.к. разговаривать про детект можно долго и много чего ещё не сказано, но для общего понимания вопроса, думаю этого достаточно !

2.Что такое эмуляция кода:

Ну вот мы и добрались, ко второму типу детекта, это эмуляция кода.

По простому, антивирус исполняет ваш код в момент сканирования и если находит какие-то касяки, например как уже-было сказано «Непонравилась API» или обнаружена сигнатура вируса, или кусок такой сигнатуры, то будет детект.

Ещё пример (мы этот пример разберём ниже, чуть позже), вы можете написать протектор, который например из дата-секции будет брать шифрованный вирус, а далее расшифровывать его, например в какой-то буфер в оперативной памяти. Казалось-бы, вирус шифрованый и антивирус его не должен детектить.

А-нет, эмулятор расшифрует код и задетектит вирус. В общем-то для этого и нужен эмулятор кода.

3.Что такое детект при запуске:

Всё-что выше, мы разбирали так-называемый «статический детект», т.е. детект, который выдаётся до запуска файла, такой детект будет если вы просканируете файл сканером, или зальёте например на вирустотал и т.д.

Но нужно ещё сказать и про динамический детект. Это детект при запуске файла. Как он формируется ?

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

Ну тут понятно, что даже при обходе сигнатурного детекта и эмулятора, если вирус по тупому крадёт пароли, создаёт скрытые процессы, всё это пересылает по сети, делает хук на клавиатуру, да ещё и добавляет себя в автозагрузку, то думаю детект не заставит себя ждать !

К тому-же, у многих антивирусов, у таких как нод и др. веб, есть так называемый детект в памяти, т.е. антивирус расшифровывает код до запуска процесса и проверяет его. Да это проблема, т.к. перед запуском нового процесса код должен-быть расшифрован.

4. Способы обхода:

Если детект по конрольной сумме, то непроблема добавить один байт к исполняемому файлу, в любом HEX-редакторе.
Если более сложный детект, то давайте рассматривать различные приёмы и ситуации.

4.1. Используем криптор, для обхода детекта:

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

Есть крипторы, как-бы являющиеся частью вируса, например вирус может содержать в дата-сакции какой-то кусок шифрованного кода, есть вероятность «проскользнуть» через антивирус, расшифровав этот код и запуститив его. Именно так запускают и шелл-коды с уязвимостями.
Теперь про дерьмовые крипторы, по сути это стабы (отдельная программа, которая отвечает за расшифровку и запуск вируса в памяти), которые идут отдельно от вируса, что представляет из себя такой криптор ?

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

Мы эти два типа криптора рассмотрим в практической части, но всё-же цель этой статьи показать на примере работу эмулятора и антиэмулятора, а также приёмы чистки исходников, нежели написание качественного криптора/протектора.

4.2. Если есть исходник вируса, то используем приёмы антиэмуляции для скрытия кода:

Хорошо, если есть исходники вируса, тем самым нужно определить, что ненравится антивирусу, а дальше не проблема это обойти. Как это сделать.
Вот предположим есть у вас исходник, после сборки идёт детект.

Для начала, нужно понять что именно срабатывает эмулятор или сигнатура, на какой-то кусок кода.

Для этого я например в самое начало програмы ставилю while (1), это бесконечный цикл, пример:

main (){while (1);bad_code ();}

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

Что делать дальше ?

Дальше, если на кусок кода поставлена сигнатура, то можно разбавить его мусором, например был у меня случай, когда нод детектил функцию запуска кода в памяти run(LPSTR szFilePath, PVOID pFile) (Ниже мы разберём алгоритм этой функции). Так-вот для обхода такого детекта, просто поставил Sleep(1000) и детект пропал. Но это как пример, можно делать какие-то вычисленя, циклы и т.д.

Что делать, если детектит эмулятор кода ?

Для начало нужно опять-таки определить, на что детектит антивирус. Тут как-раз подойдёт тот-же while (1).
Нужно ставить его выше кода, начиная например с начала программы и пока не появится детект, пример:

main (){while (1);bad_code ();}

Если убрать while (1) и будет детект, значит детект в функции bad_code (). Далее нужно залезать в эту функцию и трассировать там. Либо перед вызовом этой функции использовать приёмы антиэмуляции.

Ну вот вы нашли функцию/API на которую идёт детект, что делать дальше ?

А дальше нужно и использовать приёмы антиэмуляции, на чём они основаны:

Понятно, что ресурсы антивируса ограничены, иначе-бы проверка одного файла занимала часы, а-то и проверка программы с например while (1), вообще могла повиснуть.

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

Вообще можно придумать много способов антиэмуляции, вот краткая часть:

- Выделение большего куска памяти и работа с ней;

- Долгие вычисления и нагрузка на процессор;

- Какие-то действия, что не может сделать эмулятор.

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

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

#define TOO_MUCH_MEM 100000000int main( void ){char * memdmp = NULL;memdmp = (char *) malloc(TOO_MUCH_MEM);

if( memdmp != NULL ){memset(memdmp, 00, TOO_MUCH_MEM);free(memdmp);

//Тут код, который нужно скрыть от АВ

}return 0;}

На самом деле эмулятор зайдёт в две ветки, даже если будет memdmp = NULL он доберётся до вредоносного кода.:(

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

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

Несколько примеров мы рассмотрим в практической части.

Также если уяснили суть, сами можете придумать свою уникальную антиэмуляцию для своих программ.

Часть два. Практическая часть:

1)Обходим детект при запуске:

Вообще много-чего не сказано, но итак уже много текста, не знаю дочитают-ли до сюда. Но тут уже будет интересней.

Итак я говорил про детект при запуске, тут-бы хорошо морфить код. Т.е. как-то менять код перед запуском, тогда даже при расшифровке, должны отваливалиться сигнатуры.

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

Если вы имеете исходник шелла, или вируса. То можете поменять код перед запуском, добавив различные «мусорные» инструкции.

Также иногда помогают и легальные протекторы, например что-то типо енигмы (The Enigma Protector - профессиональная система защиты и лицензирования программного обеспечения).

Причём интересно, что многие антивирусы детектят сам пртектор, пример упакованный вирус:

Детект на енигму (запакованный келоггер)

Но после перепаковки в виртуальную машину (The Enigma Protector - профессиональная система защиты и лицензирования программного обеспечения), детект уже такой:

Перепакованная в виртуальную машину енигма (Тот-же защищенный кейлоггер)
​Как видите такие антивирусы, как касперский, нод и Ко отвалились.

Я-бы не сказал что этот способ универсален для всех вирусов, но для многих весьма не плох. :)
2)Уменьшаем вероятность попадание вируса в «облако»:

Как я уже сказал, что «облачный» детект часто это детект по контрольной сумме, не на самом деле там много чего ещё, например репутация программ, проверка на сервере и т.д.

Но расскажу про трюк, который применяют многие малварьщики на практике. Это упаковка вируса в самораспаковывающийся архив, например в винрар.

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

3)Обходим эмулятор и сигнатурный детект на примере своего простенького протектора/криптора:

В общем многие наверное знают испанские форумы. Где выкладывают крипторы: Типо гуй, который шифрует и запаковывет файл в оффсет (свободное место в бинарнике), или в ресурсы, а на выходе получаем файлик, который никто не детектит, по крайне-мере сигнатурно.

Давайте разбираться как это работает, заодно и посмотрим как обойти эмулятор.

Итак что такое криптор, я уже написал в теоретической части.

Но немного распишу алгоритм стаба здесь:

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

Как происходит запуск в памяти:

Нужно "порадить новый процесс", как это сделать:

- Например при помощи API - винды CreateProcess, создать процесс;

- Выделить для него память, при помощи VirtualAllocEx;

- Записываем наш расшифрованный вирус (Расшифрованный массив байт) в выделенную область памяти при помощи WriteProcessMemory.

- Далее нужно установить контектс потока, при помощи SetThreadContext и запустить его при помощи ResumeThread.

Пример как сделать антиэмуляцию:

Перед раскриптовкой нужно обязательно сделать антиэмуляцию, иначе антивирус дойдёт до расшифрованного файла и задетектит его в памяти, даже до запуска, при сканировании !

С приёмами антиэмуляции мы ознакомились в теоретической части, можете придумать свои, но давайте посмотрим на пример, который придумал я (а может уже и было до меня):

1)Выделяются два больших буфера в памяти (Оба по 500 мегабайт).

2)Далее один буфер заполняется "мусором".

3)Этот "мусор" записывается в файл, во временный каталог пользователя, т.е. получим файл в 500 мегабайт с мусором.

4)Далее считываем содержимое файла во второй буфер, т.е. то-что мы выделили в первом пункте.

5)Проверяем эти два куска памяти на равенство, если они равны, идём дальше.

6)Во втором буфере формируем ключ для расшифровки.

7)Расшифровываем и запускаем в памяти.

Вообще приложил исходник простенького протектора, краткое описание и алгоритм (его можно скачать в ресурсах):

Гуй (Kryptor.exe) формирует выходной фай:

  • Случайным образом генерирует ключ;
  • Криптует этим ключем, алгоритмом XTEA;
  • Ложит шифрованный файл в оффсет;
  • Далее в оффсет ещё ложит 8 байт данных (перед шифрованным файлом): Первые 4-ре байта это размер стаба, вторые четыре байта это случайный номер в массиве, при генерации огромного куска памяти, нужно для генерации ключа и расшифровки (сам ключ в файле не хранится).
  • Вроде всё, если ничего не забыл.

Что делает стаб (stab.exe):

  • Выделяются два больших буфера в памяти (Оба по 500 мегабайт).
  • Далее один буфер заполняется «мусором».
  • Этот «мусор» записывается в файл, во временный каталог пользователя, т.е. получим файл в 500 мегабайт с мусором.
  • Далее считываем содержимое файла во второй буфер, т.е. то-что мы выделили в первом пункте.
  • Проверяем эти два куска памяти на равенство, если они равны, идём дальше.
  • Во втором буфере формируем ключ для расшифровки, на основании случайного номера, который сгенерировал креатор (гуй). J
  • Выделаем шифрованный файл, расшифровываем и запускаем в памяти.

Ограничения:

Криптовать можно только x32 RunPE.
.NET криптовать нельзя.

Что в архиве:

Kryptor.exe – Гуй для криптовки.

Kryptor_Stub – Стаб.

putty_crypt.exe – Криптованный Putty, для теста.

putty.exe – Оригинал.

Сорцы (src):

Form1.h – Исходник гуя, на вижуалке можно запихнуть.
Kryptor_Stub.cpp – Исходник стаба.

Реакция антивирусов:

Сервер дарк-комет до упаковки:

Результат скана дарк-комета

После упаковки:

Результат после обработки протектором

Как видите многие отвалились.

ВАЖНО: Цель этотй статьи просто показать способы обхода эмулятора антивируса, а не научить писать подобного рода крипторы. Не забываем про детект в памяти, конкретно в этом крипторе нет обхода детекта в памяти.

Данный способ лучше применять в исходниках, т.к. профита больше будет !

Что можно сделать и как улучшить ?

Если кто-то заинтересовался этой работой и хочет взять проект как рыбу, вот-что рекомендую сделать:

  • Можно подключить енигму к себе в проект и морфить код, да-да этот протектор можно подключать к своим сорцам, можете поразбираться с этим.
  • Морфить код самим, как это делать выходит за рамки этой статьи. В качестве примера можно ещё посмотреть, так-называемые JIT – компиляторы (Возможно напишу статью про это).
  • Ложить файл не в оффсет, а в дата-секцию, тем самым скорей-всего отвалится детект авиры.
  • Добавить возможность делать иконку файлу.
  • Можно использовать другие методы антидетекта.

ВЫВОДЫ:

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

Встаёт вопрос нужен-ли антивирус вообще ?

Я сейчас думаю, что в целом он не повредить, но надеятся только-лишь на антивирусное решение глупо.

Если это предприятие, то как минимум должно-быть: Отдельные беккапы, грамотно настроенная система/сеть. Короче нужен нормальный админ и продуманная структура безопасности.

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

У меня всё, благодарю за прочтение этой статьи.

Надеюсь кто-то вынесет из этой статьи что-то полезное.

Всем добра ! :)