• [ Регистрация ]Открытая и бесплатная
  • Tg admin@ALPHV_Admin (обязательно подтверждение в ЛС форума)

Статья Beacon Object File. Прокачиваем C2-маяки на примере Adaptix

stihl

bot
Moderator
Регистрация
09.02.2012
Сообщения
1,381
Розыгрыши
0
Реакции
694
Deposit
0.228 BTC
stihl не предоставил(а) никакой дополнительной информации.
Как расширить возможности C2-маяка, не переписывая его код и не подставляясь под антивирусы? В статье разбираем Beacon Object Files — легкий модульный способ добавлять новые функции прямо на лету. С помощью BOF можно проводить постэксплуатацию, закрепление, эксфильтрацию и выполнять любые кастомные задачи без доработки самого маяка.
Например, для проведения фишинговой атаки тебе нужно разработать маяки, которые запускаются на захваченной машине и отстукивают какому‑то C2-серверу. Да, по своей сути маяк просто должен сохранять соединение с C2, и ты получаешь удаленный доступ к тачке. Но, как правило, тебе придется заниматься постэксплуатацией, эксфильтрацией, делать закреп на машине.

Тебе, конечно, не захочется переписывать маяк, раздувать его код и добавлять туда новые функции. Любую активность к тому же может запалить антивирус. Хорошо бы использовать инструмент, который уже будет обладать необходимыми фичами, а еще лучше, чтобы его можно было расширять в режиме онлайн. Именно для этого придумали Beacon Object File, или сокращенно BOF.


Beacon Object File — это концепция, которая впервые засветилась в 2021 году — в Cobalt Strike, самом известном фреймворке для постэксплуатации. Beacon Object File — это скомпилированная программа на языке C, которая может выполняться в процессе работы маяка и использовать внутренние Beacon API. BOF — это способ быстрого расширения агента новыми функциями.

BOF позволяет загружать в память нативный код во время выполнения маяка. Процесс похож на загрузку и выполнение DLL, но позволяет оставаться более незаметным. Сами BOF — это не исполняемые файлы, они представлены в форме Microsoft Common Object File Format (сокращенно COFF).

info​

Common Object File Format (COFF) — это формат исполняемых файлов, файлов объектного кода и динамических библиотек.
Я решил проверить свой BOF на C2 Для просмотра ссылки Войди или Зарегистрируйся, который разработал Для просмотра ссылки Войди или Зарегистрируйся. Это достойная замена для Cobalt Strike и опенсорсного Для просмотра ссылки Войди или Зарегистрируйся. Adaptix — это расширяемый фреймворк для постэксплуатации, разработанный специально для пентестеров. Сервер Adaptix написан на Golang, клиентский графический интерфейс — на C++ и Qt, что позволяет использовать его в Linux, Windows и macOS.

www​

Для просмотра ссылки Войди или Зарегистрируйся
Что ж, давай сядем за клавиатуру и начнем писать свой первый простенький BOF.

warning​

Статья имеет ознакомительный характер и предназначена для специалистов по безопасности, проводящих тестирование в рамках контракта. Автор и редакция не несут ответственности за любой вред, причиненный с применением изложенной информации. Распространение вредоносных программ, нарушение работы систем и нарушение тайны переписки преследуются по закону.

Создаем скелет​

Для начала создам директорию, которая называется bofs, и сделаю файл для BOF под названием example.c. Сперва просто создадим функцию, которая будет точкой опоры:

Код:
void go()
{
}
Для компиляции нужно использовать MinGWGCC. В Linux я буду компилировать так:

x86_64-w64-mingw32-gcc -c -o example.o example.c
Здесь -c — опция, которая сообщает компилятору о том, что это объектный файл.

Для просмотра ссылки Войди или Зарегистрируйся
Теперь введу file example.o и получу информацию о том, что это COFF object file.

Для просмотра ссылки Войди или Зарегистрируйся
Мы получили объектный файл, но он должен что‑то делать. Чтобы он начал работать с маяком, например Cobalt Strike или Для просмотра ссылки Войди или Зарегистрируйся, нам нужно использовать Beacon API. Откроем документацию Cobalt Strike и скачаем оттуда заголовочный файл Для просмотра ссылки Войди или Зарегистрируйся.

www​

Для просмотра ссылки Войди или Зарегистрируйся

Пишем простой BOF​

Открываем beacon.h и видим там полный набор всего необходимого. В самом начале находится Для просмотра ссылки Войди или Зарегистрируйся с примером BOF.

Для просмотра ссылки Войди или Зарегистрируйся
Дальше будем погружаться в API, но начнем с малого. Вот, например, функции, которые, видимо, способны выводить что‑то на экран:

Код:
DECLSPEC_IMPORT void   BeaconOutput(int type, char * data, int len);
DECLSPEC_IMPORT void   BeaconPrintf(int type, char * fmt, ...);

Есть еще несколько удобных штук, но в итоге мы хотим иметь возможность запускать функции Win32 API. Сначала, впрочем, убедимся, что у нас все компилируется и запускается. Предлагаю проверить на обычном Hello World. Для этого будем использовать BeaconPrintf.

В исходном коде example.c нужно включить beacon.h, используя обычный #include. Как можешь заметить, BeaconPrintf() требует как минимум два аргумента: тип (целочисленное число) и сама строка.

Первый аргумент ждет какое‑то число, и эти числа прописаны в beacon.h:

Код:
#define CALLBACK_OUTPUT      0x0
#define CALLBACK_OUTPUT_OEM  0x1e
#define CALLBACK_OUTPUT_UTF8 0x20
#define CALLBACK_ERROR       0x0d

Нам нужно CALLBACK_OUTPUT, а дальше просто вызовем BeaconPrintf(CALLBACK_OUTPUT, "Hello world!"):

Код:
#include "beacon.h"

void go()
{
    BeaconPrintf(CALLBACK_OUTPUT, "Hello world!");
}

www​

В Для просмотра ссылки Войди или Зарегистрируйся описана каждая функция и есть видео с примером, который может помочь.
Теперь это надо скомпилировать, снова используя MinGW:

x86_64-w64-mingw32-gcc -c -o example.o example.c
Для просмотра ссылки Войди или Зарегистрируйся
Теперь встает вопрос: как это проверить? Вспомним, что это COFF, а не полный исполняемый файл. Чтобы запустить этот объектный файл без C2, можно использовать Для просмотра ссылки Войди или Зарегистрируйся, который позволяет выполнить BOF в памяти.

Сначала собираем COFFLoader:

Код:
git clone https://github.com/trustedsec/COFFLoader.git
cd COFFLoader

Для просмотра ссылки Войди или Зарегистрируйся
Тут мы можем увидеть Makefile, и нам достаточно выполнить make.

Для просмотра ссылки Войди или Зарегистрируйся
После этого получим COFFLoader64.exe. Теперь этот файл надо перенести на Windows и там протестировать свой BOF. Работать с COFFLoader64.exe очень просто: вводим COFFLoader64.exe go example.o и получаем результат.

Для просмотра ссылки Войди или Зарегистрируйся

Работа с Win32 API​

Теперь наш BOF умеет что‑то делать, а мы научились его запускать. Но мы ведь хотели заняться постэксплуатацией и вызывать функции Win32 API, но BOF — это не совсем исполняемый файл. В нем нет таблицы импортов. Если захотим что‑то импортировать и использовать функции Windows, то BOF нужны Dynamic Function Resolutions. С ними мы сможем вызывать функции системных библиотек. DFR работает, добавляя к импортируемому символу префикс с именем библиотеки, из которой он импортируется.

www​

Для просмотра ссылки Войди или Зарегистрируйся
Например, есть вот такая функция:

DECLSPEC_IMPORT DWORD WINAPI NETAPI32$DsGetDcNameA(LPVOID, LPVOID, LPVOID, LPVOID, ULONG, LPVOID);
Тут указано, что вызывается функция DsGetDcNameA из NetApi32.dll. Разделитель — знак доллара.

Теперь попробуем сделать что‑нибудь очень простое, но с использованием Win32 API. К примеру, вызовем функцию GetCurrentProcessId(). Как с ней работать, посмотрим в Для просмотра ссылки Войди или Зарегистрируйся.

Для просмотра ссылки Войди или Зарегистрируйся
Видим, что функция находится в библиотеке kernel32.dll и возвращает данные типа DWORD.

Теперь вернемся в наш исходный код (example.c) и объявим эту функцию с использованием специального синтаксиса DECLSPEC_IMPORT. Так BOF узнает, что мы хотим использовать эту функцию:

DECLSPEC_IMPORT DWORD KERNEL32$GetCurrentProcessId();
  • KERNEL32 сообщает, что мы хотим использовать функцию из DLL kernel32.dll;
  • DWORD — возвращаемый тип данных.
Теперь осталось только вызвать функцию и вывести на экран число:

Код:
#include "beacon.h"

DECLSPEC_IMPORT DWORD KERNEL32$GetCurrentProcessId();

void go()
{
    DWORD pid = KERNEL32$GetCurrentProcessId();
    BeaconPrintf(CALLBACK_OUTPUT, "[!] Current Process ID: %ld", pid);
}

Использование %ld означает long int, что не противоречит DWORD.

Для просмотра ссылки Войди или Зарегистрируйся
И компилируем код:

x86_64-w64-mingw32-gcc -c -o example.o example.c
Для просмотра ссылки Войди или Зарегистрируйся
Теперь так же проверим через COFFLoader64.exe:

COFFLoader64.exe go example.o
Для просмотра ссылки Войди или Зарегистрируйся

Добавляем BOF в Adaptix C2​

Попробуем добавить этот BOF в Adaptix C2. Судя по Для просмотра ссылки Войди или Зарегистрируйся, нужно будет сделать скрипт .axs. Изучив код из Для просмотра ссылки Войди или Зарегистрируйся с расширениями Adaptix, я взял за основу экстеншн Для просмотра ссылки Войди или Зарегистрируйся, который использует команду execute bof для запуска BOF.

Для просмотра ссылки Войди или Зарегистрируйся
В первой части metadata нужно указать имя и описание расширения. Это удобно для управления, и сразу понятно, для чего нужен этот BOF. В нашем случае напишем что‑нибудь простенькое:

Код:
var metadata = {
    name: "my_bof_pid",
    description: "my_bof_pid"
};

Далее создадим команду. Тут будем использовать ax.create_command(). Эта функция создает объект AxCommand, который будет преобразован в команду для агентов Adaptix C2:

AxCommand ax.create_command(string name, string description, string example = "", string message = "");
  • name — имя консольной команды;
  • description — описание команды;
  • example — пример ввода;
  • message — сообщение, которое будет отображено в консоли агента при отправке команды.
В нашем случае будет так:

var cmd_my_get_pid = ax.create_command("my_get_pid_bof", "TEST MY BOF", "my_getpid_bof");
Дальше я буду использовать функцию PreHook. Это метод объекта AxCommand, обрабатывающий данные между вводом команды и отправкой данных на сервер:

setPreHook(function() handler)
handler(id, cmdline, parsed_json, ...parsed_lines) — это функция AxScript, которая принимает четыре аргумента:

  • id — идентификатор агента, для которого указана команда;
  • cmdline — команда отправлена с консоли агента;
  • parsed_json — объект JSON, в который была преобразована команда после обработки;
  • parsed_lines — массив строк, в который была преобразована команда.
Это нам нужно, чтобы установить обработчик, который будет упаковывать аргументы в формат BOF и выполнять команду execute bof через функцию ax.execute_alias.

В нашем случае это будет выглядеть так:

cmd_my_get_pid.setPreHook(function (id, cmdline, parsed_json, ...parsed_lines){}
Затем создадим переменную bof_path, в которой будет храниться путь до объектного файла example.o:

let bof_path = ax.script_dir() + "example.o";
Далее используем функцию execute_alias. Она передает command обработчику с указанным id. Это будет работать так, как если бы пользователь ввел команду в консоли агента:

void ax.execute_command(string id, string command, handler(task){} = nil);
  • id — идентификатор агента;
  • command — команда;
  • handler — PostHook.
В нашем случае так:

ax.execute_alias(id, cmdline, execute bof ${bof_path}, "Task: MY GET PID BOF");
Чтобы созданные команды можно было использовать в консоли агента, их необходимо объединить в группы команд с помощью функции create_commands_group. Затем регистрируем с помощью функции register_commands_group:

AxCommandsGroup ax.create_commands_group(string name, AxCommand[] commands);
  • name — имя группы команд;
  • commands — массив объектов AxCommand.
ax.register_commands_group(AxCommandsGroup group, string[] agents, string[] os, string[] listeners);
  • group — объект AxCommandsGroup;
  • agents — массив имен агентов, для которых будет доступна группа команд. Доступны варианты вроде beacon, gopher и так далее;
  • os — массив строк с названиями операционных систем, для которых будет доступна группа команд. Доступны следующие варианты: windows, linux, macos. Если массив пустой, то группа команд будет зарегистрирована для всех операционных систем;
  • listeners— массив строк с именами зарегистрированных листенеров, для которых будет доступна группа команд. Доступны следующие варианты: BeaconHTTP, BeaconSMB и так далее. Если указан пустой массив [], группа команд будет зарегистрирована для всех листенеров.
В нашем случае BOF только для Windows, поэтому участок кода будет таким:

Код:
var group_test = ax.create_commands_group("my_bof_pid", [cmd_my_get_pid]);
ax.register_commands_group(group_test, ["beacon", "gopher"], ["windows"], []);

В итоге получаем вот такой код:

Код:
var metadata = {
    name: "my_bof_pid",
    description: "my_bof_pid"
};

var cmd_my_get_pid = ax.create_command("my_get_pid_bof", "TEST MY BOF", "my_getpid_bof");

cmd_my_get_pid.setPreHook(function (id, cmdline, parsed_json, ...parsed_lines) {
    let bof_path = ax.script_dir() + "example.o";

    ax.execute_alias(id, cmdline, execute bof ${bof_path}, "Task: MY GET PID BOF");
});

var group_test = ax.create_commands_group("my_bof_pid", [cmd_my_get_pid]);
ax.register_commands_group(group_test, ["beacon", "gopher"], ["windows"], []);

Далее в Adaptix C2 перехожу в Script Manager.

Для просмотра ссылки Войди или Зарегистрируйся
Выбираю Load New и загружаю скрипт.

Для просмотра ссылки Войди или Зарегистрируйся
Теперь на виртуальной машине с Windows запущу маяк и попробую выполнить BOF.

Для просмотра ссылки Войди или Зарегистрируйся
В выводе команды help можно увидеть наш BOF.

Для просмотра ссылки Войди или Зарегистрируйся
Теперь введем команду my_get_pid_bof и получим результат.

Для просмотра ссылки Войди или Зарегистрируйся
Проверим, что это соответствует действительности.

Для просмотра ссылки Войди или Зарегистрируйся
Да, так и есть. Маяк запущен с идентификатором процесса 7540, и BOF выводит то же самое.


Заключение​

Мы разобрались, как писать собственные простые BOF, проверять их с помощью COFFLoader, а также работать с Win32 API. Плюс ко всему получилось интегрировать BOF в популярный C2-фреймворк Adaptix и уже там проверить работоспособность. Мне кажется, это неплохая отправная точка для будущих исследований.
 
Activity
So far there's no one here
Сверху Снизу