• Твой профиль заполнен на 0%. Заполни за 1 минуту, чтобы тебя нашли единомышленники и работодатели. Заполнить →

Статья Разработка расширений C2 фреймворков: BOF, агенты Mythic и плагины Sliver на практике

Матрёшка с поднятой крышкой, внутри которой спрятан миниатюрный одноплатный компьютер. Тёплый янтарный свет лампы, глубокие тёмно-бирюзовые тени, плёночное зерно.


Когда стандартного набора команд 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-модуля в памяти, который можно найти при периодическом сканировании.
С точки зрения MITRE ATT&CK, выполнение BOF - это Reflective Code Loading (T1620, Defense Evasion): загрузка и исполнение кода непосредственно в памяти процесса, минуя файловую систему.

Архитектура 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 BOFMythic (агенты)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 + GraphQLgRPC (любой язык)
Порог входаСредний (знание 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) - многоэтапные каналы связи.
Понимание этого маппинга критично не только для атаки, но и для обхода: знаете, что EDR мониторит конкретную технику - выбираете архитектуру расширения, которая минимизирует использование детектируемых паттернов.

Заключение: когда что использовать​

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.
 
Последнее редактирование модератором:
Мы в соцсетях:

Взломай свой первый сервер и прокачай скилл — Начни игру на HackerLab

Похожие темы

🚀 Первый раз на Codeby?
Гайд для новичков: что делать в первые 15 минут, ключевые разделы, правила
Начать здесь →
🔴 Свежие CVE, 0-day и инциденты
То, о чём ChatGPT ещё не знает — обсуждаем в реальном времени
Threat Intel →
💼 Вакансии и заказы в ИБ
Pentest, SOC, DevSecOps, bug bounty — работа и проекты от проверенных компаний
Карьера в ИБ →

HackerLab