Когда стандартного набора команд Cobalt Strike не хватает для выгрузки LSASS в обход CrowdStrike, а штатный имплант Sliver весит 9 мегабайт и палится ещё на этапе доставки - наступает тот момент, когда садишься писать своё. Не новый C2 с нуля (об этом ниже - почему это плохая идея), а точечные дополнения к зрелым фреймворкам: BOF-файл под конкретную задачу, кастомный агент под профиль операции, gRPC-плагин для автоматизации lateral movement.
Здесь - практический разбор трёх моделей расширения C2: Beacon Object Files для Cobalt Strike, разработка агентов под Mythic и плагинная система Sliver. Без абстрактных диаграмм - конкретные архитектурные решения, код и объяснение, почему каждый подход работает именно так.
Зачем расширять C2 вместо создания с нуля
Среди пентестеров живёт романтическая идея написать собственный C2 фреймворк. Логика понятна: уникальный протокол - уникальные сигнатуры, а точнее, их отсутствие. Но в реальных red team-операциях этот подход упирается в две стены.Первая - время. По данным исследования Kaspersky (Securelist), написание полноценного Stage 1 модуля с соблюдением OPSEC-принципов требует модульности, скрытых каналов связи, механизмов обхода сигнатурного сканирования памяти - и всё это при целевом размере 100–200 КБ. На engagement в две-три недели этого времени тупо нет.
Вторая - зоопарк готового. У Cobalt Strike - сотни публичных BOF-коллекций, у Mythic - десятки агентов с поддержкой разных ОС, у Sliver - Armory с расширениями. Вместо того чтобы городить всё с нуля, эффективнее взять зрелый фреймворк и дописать именно то, чего не хватает под конкретный engagement.
Контекст тоже важен: Cobalt Strike остаётся доминирующим C2-фреймворком, тогда как Mythic и Sliver занимают куда меньшие доли. Сигнатурные базы для CS - самые зрелые, и расширения для него имеют наибольшую практическую ценность именно как средство обхода этих сигнатур.
С точки зрения MITRE ATT&CK, разработка кастомных расширений C2 охватывает разные техники Resource Development в зависимости от глубины кастомизации: создание нового агента маппится на Develop Capabilities: Malware (T1587.001), написание кастомных BOF/плагинов к существующим фреймворкам - тоже на T1587.001, а использование готовых публичных BOF-коллекций без модификации - на Obtain Capabilities: Tool (T1588.002).
BOF для Cobalt Strike: написание Beacon Object Files
Что такое BOF и почему это лучше reflective DLL
Beacon Object File - скомпилированный объектный файл (COFF), который Cobalt Strike загружает и исполняет прямо внутри процесса Beacon. Без создания нового потока, без нового процесса. В отличие от reflective DLL injection, BOF не тащит с собойReflectiveLoader - функцию, которую EDR знают наизусть и детектируют с закрытыми глазами. Но BOF - не плащ-невидимка: зрелые EDR (Elastic, CrowdStrike, MDE) с 2022 года ловят inline BOF execution через мониторинг RWX-аллокаций, ETW-события и поведенческий анализ вызовов API из нехарактерных регионов памяти.Согласно исследованию Kaspersky (Securelist), reflective DLL injection, появившаяся в 2008 году, имеет очевидные минусы: функция
ReflectiveLoader легко обнаруживается, и для боевого использования нужна кастомная реализация. BOF решает проблему архитектурно - Beacon содержит упрощённый COFF-парсер, который разрешает релокации и внешние символы, но не выполняет полноценную линковку (отсюда ограничения на глобальные переменные, .bss секцию и отсутствие CRT).Почему BOF выгоден для offensive-разработки:
- Размер - типичный BOF весит 2–10 КБ против сотен килобайт у DLL. При требовании к Stage 1 модулю в 100–200 КБ (рекомендация Kaspersky) это критично.
- Выполнение в контексте Beacon - нет создания нового процесса, нет нового потока с подозрительным start address. EDR не видит характерных паттернов
CreateRemoteThreadилиNtQueueApcThread. - Одноразовость - BOF отработал, вернул результат и выгрузился. Нет persistent-модуля в памяти, который можно найти при периодическом сканировании.
Архитектура BOF: что происходит под капотом
BOF пишется на C и компилируется в объектный файл без линковки. Вместо стандартных вызовов Windows API через import table используется Beacon Internal API - набор функций, которые Beacon предоставляет через Dynamic Function Resolution. Когда оператор грузит.o файл через inline-execute, Beacon парсит COFF-заголовки, разрешает релокации и внешние символы (LIBRARY$Function), маппит секции в свою память и вызывает entry point - функцию go().Момент, который многие новички упускают: BOF не может вызывать Win32 API напрямую через
#include <windows.h>. Каждый вызов - будь то OpenProcess, VirtualAllocEx или NtWriteVirtualMemory - объявляется через DECLSPEC_IMPORT и разрешается Beacon при загрузке COFF через символьное разрешение внешних зависимостей (COFF external symbol resolution). Вот эта точка и отличает BOF от обычного C-кода.Минимальный скелет выглядит так: entry point
go() принимает аргументы через BeaconDataParse, выполняет логику с использованием Native API (T1106, Execution) и возвращает результат через BeaconPrintf или BeaconOutput:
C:
#include "beacon.h"
DECLSPEC_IMPORT DWORD WINAPI KERNEL32$GetCurrentProcessId(void);
void go(char *args, int alen) {
DWORD pid = KERNEL32$GetCurrentProcessId();
BeaconPrintf(CALLBACK_OUTPUT, "Current PID: %d", pid);
}
x86_64-w64-mingw32-gcc -c bof.c -o bof.o. Флаг -c критически важен: он говорит компилятору не линковать. На выходе - объектный файл, готовый к загрузке через inline-execute.Практика: BOF для конкретных задач
В реальных операциях BOF закрывают задачи, которые стандартный набор команд Beacon не покрывает или покрывает с паршивым OPSEC.Дамп учётных данных без fork&run. Стандартная команда
mimikatz в Cobalt Strike использует fork&run - создаёт жертвенный процесс, инжектит туда код и читает результат. Два лишних события для EDR: создание процесса и cross-process injection. BOF позволяет выполнить тот же дамп inline, без дополнительных процессов. Но BOF не убирает необходимость открыть handle к LSASS (OpenProcess с PROCESS_VM_READ) - а это одно из самых мониторируемых событий (Sysmon Event ID 10). CrowdStrike и MDE детектируют именно доступ к LSASS, а не fork&run. Для полноценного обхода нужны дополнительные техники: дупликация handle через существующий процесс, direct syscalls через NtReadVirtualMemory или чтение из дампа через MiniDumpWriteDump callback. По ATT&CK - сочетание Credential Access и Defense Evasion, где BOF устраняет IoC создания жертвенного процесса, но не все IoC доступа к LSASS.Enumeration через Native API. Перечисление пользователей, групп, сервисов через прямые вызовы
ntdll и advapi32 вместо запуска net.exe или PowerShell. BOF дёргает NetUserEnum или LsaEnumerateAccountsWithUserRight напрямую, результат улетает через Beacon callback - никаких дочерних процессов, никаких подозрительных command line в логах Sysmon.Кастомная работа с токенами. Manipulate, impersonate, дублирование - всё через BOF с прямым вызовом
ADVAPI32$DuplicateTokenEx, ADVAPI32$ImpersonateLoggedOnUser. Компактнее и безопаснее, чем грузить целый модуль.При разработке BOF помните об ограничениях: нет глобальных переменных (они не инициализируются корректно), нет статических библиотек, нет C runtime. Всё должно быть самодостаточным - только Beacon API плюс динамически разрешённые Win32/NT-функции.
Кастомные агенты Mythic C2: модульная архитектура
Как устроен агент Mythic и почему это не просто бинарник
Mythic принципиально отличается от Cobalt Strike архитектурным подходом. CS - монолит с единственным типом агента (Beacon). Mythic - платформа, где агент - отдельный Docker-контейнер с собственной логикой сборки, набором команд и транспортными профилями.По данным из официальной документации и исследования r4ulcl, Mythic из коробки не поставляется ни с одним агентом. Каждый ставится отдельно:
./mythic-cli install github https://github.com/MythicAgents/Apollo.git для Windows-агента Apollo на .NET, или аналогичная команда для Poseidon (Go, Linux/macOS). Решение сознательное - оператор собирает свой стек под конкретный engagement.Архитектура Mythic строится на микросервисах, координируемых через RabbitMQ. Ядро: PostgreSQL для хранения, GraphQL API для внешнего взаимодействия, React-интерфейс, Nginx как reverse proxy и Jupyter для разработки. Каждый агент и каждый C2-профиль - отдельный контейнер, который регистрируется через RabbitMQ и получает задачи через очередь сообщений.
По сути это Multi-Stage Channels (T1104, Command and Control) на уровне самого фреймворка: разные этапы операции могут использовать разные агенты с разными транспортами, координируемые единым сервером.
Разработка кастомного агента: от протокола до callback
Для написания кастомного агента Mythic нужно реализовать два компонента: серверную часть (Translation Container) и сам агент.Серверная часть описывает метаданные агента - какие команды поддерживаются, какие параметры принимаются при сборке, как обрабатывается callback. Это Python-файлы по структуре Mythic SDK. Каждая команда агента описывается как класс с параметрами, парсерами аргументов и обработчиками результатов.
Сам агент реализует beacon loop: периодический callback на C2-профиль (HTTP, TCP, WebSocket или кастомный), получение задач в формате JSON, исполнение и отправка результатов. Протокол использует Application Layer Protocol (T1071, Command and Control) - для HTTP-профиля это стандартные POST-запросы, которые при правильной настройке маскируются под легитимный трафик.
Главное преимущество Mythic для кастомной разработки - staging. По модели из исследования Kaspersky, payload делится на три стадии: Stage 0 (доставка и запуск), Stage 1 (разведка и закрепление), Stage 2 (пост-эксплуатация). Mythic нативно поддерживает этот подход - можно написать минимальный Stage 1 агент на C/C++ размером в 100 КБ, который обеспечивает базовый callback и загрузку модулей, а всю тяжёлую функциональность подгружать через BOF/COFF или динамические модули. Вот я считаю это самым сильным аргументом в пользу Mythic для операций, где критичен размер доставляемого payload.
BOF и COFF в экосистеме Mythic
Mythic поддерживает загрузку и выполнение BOF через встроенный COFF-парсер агента - Apollo, например, использует порт COFFLoader (TrustedSec) для запуска COFF-файлов, скомпилированных для Cobalt Strike, внутри своего контекста. Агент грузит.o файлы, парсит COFF-заголовки и исполняет их аналогично Beacon.Ситуация интересная: BOF, использующие только базовый Beacon API (
BeaconPrintf, BeaconOutput, BeaconDataParse) и Win32/NT API через DECLSPEC_IMPORT, совместимы с Mythic. А вот BOF, завязанные на расширенный Beacon API (fork&run функции: BeaconSpawnTemporaryProcess, BeaconInjectProcess и др.), требуют портирования. Если вы уже написали BOF под CS - его можно переиспользовать. Более того, AdaptixC2 (набирающий популярность open-source фреймворк) тоже поддерживает BOF-файлы, включая асинхронные, как отмечается в исследовании Kaspersky по детектированию AdaptixC2. BOF становится де-факто стандартом расширения для C2-фреймворков - и это хорошая новость для тех, кто вкладывается в их разработку.Плагины Sliver: расширение через gRPC и Armory
Модель расширения Sliver
Sliver идёт принципиально другим путём. Вместо объектных файлов или контейнерных агентов - два механизма: extensions (расширения самого импланта) и aliases (обёртки над существующими командами), управляемые через пакетный менеджер "Armory".Armory - по сути
apt-get для Sliver: armory install all ставит все доступные расширения, armory install <name> - конкретное. При первом запуске Sliver подтягивает более 100 расширений с диска.Extension для Sliver - shared library (DLL для Windows, SO для Linux), которая загружается в контекст импланта. На Windows это Reflective Code Loading (T1620, Defense Evasion), на Linux загрузка SO может классифицироваться как T1620, если происходит из памяти (через
memfd_create + dlopen), или как T1129 (Shared Modules), если используется стандартный dlopen с диска. T1620 - не зависимая от платформы концепция. Конкретный маппинг зависит от механизма, который имплант использует на целевой ОС. В отличие от одноразовых и легковесных BOF, Sliver extensions - полноценные библиотеки, которые могут содержать сложную логику и сохранять состояние между вызовами.Серверная автоматизация строится на gRPC API. Sliver-сервер предоставляет полноценный gRPC-интерфейс, через который можно программно управлять всей операцией: создавать listener'ы, генерировать payload'ы, слать команды на импланты, забирать результаты. Писать внешние плагины можно на любом языке с поддержкой gRPC - Go, Python, Ruby.
Практика: автоматизация через gRPC
Типичный сценарий для gRPC-плагина Sliver - автоматизация lateral movement. Вместо ручного последовательного тыканья (перечисление хостов → проверка доступов → развёртывание импланта) пишется скрипт, который через gRPC получает список активных сессий, запрашивает у каждой сетевую информацию, анализирует результаты и автоматически генерирует payload для обнаруженных целей. На 50 хостах руками это делать - мазохизм.Для подключения к gRPC API используется конфигурационный файл оператора (
.cfg), который генерируется через new-operator --name <username> --lhost <ip>. Файл содержит mTLS-сертификаты для аутентификации. Sliver предоставляет официальный Python-клиент sliver-py (pip install sliver-py), который инкапсулирует gRPC-взаимодействие. Ручная генерация стабов из proto-файлов возможна, но не рекомендуется - зачем усложнять себе жизнь.Важное ограничение Sliver, отмеченное в исследовании Kaspersky: размер payload по умолчанию - 8–9 мегабайт. Для red team-операций, где идеальный размер агента около 100 КБ, это жирный минус. Расширения проблему не решают - они увеличивают и без того крупный имплант. Поэтому в операциях, где критичен размер, Sliver чаще используется как вторичный C2 на этапе пост-эксплуатации, когда закрепление уже выполнено и никто не считает килобайты.
Сравнение моделей расширения: CS, Mythic, Sliver
Три фреймворка - три фундаментально разных подхода к расширяемости. Выбор определяется не абстрактными преимуществами, а конкретными требованиями engagement.| Параметр | Cobalt Strike BOF | Mythic (агенты) | Sliver (extensions/gRPC) |
|---|---|---|---|
| Язык разработки | C | Любой (Go, C#, Python, C++) | Любой компилируемый в DLL/SO (C, C++, Rust, Nim, C# и др.); gRPC-плагины - любой язык |
| Размер расширения | 2–10 КБ | Зависит от агента | Десятки–сотни КБ |
| Модель выполнения | Inline в процессе Beacon | Отдельный имплант | Reflective loading в имплант |
| Persistence в памяти | Нет (одноразовый) | Да (агент постоянный) | Да (расширение сохраняется) |
| Переиспользование между C2 | Да (Mythic Forge, AdaptixC2) | Нет (привязан к Mythic) | Нет (привязан к Sliver) |
| Серверная автоматизация | Aggressor Script (Sleep) | Python SDK + GraphQL | gRPC (любой язык) |
| Порог входа | Средний (знание C, COFF) | Высокий (Docker, RabbitMQ, SDK) | Средний (Go, gRPC) |
Из таблицы видно, почему BOF стали универсальным стандартом: минимальный размер, отсутствие persistence в памяти, совместимость между фреймворками. Mythic выигрывает, когда нужен принципиально другой агент - скажем, на Go под Linux вместо .NET под Windows. Sliver - когда нужна программная автоматизация всей операции через gRPC.
Пошаговый процесс разработки offensive-расширения
Этап 1: формулировка задачи под engagement
Любое расширение начинается не с кода, а с вопроса: «Что конкретно стандартный набор команд не может сделать?». Типичные триггеры:- Команда
mimikatzвызывает fork&run, а EDR на целевой машине мониторит именно создание жертвенных процессов - нужен BOF для inline-выполнения. - Нужен агент под macOS, а основной C2 - Cobalt Strike, который поддерживает только Windows - нужен Poseidon под Mythic как вторичный канал.
- Lateral movement требует выполнения одной последовательности на 50 хостах - нужен gRPC-скрипт для Sliver.
Этап 2: выбор механизма
Определяется на основе таблицы выше. Задача - одна операция без persistence в памяти? BOF. Нужен постоянный агент с уникальным транспортом? Кастомный агент Mythic. Автоматизация серверной логики? gRPC-плагин Sliver.Этап 3: разработка и тестирование
Для BOF: компиляция через MinGW, тестирование в лабораторной среде с Cobalt Strike, проверка черезinline-execute. Обязательно тестируйте на системе с включённым EDR - поведение BOF в «чистой» среде не показывает реальных проблем. И проверяйте, что BOF корректно возвращает управление Beacon после завершения - необработанное исключение убьёт весь процесс Beacon, и вы потеряете сессию. На одном engagement коллега так потерял единственную сессию на DC - обидно было до зубовного скрежета.Для агентов Mythic: локальное развёртывание через Docker (
sudo ./mythic-cli start), регистрация агента, тестирование каждой команды. Mythic просит минимум 2 CPU и 4 ГБ RAM для комфортной работы.Для Sliver: разработка extension как shared library, компиляция под целевую ОС, загрузка через Armory или ручная регистрация. Тестирование через Sliver в multiplayer-режиме.
Антипаттерны при разработке расширений
Не используйте BOF для долгоживущих задач. BOF блокирует Beacon на время выполнения. Ваш BOF работает 30 секунд - Beacon 30 секунд не отвечает на команды. Для длительных операций лучше подходит fork&run или отдельный имплант.Не забывайте про Obfuscation (T1027, Defense Evasion). Даже кастомный BOF содержит строки - имена API-функций, пути, параметры. Без обфускации строк статический анализ вычислит назначение BOF по строковым артефактам. Минимум - шифруйте строки через compile-time XOR или используйте hashing для резолва API.
Не оставляйте дефолтные профили C2. По данным Team Cymru, 90% Mythic-серверов в интернете сидят на дефолтных настройках: порт 7443, SSL-сертификат с
O=Mythic. Детектируется моментально. При развёртывании любого фреймворка меняйте порты, сертификаты, заголовки - используйте Encrypted Channel (T1573, Command and Control) с кастомными параметрами. На заборе написано «Mythic» - так зачем вешать тот же транспарант на свой сервер?Маппинг на MITRE ATT&CK
Разработка расширений C2 затрагивает несколько тактик и техник, которые важно учитывать и при разработке, и при построении защиты:- Resource Development: Develop Capabilities: Malware (T1587.001) - разработка кастомных агентов; Obtain Capabilities: Tool (T1588.002) - использование готовых публичных BOF-коллекций и плагинов без модификации.
- Execution: Native API (T1106) - BOF и extensions дёргают Windows API напрямую.
- Defense Evasion: Reflective Code Loading (T1620) - загрузка BOF/extensions в память; Process Injection (T1055) - если расширение инжектит код в другие процессы; Obfuscated Files or Information (T1027) - обфускация payload'ов.
- Command and Control: Application Layer Protocol (T1071) - транспорт агентов; Encrypted Channel (T1573) - шифрование канала; Multi-Stage Channels (T1104) - многоэтапные каналы связи.
Заключение: когда что использовать
BOF - основной инструмент, если Cobalt Strike ваш первичный C2. Пишите BOF под конкретные задачи engagement, не используйте публичные коллекции без модификации - их сигнатуры уже в базах EDR. BOF также работает в Mythic (через встроенный COFF-парсер агентов) и AdaptixC2, что делает его самым "портабельным" форматом расширения.Кастомные агенты Mythic - для операций, где нужно управлять гетерогенной инфраструктурой (Windows + Linux + macOS) из единого интерфейса, или когда архитектура engagement требует принципиально другого транспорта.
gRPC-плагины Sliver - для автоматизации масштабных операций, где ручное управление десятками сессий нереально. Sliver как вторичный C2 на этапе пост-эксплуатации, когда закрепление выполнено и размер payload не критичен.
Стандартные инструменты детектируются на следующий день после релиза. Возьмите любой BOF из публичной коллекции, прогоните через VirusTotal - и убедитесь сами. Уникальный код, написанный под конкретную операцию - единственное, что даёт устойчивое преимущество. Так что откройте MinGW, напишите свой первый BOF на 20 строк - и посмотрите, как он выглядит в Process Hacker по сравнению с fork&run
mimikatz.
Последнее редактирование модератором: