Содержание статьи
- Проблематика
- A long time ago in a galaxy far, far away...
- Флипы памяти RX → RW / NA
- Cobalt Strike: Obfuscate and Sleep
- Флуктуация шелл-кода на GitHub
- ShellcodeFluctuation
- fastTrampoline
- MyMessageBoxA
- Пилим свой флуктуатор на С#
- Прототипирование
- Реализация
- Использование с агентом C2
- Бонус. Реализация API Hooking с помощью MiniHook.NET
- Выводы
Если взглянуть на список фич, которыми хвастаются все коммерческие фреймворки C2 стоимостью 100500 долларов в час (Cobalt Strike, Nighthawk, Brute Ratel C4), первой в этих списках значится, как правило, возможность уклониться от сканирования памяти запущенных процессов на предмет наличия сигнатур агентов этих самых C2. Что, если попробовать воссоздать эту функцию самостоятельно? В статье я покажу, как я это сделал.
Итак, что же это за зверь такой, этот флуктуирующий шелл‑код?
Проблематика
В основном мой хлеб — это внутренние пентесты, а на внутренних пентестах бывает удобно (хотя и совсем не необходимо) пользоваться фреймворками C2. Представь, что ты разломал рабочую станцию пользователя, имеешь к ней админский доступ, но ворваться туда по RDP нельзя, ведь нарушать бизнес‑процессы заказчика (то есть выбивать сотрудника из его сессии, где он усердно заполняет ячейки в очень важной накладной) «западло».
Одно из решений при работе в Linux — квазиинтерактивные шеллы вроде smbexec.py, wmiexec.py, dcomexec.py, scshell.py и Evil-WinRM. Но, во‑первых, это чертовски неудобно, во‑вторых, ты потенциально сталкиваешься с проблемой double-hop-аутентификации (как, например, с Evil-WinRM), а в‑третьих и далее — ты не можешь пользоваться объективно полезными фичами C2, как, например, исполнение .NET из памяти или поднятие прокси через скомпрометированную тачку.
Если не рассматривать совсем уж инвазивные подходы типа патчинга RDP при помощи Mimikatz (AKA ts::
), остается работа из агента С2. И вот здесь ты столкнешься с проблемой байпаса средств защиты. Спойлер: по моему опыту, в 2022-м при активности любого «увожаемого» антивируса или EDR на хосте твой агент C2, которого ты так долго пытался получить (и все же получил, закриптовав нагрузку мильён раз), проживет в лучшем случае не больше часа.
Всему виной банальное сканирование памяти запущенных процессов антивирусами, которое выполняется по расписанию с целью поиска сигнатуры известных зловредов. Еще раз: получить агент с активным AV (и даже немного из него поработать) нетрудно; сделать так, чтобы этот агент прожил хотя бы сутки на машине‑жертве, бесценно уже сложнее, потому что, как бы ты ни криптовал и ни энкодил бинарь, PowerShell-стейжер или шелл‑код агента, вредоносные инструкции все равно окажутся в памяти в открытом виде, из‑за чего станут легкой добычей для простого сигнатурного сканера.
KES поднимает тревогу!
Если тебя спалят с вредоносом в системной памяти, который не подкреплен подозрительным бинарем на диске (например, когда имела место инъекция шелл‑кода в процесс), тот же Kaspersky Endpoint Security при дефолтных настройках не определит, какой именно процесс заражен, и в качестве решения настойчиво предложит тебе перезагрузить машину.
Такое поведение вызывает еще большее негодование у пентестера, потому что испуганный пользователь сразу побежит жаловаться в IT или к безопасникам.
Есть два пути решить эту проблему.
- Использовать C2-фреймворки, которые еще не успели намозолить глаза блютимерам и чьи агенты еще не попали в список легкодетектируемых. Другими словами, писать свое, искать малопопулярные решения на гитхабе с учетом региональных особенностей AV, который ты собрался байпасить, и тому подобное.
- Прибегнуть к продвинутым техникам сокрытия индикаторов компрометации после запуска агента C2. Например, подчищать аномалии памяти после запуска потоков, использовать связку «неисполняемая память + ROP-гаджеты» для размещения агента и его функционирования, шифровать нагрузку в памяти, когда взаимодействие с агентом не требуется.
В этой статье мы на примере посмотрим, как вооружить простой PoC флуктуирующего шелл‑кода (комбинация пунктов из абзаца выше) для его использования с почти любым опенсорсным фреймворком C2. Но для начала небольшой экскурс в историю.
A long time ago in a galaxy far, far away...
Флипы памяти RX → RW / NA
Первым опенсорсным проектом, предлагающим PoC-решение для уклонения от сканирования памяти, о котором я узнал, был gargoyle.
Если не углубляться в реализацию, его главная идея заключается в том, что полезная нагрузка (исполняемый код) размещается в неисполняемой области памяти (PAGE_READWRITE
или PAGE_NOACCESS
), которую не станет сканировать антивирус или EDR. Предварительно загрузчик gargoyle формирует специальный ROP-гаджет, который выстрелит по таймеру и изменит стек вызовов таким образом, чтобы верхушка стека оказалась на API-хендле VirtualProtectEx
, — это позволит нам изменить маркировку защиты памяти на PAGE_EXECUTE_READ
(то есть сделать память исполняемой). Дальше полезная нагрузка отработает, снова передаст управление загрузчику gargoyle, и процесс повторится.
www
Принцип работы gargoyle много раз дополнили, улучшили и «переизобрели». Вот несколько примеров:
Также интересный подход продемонстрировали в 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!
и ожидая команды от оператора, содержимое исполняемого (RWX) сегмента памяти полезной нагрузки обфусцируется. Это мешает сигнатурным сканерам распознать в немSleep Behavior:
или похожую бяку.Win32/ CobaltStrike - Если маячку поступает на исполнение следующая команда из очереди, содержимое исполняемого сегмента памяти полезной нагрузки деобфусцируется, команда выполняется, и подозрительное содержимое маяка обратно обфусцируется, превращаясь в неразборчивый цифровой мусор на радость оператору «Кобы» и назло бдящему антивирусу.
Эти действия проходят прозрачно для оператора, а процесс обфускации представляет собой обычный XOR по исполняемой области памяти с фиксированным размером ключа 13 байт (для версий CS от 3.12 до 4.3).
Продемонстрируем это на примере. Я возьму этот профиль для CS, написанный @an0n_r0 как PoC минимально необходимого профиля Malleable C2 для обхода «Дефендера». Опция set
активирует процесс obfuscate-and-sleep
.
Далее с помощью Process Hacker найдем в бинаре «Кобы» сегмент RWX-памяти (при заданных настройках профиля он будет один) и посмотрим его содержимое.
На первый взгляд, и правда, выглядит как ничего не значащий набор байтов. Но если установить интерактивный режим маячка командой sleep
и «поклацать» несколько раз на Re-read в PH, нам откроется истина.
Возможно, это содержимое все еще не очень информативно (сама нагрузка чуть дальше в памяти стаба), но, если пересоздать бикон без использования профиля, можно увидеть сердце маячка в чистом виде.
Однако на любое действие есть противодействие (или наоборот), поэтому люди из Elastic, недолго думая, запилили YARA-правило для обнаружения повторяющихся паттернов, «заксоренных» на одном и том же ключе:
rule cobaltstrike_beacon_4_2_decrypt{meta: author = "Elastic" description = "Identifies deobfuscation routine used in Cobalt Strike Beacon DLL version 4.2."strings: $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 = {8B 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}condition: any of them}
Продолжение доступно только участникам
Вариант 1. Присоединись к сообществу «Xakep.ru», чтобы читать все материалы на сайте
Членство в сообществе в течение указанного срока откроет тебе доступ ко ВСЕМ материалам «Хакера», позволит скачивать выпуски в PDF, отключит рекламу на сайте и увеличит личную накопительную скидку! Подробнее
Вариант 2. Открой один материал
Заинтересовала статья, но нет возможности стать членом клуба «Xakep.ru»? Тогда этот вариант для тебя! Обрати внимание: этот способ подходит только для статей, опубликованных более двух месяцев назад.
Я уже участник «Xakep.ru»