Сегодня поговорим об одной из продвинутых техник уклонения от средств защиты при использовании фреймворков Command & Control — динамического сокрытия шелл-кода в памяти ожидаемого процесса. Я соберу PoC из доступного на гитхабе кода и применю его к опенсорсным фреймворкам.
Если взглянуть на список фич, в мире хвастаются все коммерческие фреймворки C2 стоимостью 100500 долларов в час (Cobalt Strike, Nighthawk, Brute Ratel C4), в первом в этих списках значится, как правило, уклониться от возможности использования памяти запущенных процессов по объекту финансирования сигнатур агентов. самые С2. Что, если попробовать эту функцию в регионе самостоятельно? В статье я покажу, как я это сделал.
Итак, что же это за зверь такой, этот флуктуирующий шелл-код?
ПРОБЛЕМАТИКА
В общем мой хлеб — это основные пентесты, а на внутренних пентестах удобно (хотя и совсем не обязательно) пользоваться фреймворками C2. Представь, что ты разломал рабочую станцию пользователя, имеешь к ней админский доступ, но ворваться туда по RDP нельзя, ведь нарушать бизнес-процессы клиента (то есть выбить сотрудника из его сессии, где он старательно выполняет структуру в очень важной накладной) «западло» .
Одни из решений при работе в Linux — квазиинтерактивные оболочки вроде smbexec.py , wmiexec.py , dcomexec.py , scshell.py и Evil-WinRM . Но, во-первых, это чертовски неудобно, во-вторых, ты сдерживаешь сталкивание с проблемой двойной хоп-аутентификации (как, например, с Evil-WinRM), а в-третьих и далее — ты можешь не пользоваться объективно ключами фичами C2 , как, например, исполнение .NET из памяти или поднятие прокси через скомпрометированную тачку.
Если не рассматривать совсем уж инвазивные подходы типа исправления RDP с помощью Mimikatz (AKA ts::multirdp
), остается работа агента С2. И вот здесь ты сталкиваешься с проблемой байпаса средств защиты. Спойлер: по моему опыту, в 2022-м при активности любого «увожаемого» антивируса или EDR на хосте твоего агента C2, который ты так долго пытался получить (и все же получил, зашифровав группу группён раз), проживёт в лучшем случае не больше .
Всему виной банальное сканирование памяти запущенных процессов антивирусами, результат которого по расписанию с целью поиска сигнатур известных зловредов. Еще раз: получить агента с активным AV (и даже немного из него поработать) нетрудно; сделать так, чтобы этот агент прожил, хотя бы сутки на машине-жертве, бесценно уже сложнее, потому что, как бы ты ни криптовал и не энкодил бинарь, PowerShell-стейжер или шелл-кода, конкурентные инструкции все равно окажутся в памяти в открытом виде вид, из‑за чего стал свет добычей для простого сигнатурного сканера.
КЭС поднимает тревогу!
Если вас спалят с конкурентом в системной памяти, которая не подкреплена подозрительным бинарным файлом на диске (например, когда было произведено место инъекций шелл-кода в процессе), то же Kaspersky Endpoint Security при дефолтных компонентах не определено, какой именно процесс включается, и в каком качестве решения настойчиво предлагает тебе перезагрузить машину.
Такое поведение вызывает еще большее негодование у пентестера, потому что неуверенный пользователь сразу побежит жаловаться в ИТ или к безопасникам.
Есть два пути решения этой проблемы.
- Используйте C2-фреймворки, которые еще не успели намозолить глаза блютимерам, а их агенты еще не попали в список легкодетектируемых. Другими словами, напишите свое, ищите малопопулярные решения на гитхабе с учетом региона AV, который вы собрали байпасить, и тому подобное.
- Прибегнуть к продвинутым оборудованиям, закройте индикаторы компрометации после запуска агента C2. Например, подчищать аномалии памяти после запуска потоков, использовать связку «неисполняемая память + ROP-гаджеты» для размещения агента и его резервирования, шифровать нагрузку в памяти, когда взаимодействие с агентом не требуется.
В этой статье мы рассчитываем, как вооружить простой PoC флуктуирующий шелл-код (комбинацию третьего и частично второго и пункта из абзаца выше) для его использования с почти любым опенсорсным каркасом C2. Но для начала небольшой экскурс в историю.
ДАВНЫМ ВРЕМЯ В ДАЛЕКОЙ-ДАЛЕКОЙ ГАЛАИКЕ...
Флипы памяти RX → RW / NA
первым опенсорсным проектом, отключим PoC-решение для уклонения от внезапной памяти, о которой я узнал, была горгулья .
Если не использовать в реализации, его главная идея заключается в том, что полезная нагрузка (исполняемый код) размещается в неисполняемой области памяти ( PAGE_READWRITE
или PAGE_NOACCESS
), которую не станет сканировать антивирус или EDR. Предварительно загрузчик gargoyle формирует специальный ROP-гаджет, который запускает по таймеру и меняет стек вызовов таким образом, чтобы верхушка стека оказалась на API-хендле VirtualProtectEx
, — это позволит нам изменить маркировку защиты памяти PAGE_EXECUTE_READ
(чтобы есть память, делающая исполняемую). Дальнейшая полезная нагрузка от работы снова передаст управление загрузчику gargoyle, и процесс повторится.
WWW
Принцип работы горгульи много раз дополнили, ограничили и «переизобрели». Вот несколько примеров:
- Обход сканеров памяти с помощью Cobalt Strike и Gargoyle
- В обход PESieve и Moneta («Легкий» способ....?)
- Вариант Gargoyle для x64 для сокрытия артефактов памяти с использованием только ROP и PIC.
Также интересный подход продемонстрировали в F-Secure Labs, реализовав расширение Ninjasploit для Meterpreter, которое по косвенным признакам определяет, что Windows Defender вот‑вот запустит процедуру сканирования, и тогда «флипает» область памяти с агентом на неисполняемую прямо перед этим. Сейчас, скорее всего, это расширение уже не «взлетит», так как и Meterpreter, и «Дефендер» обновились не по одному разу, но идея все равно показательна.
Из этого пункта мы заберем с собой главную идею: изменение маркировки защиты памяти помогает скрыть факт ее заражения.
Cobalt Strike: Obfuscate and Sleep
В далеком 2018 году вышла версия 3.12 культовой C2-платформы Cobalt Strike. Релиз назывался «Blink and you’ll miss it», что как бы намекает на главную фичу новой версии — директиву sleep_mask
, в которой реализована концепция obfuscate-and-sleep.
Эта концепция включает в себя следующий алгоритм поведения бикона:
- Если маячок «спит», то есть бездействует, выполняя
kernel32!Sleep
и ожидая команды от оператора, содержимое исполняемого (RWX) сегмента памяти полезной нагрузки обфусцируется. Это мешает сигнатурным сканерам распознать в немBehavior:Win32/CobaltStrike
или похожую бяку. - Если маячку поступает на исполнение следующая команда из очереди, содержимое исполняемого сегмента памяти полезной нагрузки деобфусцируется, команда выполняется, и подозрительное содержимое маяка обратно обфусцируется, превращаясь в неразборчивый цифровой мусор на радость оператору «Кобы» и на зло бдящему антивирусу.
Эта операция обеспечивает прозрачность для оператора, а процесс обфускации представляет собой обычное XOR для исполняемой области памяти с фиксированным размером ключа 13 байт (для версии CS от 3.12 до 4.3).
Продемонстрируем это на примере. Я возьму этот профиль для CS, написанный @an0n_r0 как PoC минимально необходимого профиля Malleable C2 для обхода «Дефендера». Опция set sleep_mask "true"
активирует процесс obfuscate-and-sleep
.
Далее с помощью Process Hacker найдем в бинарном файле «Кобы» сегмент RWX-памяти (при заданных параметрах профиля он будет один) и будем следить за его соблюдением.
На первый взгляд, правда, выглядит ничего не значащий набор байтов. Но если установить интерактивный режим маячка команды sleep 0
и несколько раз «поклацать» на Re-read в PH, у нас откроется истина.
Возможно, это правило все еще не очень информативно увидеть (сама нагрузка чуть дальше в памяти стаба), но, если пересоздать бикон без использования профиля, можно сердце маячка в чистом виде.
Однако на любое действие есть противодействие (или наоборот), поэтому люди из Elastic, недолго думая, запилили YARA -правило для обнаружения повторяющихся паттернов, «заксоренных» на одном и том же ключе:
rule cobaltstrike_beacon_4_2_decrypt{
Meta:
author = "Elastic"
description = "Идентифицирует температуру деобфускации, используемую в DLL Cobalt Strike Beacon DLL версии 4.2."
строки:
$a_x64 = {4C 8B 53 08 45 8B 0A 45 8B 5A 04 4D 8D 52 08 45 85 C9 75 05 45 85 DB 74 33 45 3B CB 73 E6 49 8B F9 4C 8B 03} $a_x86 = { 8Б 46
04 8B 08 8B 50 04 83 C0 08 89 55 08 89 45 0C 85 C9 75 04 85 D2 74 23 3B CA 73 E6 8B 06 8D 3C 08 33 D2} состояние: любое
из
них
}
17.06.2022 18:03:00