• XSS.stack #1 – первый литературный журнал от юзеров форума

Статья F#ck da Antivirus. Как обходить антивирус при пентесте

baykal

(L2) cache
Пользователь
Регистрация
16.03.2021
Сообщения
370
Реакции
838
Антивирус — крайне полезная штука, но только не тогда, когда тебе нужно остаться незамеченным в атакуемой сети. Сегодня мы поговорим о том, как при пентесте можно обмануть антивирусные программы и избежать обнаружения в скомпрометированной системе.

Эта статья — продолжение цикла публикаций, посвященных постэксплуатации. В предыдущих сериях:
  • Шпаргалка по persistence. Как надежно прописаться на хосте или выявить факт компрометации;
  • Кунг‑фу pivoting. Выжимаем максимум из постэксплуатации;
  • Гид по Lateral. Изучаем удаленное исполнение кода в Windows со всех сторон;
Наверняка тебе знакома ситуация, когда доступ к атакуемой сети получен и до цели остался один шаг. Но антивирус не дает выполнить нужное действие, запустить ту или иную программу. В моей практике были случаи, когда имелась лишь одна попытка, которая проваливалась из‑за поднятой антивирусом тревоги. Антивирусы иногда могут удалить и совсем безобидные файлы, причем не только исполняемые. Поэтому скрыть настоящую угрозу — крайне непростая задача.

Вообще, задача обхода антивируса может возникнуть в двух случаях:
  • при атаке. Тут полезную нагрузку запускает либо уязвимое приложение, либо, что чаще, пользователь (социальная инженерия). Главным образом это будет некое средство закрепления и обеспечения постоянного присутствия. Самое важное — не спалиться;
  • при постэксплуатации. В этом случае мы сами запускаем программу на скомпрометированной системе. Это может быть сниффер, средство повышения привилегий или просто какой‑то хакерский софт, используемый для продвижения по сети. И при этом для нас важнее запустить программу, даже если это получилось не с первой попытки и антивирус выкинул несколько алертов.
В первом случае достаточно лишь применить известную технику — полиморфизм. Изменяем код, не меняя его функциональность. Хорошая тактика — написать код самому. Например, с помощью двадцати строк кода на VBS можно реализовать простой reverse shell и успешно обойти любой антивирус. Нас же больше будет интересовать второй случай, и именно это и будет темой данной статьи.

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

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

По большому счету мы имеем дело с двумя механизмами защиты:
  • сигнатурным;
  • эвристическим (поведенческим).
При сигнатурном анализе антивирус учитывает множество факторов, в частности большое влияние на результат оказывает компилятор. Вот достаточно забавные результаты VirusTotal для безобидного helloworld-приложения, написанного на С:
  • i686-w64-mingw32-gcc — 11/68 детектов;
  • msvc — 2/64 детекта;
  • win-gcc — 0 детектов.
Что же касается анализа поведения программы, тут нужно понимать, что, даже если тебе удалось обойти сигнатуры, ты все еще можешь спалиться, поскольку всякий migrate PID или sekurlsa::logonPasswords может быть перехвачен по причине использования характерных сочетаний WinAPI-функций, которые антивирусы очень внимательно мониторят.

Я предлагаю сосредоточиться именно на сигнатурном движке, обхода которого для большинства случаев достаточно. В статье не будет прямых упоминаний конкретных названий антивирусов, чтобы не создавать никому рекламы или антирекламы. В то же время мы не станем «затачиваться» под конкретный антивирус. Результаты будем проверять именно на работающем антивирусе, при этом попробуем использовать некий универсальный способ, чтобы каждый раз не придумывать все новые методы обхода. В каждом случае целью будет тайком протащить на скомпрометированную машину Meterpreter, который позволит нам исполнить в памяти что угодно, запустить весь имеющийся в нашем распоряжении хакерский арсенал.

LEGAL​

Лучший бой — это тот, которого удалось избежать. Поэтому в борьбе с антивирусами часто используются легальные средства. Да, они не могут предоставить многие «продвинутые штуки», но необходимый минимум в виде reverse shell при persistence и lateral movement, а также встроенный прокси‑сервер и гибкую систему редиректа трафика при pivoting они реализовать могут. И это замечательные, всем известные утилиты — nc.exe, ncat.exe, socat.exe, plink.exe. Примеры их использования были описаны в моих прошлых статьях. Блеки же и вовсе порою используют обычные средства облачного удаленного администрирования вроде RMS.

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

SHELLCODE INJECTING​

Техника встраивания кода в уже запущенные, а значит, прошедшие проверку процессы широко известна. Идея состоит в том, что мы имеем отдельную программу shellcode_inject.exe и сам shellcode в разных файлах. Антивирусу сложнее распознать угрозу, если она раскидана по нескольким файлам, тем более по отдельности эти файлы не представляют угрозы.

shellcode_inject.exe не содержит угроз

shellcode_inject.exe не содержит угроз

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

Cоздание автономного (не staged) шелл-кода. Выглядит безобидно

Cоздание автономного (не staged) шелл‑кода. Выглядит безобидно

Глядя на содержимое meter.txt, я бы скорее решил, что это строка в Base64, чем шелл‑код.

Стоит отметить, что мы использовали шелл‑код meterpreter_reverse_tcp, а не meterpreter/reverse_tcp. Это автономный код, который содержит в себе все функции Meterpreter, он ничего не будет скачивать по сети, следовательно, шансов спалиться у нас будет меньше. Но вот связка shellcode_inject.exe и meter.txt уже представляет опасность. Давай посмотрим, сможет ли антивирус распознать угрозу?

Инжект Meterpreter

Инжект Meterpreter

Обрати внимание: мы использовали для инжекта кода системный процесс, он сразу работает в контексте System. И похоже, что наш подопытный антивирус хоть в конце и ругнулся на shellcode_inject.exe, но все же пропустил данный трюк.

Выполнение вредоносного кода в обход антивируса

Выполнение вредоносного кода в обход антивируса

Запустив что‑то вроде Meterpreter, атакующий получит возможность выполнить полезную нагрузку прямо в памяти, минуя тем самым HDD.

Запуск вредоносного ПО через Meterpreter в памяти

Запуск вредоносного ПО через Meterpreter в памяти

Много лет этот простой трюк выручал меня. Сработал он и на этот раз.

CODE CAVES​

Каждый exe-файл (PE-формат) содержит код. При этом весь код оформлен в виде набора функций. В свою очередь, функции при компиляции размещаются не одна за другой вплотную, а с некоторым выравниванием (16 байт). Еще большие пустоты возникают из‑за выравнивания между секциями (4096 байт). И благодаря всем этим выравниваниям создается множество небольших «кодовых пустот» (code caves), которые доступны для записи в них кода. Тут все очень сильно зависит от компилятора. Но для большинства PE-файлов, а нас главным образом интересует ОС Windows, картина может выглядеть примерно так, как показано на следующем скриншоте.

Графическое представление расположения функций и пустот между ними

Графическое представление расположения функций и пустот между ними

Что представляет собой каждая такая «пустота»?

Code cave

Code cave

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

Поиск кодовых пустот в исполняемом файле

Поиск кодовых пустот в исполняемом файле

Визуальное расположение пустот по всему файлу

Визуальное расположение пустот по всему файлу

В этом примере мы поискали только 12-байтные пустоты, так что реальное их количество будет гораздо большим. Пустот хоть и немало, но их явно недостаточно для размещения полноценной программы. Поэтому данный способ годится только для вставки шелл‑кодов, размер которых редко превышает 1 Кбайт.

Давай посмотрим, сможем ли мы разложить небольшой многоступенчатый Windows/Meterpreter/reverse_tcp шелл‑код по этим пустотам. Размер code cave редко превышает 16 байт, так что нам потребуется разбивать шелл‑код сильнее, чем по базовым блокам. Следовательно, придется вставлять еще и дополнительные jmp-инструкции для их связи и корректировать адреса условных переходов. На деле это достаточно рутинная операция.

Фрагментация и вставка шелл-кода в code caves

Фрагментация и вставка шелл‑кода в code caves

py2.png

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

Исходный исполняемый файл — пустота между функциями из-за выравнивания

Исходный исполняемый файл — пустота между функциями из‑за выравнивания

Модифицированный исполняемый файл — зараженная пустота между функциями

Модифицированный исполняемый файл — зараженная пустота между функциями

По идее, такой подход должен дать нам полиморфизм, так как каждый раз шелл‑код будет помещаться в случайные пустоты по две‑три инструкции (это называется умным словом «пермутация»). Даже на уровне трассы исполнения код будет обфусцирован из‑за достаточно большого количества инструкций jmp между фрагментами.

Оригинальная трасса исполнения шелл-кода

Оригинальная трасса исполнения шелл‑кода

Обфусцированная трасса исполнения шелл-кода

Обфусцированная трасса исполнения шелл‑кода

C помощью этого способа мы можем обойти таким образом много «простых» антивирусов.
Выполнение вредоносного шелл-кода в обход антивируса

Выполнение вредоносного шелл‑кода в обход антивируса

2meter.png

Однако серьезные антивирусные продукты таким трюком все же не проведешь.

CRYPT​

Как ни странно, классический xor исполняемого файла с динамическим ключом все еще успешно работает против даже самых грозных антивирусов. Для примера возьмем какой‑нибудь очень палевный исполняемый файл и закриптуем простым xor все, что только можно. Крипт секций .text и .data выглядит примерно так.

Xor с ключом 0x77 указанных адресов и размеров

Xor с ключом 0x77 указанных адресов и размеров

Теперь спрячем информацию о версии.
Шифрование ресурсов исполняемого файла

Шифрование ресурсов исполняемого файла

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

Поиск области, содержащей строки, и ее шифрование

Поиск области, содержащей строки, и ее шифрование

Поскольку мы все зашифровали в исполняемом файле, то в момент запуска его смещения в коде не будут правильно скорректированы в соответствии с адресом размещения. Поэтому еще придется отключить ASLR:
Код:
PE->NT headers->Optional header->DllCharacteristics |=0x40
Теперь самое время проверить, что мы все спрятали, и наш mimikatz больше не вызывает подозрений.
Все вредоносное содержимое успешно скрыто

Все вредоносное содержимое успешно скрыто

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

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

Наш xor_stub.asm будет сохранять начальное состояние и добавлять права на запись в зашифрованные секции.

Изменение permissions зашифрованных секций

Изменение permissions зашифрованных секций

Здесь и далее не забудь изменить адреса WinAPI-функций на свои значения. Теперь мы скорректируем точку входа, так как в нее чуть позже будет вставлен jump на данный код.
Восстановление инструкций точки входа

Восстановление инструкций точки входа

Запросим у пользователя ключ для расшифровки и выполним де-xor всех зашифрованных областей.
Запрос ключа и расшифровка им заксоренных областей

Запрос ключа и расшифровка им заксоренных областей

Наконец мы восстанавливаем начальное состояние, корректируем стек и перемещаемся в entry point.
Возврат на точку входа

Возврат на точку входа

Самое время добавить пустую секцию r-x в mimikatz, куда мы разместим наш xor_stub.
Добавление секции, куда будет вставлен код расшифровки

Добавление секции, куда будет вставлен код расшифровки

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

Компиляция и вставка кода для расшифровки

Компиляция и вставка кода для расшифровки

В конце не забудем из entry point сделать jump на наш код.
Передача управления на код расшифровки

Передача управления на код расшифровки

Готово. Запускаем и вводим ключ, которым мы шифровали, — в моем случае это символ w (0x77).
Выполнение вредоносного кода в обход антивируса

Выполнение вредоносного кода в обход антивируса

Вот и все. Немного автоматизировав данный процесс, попробуем запустить Meterpreter.

Автоматическое шифрование описанным способом

Автоматическое шифрование описанным способом

Запускаем и вводим ключ w.

Запуск вредоносного кода

Запуск вредоносного кода

И получаем тот же эффект.
Запуск вредоносного кода в обход антивируса

Запуск вредоносного кода в обход антивируса

VULN INJECT (SPAWN)​

Мне хорошо запомнился один давний случай. Я никак не мог открыть сессию Meterpreter на victim из‑за антивируса, и вместо этого мне каждый раз приходилось заново эксплуатировать старую добрую MS08-067, запуская Meterpreter сразу в памяти. Антивирус почему‑то не мог помешать этому.

Думаю, антивирус главным образом заточен на отлов программ (на HDD) и шелл‑кодов (по сети) с известными сигнатурами или на эксплуатацию популярных уязвимостей. Но что, если уязвимость еще неизвестна для антивируса?

В этом случае используется достаточно необычная техника, которая строится на принципе внедрения уязвимости (buffer overflow) и, тем самым, неочевидном исполнении произвольного кода. По сути, это еще один вариант рефлективного исполнения кода, то есть когда код присутствует исключительно в RAM, минуя HDD. Но в нашем случае мы еще и скрываем точку входа во вредоносный код.

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

Чтобы проверить это, нам нужно написать и запустить простенький сетевой сервис, содержащий придуманную нами 0-day-уязвимость (buffer overflow на стеке) и проэксплуатировать ее. Чтобы все работало еще и в новых версиях Windows, придется обойти DEP. Но это не проблема, если мы можем добавить нужные нам ROP-gadgets в программу.

Не будем глубоко вдаваться в детали buffer overflow и ROP-chains, так как это выходит за рамки данной статьи. Вместо этого возьмем готовое решение. Компилируем сервис обязательно без поддержки ASLR (и можно без DEP):
Код:
cl.exe /c vuln_rop.c
link.exe /out:vuln_rop.exe vuln_rop.obj /nxcompat:no /fixed
Наш сетевой сервис будет работать в режимах listen и reverse connect. А все данные будут передаваться в зашифрованном виде, чтобы не спалиться на сигнатурном анализаторе. И это очень важно, поскольку некоторые антивирусы настолько «не любят» Meterpreter, что даже простая его отправка в любой открытый порт спровоцирует неминуемый алерт и последующий бан IP-адреса атакующего.

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

Запуск сервиса с заложенной нами buffer overflow

Запуск сервиса с заложенной нами buffer overflow

Наш уязвимый сервис успешно запущен и ждет входящих данных. Создаем payload и запускаем эксплоит с ним.

Эксплуатация нашего buffer overflow

Эксплуатация нашего buffer overflow

В итоге уязвимый сервис принимает наши данные и в результате заложенной ошибки при работе с памятью непроизвольно запускает код payload.
Переполнение буфера в действии

Переполнение буфера в действии

Эта полезная нагрузка открывает нам сессию Meterpreter.
Выполнение вредоносного кода в обход антивируса

Выполнение вредоносного кода в обход антивируса

И все это безобразие происходит при работающем антивирусе. Однако было замечено, что при использовании некоторых антивирусов все еще срабатывает защита. Давай порассуждаем, почему. Мы вроде бы смогли внедрить код крайне неожиданным способом — через buffer overflow. И в то же самое время по сети мы не передавали код в открытом виде, так что сигнатурные движки не сработали бы. Но перед непосредственным исполнением мы получаем в памяти тот самый машинный код. И тут, по‑видимому, антивирус и ловит нас, узнавая до боли знакомый Meterpreter.

Антивирус не доверяет exe-файлу, скачанному неизвестно откуда и запущенному первый раз. Он эмулирует выполнение кода и достаточно глубоко анализирует его, возможно даже на каждой инструкции. За это приходится платить производительностью, и антивирус не может позволить себе делать так для всех процессов. Поэтому процессы, уже прошедшие проверку на этапе запуска (например, системные компоненты или программы с известной контрольной суммой), работают под меньшим надзором. Именно в них мы и внедрим нашу уязвимость.

VULN INJECT (ATTACH)​

Самый простой и удобный способ выполнить код в чужом адресном пространстве (процессе) — инжект библиотеки. Благо DLL мало чем отличается от EXE и мы можем перекомпилировать наш уязвимый сервис в форм‑фактор библиотеки, просто изменив main() на DllMain():
Код:
cl.exe /c vuln_rop.c
link.exe vuln_rop.obj /out:vuln_rop.dll /dll /nxcompat:no /fixed
Для максимальной переносимости я использую 32-битные программы, поэтому внедрять уязвимость нам придется в 32-разрядные процессы. Можно взять любой уже запущенный или запустить самому. На 64-битной Windows мы всегда можем найти 32-битные системные программы в c:\windows\syswow64.

Запуск 32-битного процесса

Запуск 32-битного процесса

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

Инжект библиотеки в только что запущенный 32-битный системный процесс

Наша DLL без ASLR успешно загружена по стандартному адресу.

Уязвимая DLL загружена

Уязвимая DLL загружена

И теперь целевой процесс с занесенным buffer overflow готов получать данные по сети.
Уязвимый код начал работать в контексте легитимного процесса

Уязвимый код начал работать в контексте легитимного процесса

Поскольку наш уязвимый модуль загрузился по адресу 0x10000000 (это дефолтный адрес для не ASLR-библиотек), нужно слегка скорректировать код эксплоита.
Небольшая корректировка кода эксплоита

Небольшая корректировка кода эксплоита

Время запустить сам эксплоит.
Запуск эксплоита

Запуск эксплоита

В контексте легитимного процесса происходит overflow.
Переполнение буфера в легитимном процессе в действии

Переполнение буфера в легитимном процессе в действии

И мы исполняем «вредоносный» код в обход антивируса.
Выполнение вредоносного кода в обход антивируса

Выполнение вредоносного кода в обход антивируса

ВЫВОДЫ​

Мы использовали эффект «неожиданного» исполнения кода в памяти через 0-day-уязвимость, антивирус не смог ее спрогнозировать и заблокировать угрозу. Загрузка DLL в чужой процесс — трюк достаточно известный, и мы использовали его исключительно для удобства: нам почти не пришлось ничего менять.

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

Техника сокрытия выполнения кода через buffer overflow не нова, хоть и достаточно малоизвестна. В данном примере был использован самый тривиальный пример buffer overflow на стеке, и он принес нам успех. Но существуют куда более хитрые ошибки работы с памятью, приводящие к RCE (скрытому исполнению): use after free, double free, overflow in heap, format strings и так далее. Это открывает практически неисчерпаемый потенциал для приемов обхода антивирусных программ.

Автор @s0i37
источник: xakep.ru
 


Напишите ответ...
  • Вставить:
Прикрепить файлы
Верх