Здорова народ! В сегодняшней статье я покажу, как обойти антивирус при пентесте используя фреймворк Metasploit.
Еще по теме: Как обойти антивирус с помощью Chimera
Обход антивируса в Meterpreter
Техника встраивания кода в уже запущенные, а значит, прошедшие проверку процессы широко известна. Идея состоит в том, что мы имеем отдельную программу shellcode_inject.exe и сам shellcode в разных файлах. Антивирусу сложнее распознать угрозу, если она раскидана по нескольким файлам, тем более по отдельности эти файлы не представляют угрозы.
В свою очередь, наш shellcode выглядит еще более безобидно, если мы преобразуем его в печатные символы.
Глядя на содержимое meter.txt, я бы скорее решил, что это строка в Base64, чем шелл‑код.
Стоит отметить, что мы использовали шелл‑код meterpreter_reverse_tcp, а не meterpreter/reverse_tcp. Это автономный код, который содержит в себе все функции Meterpreter, он ничего не будет скачивать по сети, следовательно, шансов спалиться у нас будет меньше. Но вот связка shellcode_inject.exe и meter.txt уже представляет опасность. Давай посмотрим, сможет ли антивирус распознать угрозу?
Обрати внимание: мы использовали для инжекта кода системный процесс, он сразу работает в контексте System. И похоже, что наш подопытный антивирус хоть в конце и ругнулся на shellcode_inject.exe, но все же пропустил данный трюк.
Запустив что‑то вроде Meterpreter, атакующий получит возможность выполнить полезную нагрузку прямо в памяти, минуя тем самым HDD.
Много лет этот простой трюк выручал меня. Сработал он и на этот раз.
Code caves
Каждый exe-файл (PE-формат) содержит код. При этом весь код оформлен в виде набора функций. В свою очередь, функции при компиляции размещаются не одна за другой вплотную, а с некоторым выравниванием (16 байт). Еще большие пустоты возникают из‑за выравнивания между секциями (4096 байт). И благодаря всем этим выравниваниям создается множество небольших «кодовых пустот» (code caves), которые доступны для записи в них кода. Тут все очень сильно зависит от компилятора. Но для большинства PE-файлов, а нас главным образом интересует ОС Windows, картина может выглядеть примерно так, как показано на следующем скриншоте.
Что представляет собой каждая такая «пустота»?
Так, если мы более точно (сигнатурно) определим расположение всех пустот, то получим примерно следующую картину в исполняемом файле.
В этом примере мы поискали только 12-байтные пустоты, так что реальное их количество будет гораздо большим. Пустот хоть и немало, но их явно недостаточно для размещения полноценной программы. Поэтому данный способ годится только для вставки шелл‑кодов, размер которых редко превышает 1 Кбайт.
Давай посмотрим, сможем ли мы разложить небольшой многоступенчатый Windows/Meterpreter/reverse_tcp шелл‑код по этим пустотам. Размер code cave редко превышает 16 байт, так что нам потребуется разбивать шелл‑код сильнее, чем по базовым блокам. Следовательно, придется вставлять еще и дополнительные jmp-инструкции для их связи и корректировать адреса условных переходов. На деле это достаточно рутинная операция.
В результате наш шелл‑код размером в 354 байта был разбит на 62 кусочка и помещен в рандомные пустоты между функциями.
По идее, такой подход должен дать нам полиморфизм, так как каждый раз шелл‑код будет помещаться в случайные пустоты по две‑три инструкции (это называется умным словом «пермутация»). Даже на уровне трассы исполнения код будет обфусцирован из‑за достаточно большого количества инструкций jmp между фрагментами.
C помощью этого способа мы можем обойти таким образом много «простых» антивирусов.
Однако серьезные антивирусные продукты таким трюком все же не проведешь.
Crypt
Как ни странно, классический xor исполняемого файла с динамическим ключом все еще успешно работает против даже самых грозных антивирусов. Для примера возьмем какой‑нибудь очень палевный исполняемый файл и закриптуем простым xor все, что только можно. Крипт секций .text и .data выглядит примерно так.
Теперь спрячем информацию о версии.
Секция .rdata содержит практически все палевные строки. Но вот беда, на нее проецируются директории таблиц импорта и отложенного импорта, которые трогать нельзя, иначе файл не запустится. Поэтому просто найдем в данной секции область, где главным образом содержатся строки, и зашифруем ее.
Поскольку мы все зашифровали в исполняемом файле, то в момент запуска его смещения в коде не будут правильно скорректированы в соответствии с адресом размещения. Поэтому еще придется отключить ASLR:
1 |
PE->NT headers->Optional header->DllCharacteristics |=0x40 |
Теперь самое время проверить, что мы все спрятали, и наш mimikatz больше не вызывает подозрений.
Отлично. Только пока наш файл неработоспособен, так как в нем все зашифровано. Перед дальнейшими действиями рекомендую попробовать запустить файл в отладчике, чтобы убедиться, что структуры PE-формата не повреждены и файл валиден.
Теперь нам потребуется написать небольшой машинный код для расшифровки. И, что важно, ключ будет задаваться во время исполнения, то есть нигде в коде он не будет сохранен. Следовательно, запуск приложения в песочнице антивируса не должен выявить угрозы.
Наш xor_stub.asm будет сохранять начальное состояние и добавлять права на запись в зашифрованные секции.
Здесь и далее не забудь изменить адреса 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):
1 2 |
cl.exe /c vuln_rop.c link.exe /out:vuln_rop.exe vuln_rop.obj /nxcompat:no /fixed |
Наш сетевой сервис будет работать в режимах listen и reverse connect. А все данные будут передаваться в зашифрованном виде, чтобы не спалиться на сигнатурном анализаторе. И это очень важно, поскольку некоторые антивирусы настолько «не любят» Meterpreter, что даже простая его отправка в любой открытый порт спровоцирует неминуемый алерт и последующий бан IP-адреса атакующего.
Полученный исполняемый файл технически не является вредоносным, ведь он содержит в себе лишь ошибку при работе с памятью. В противном случае любая программа может считаться вредоносной, поскольку потенциально она тоже может включать ошибки.
Наш уязвимый сервис успешно запущен и ждет входящих данных. Создаем payload и запускаем эксплоит с ним.
В итоге уязвимый сервис принимает наши данные и в результате заложенной ошибки при работе с памятью непроизвольно запускает код payload.
Эта полезная нагрузка открывает нам сессию Meterpreter.
И все это безобразие происходит при работающем антивирусе. Однако было замечено, что при использовании некоторых антивирусов все еще срабатывает защита. Давай порассуждаем, почему. Мы вроде бы смогли внедрить код крайне неожиданным способом — через buffer overflow. И в то же самое время по сети мы не передавали код в открытом виде, так что сигнатурные движки не сработали бы. Но перед непосредственным исполнением мы получаем в памяти тот самый машинный код. И тут, по‑видимому, антивирус и ловит нас, узнавая до боли знакомый Meterpreter.
Антивирус не доверяет exe-файлу, скачанному неизвестно откуда и запущенному первый раз. Он эмулирует выполнение кода и достаточно глубоко анализирует его, возможно даже на каждой инструкции. За это приходится платить производительностью, и антивирус не может позволить себе делать так для всех процессов. Поэтому процессы, уже прошедшие проверку на этапе запуска (например, системные компоненты или программы с известной контрольной суммой), работают под меньшим надзором. Именно в них мы и внедрим нашу уязвимость.
Vuln inject (attach)
Самый простой и удобный способ выполнить код в чужом адресном пространстве (процессе) — инжект библиотеки. Благо DLL мало чем отличается от EXE и мы можем перекомпилировать наш уязвимый сервис в форм‑фактор библиотеки, просто изменив main() на DllMain():
1 2 |
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-битный процесс мы можем внедрить уязвимость, просто заинжектив туда нашу библиотеку.
Наша DLL без ASLR успешно загружена по стандартному адресу.
И теперь целевой процесс с занесенным 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 и так далее. Это открывает практически неисчерпаемый потенциал для приемов обхода антивирусных программ.
То есть все крипторы работают подобным методом м переполнением буфера ? Если так почему они очень быстро паляться после компеляции и детектятся , не переписывать же все время софт . Есть ли какие либо решение для скрипт-кидди ? Или может совет куда копать . Peace !