Оглавление
- Что такое CrossC2?
- Попытка 1. Погружаемся в дебри beacon’a
- Попытка 2. Заглядываем в genCrossC2
- Собираем всё воедино
- Вывод
- IOCs
- Приложение 1. Расширенная информация по файловым IOCs
В процессе реагирования на инциденты к нам на анализ приходят довольно разнообразные сэмплы, в том числе и различные вариации beacon’ов CobaltStrike. Наше внимание привлек CrossC2 — генератор полезных нагрузок под Unix-системы. По какой-то причине на просторах интернета встречается лишь одна техническая публикация, посвященная данному фреймворку, - от китайской ИБ-компании Qi An Xin.
В данной статье мы попробуем окунуться в процедуру генерации beacon’ов для версии 3.3, а также опишем процесс извлечения из них конфигов статически для получения из них полезных индикаторов.
Что такое CrossC2?
Этот фреймворк позволяет генерировать полезные нагрузки Cobalt Strike под Unix-платформы. Вместе с самим генератором (genCrossC2) предоставляются Agressive-скрипты для взаимодействия с нагрузкой. Данный генератор также является кросс-платформенным, так как написан на языке Golang.
Другим нюансом является то, что genCrossC2 выложен в официальном репозитории только в скомпилированном виде без доступа к исходному коду, а также обфусцирован, что значительно затрудняет его анализ.
В настоящий момент злоумышленники продолжают использовать данный фреймворк в различных вредоносных кампаниях. Упоминания о нем встречаются в отчетах от Moonlock Lab (июль 2023), Recorded Future (июль 2024). Последний раз семплы, сгенерированные с помощью данного фреймворка, встречались нам в августе 2024 года.
Среди доступных опций есть несколько платформ. Есть возможность запаковать сгенерированный бинарный файл с помощью UPX. Приведенный на скриншоте генератор поддерживает версии CobaltStrike 4.x. Версии в репозитории распределяются следующим образом.
Ветка |
CS3.14 |
CS4.0 |
CS4.X (4.1~4.8) |
---|---|---|---|
Master |
+ |
|
|
cs4.0 |
|
+ |
|
cs4.1 |
|
|
+ |
В расширенном ReadME приводится информация по поддерживаемым платформам.
|
Windows |
Linux |
MacOS |
iOS |
Android |
Embedded |
---|---|---|---|---|---|---|
Run Env (x86) |
|
+ |
|
|
|
|
Run Env (x64) |
+ |
+ |
+ |
|
|
|
gen beacon (x86) |
|
+ |
|
|
+ |
|
gen beacon (x64) |
|
+ |
+ |
|
|
|
gen beacon (armv7) |
|
|
|
in progress |
+ |
|
gen beacon (arm64) |
|
|
|
+ |
+ |
|
gen beacon (mips[el]) |
|
|
|
|
|
+ |
Попытка 1. Погружаемся в дебри beacon’a
Рассмотрим подробнее сэмпл, сгенерированный с использованием фреймворка CrossC2, который встретился нам ITW.
MD5
b103b00239a6819abd9d8c69dc9eaec7
SHA1
3850f7cccbdb3b08d05df6bde09e4396569628f5
SHA256
1a8c9b9a953012396ee8140b04b4d2a9a383d68708dea670d0b1396a96e5e2fa
File Type
ELF
File size
1393 kB
File Name
t_cc2
Бинарный файл запакован с помощью UPX, однако расшифровка с помощью upx -d не удается.
Если посмотреть в конец файла, то можно заметить, что к нему добавлена некоторая зашифрованная структура, которая начинается с сигнатуры HOOK.
Если убрать из файла данную структуру, то UPX сможет успешно ее распаковать.
MD5
e7347a3cb7cef52e728112b372c3d09a
SHA1
364d185fe2cfb55346d1718d2c76d5d955ddaa71
SHA256
3d1846f254f33e7d03d36491ba5980c715006053dd17a3a67348cc5fa4377739
File Type
ELF
File size
3713 kB
В распакованном файле встречается обфускация LLVM, которая включает шифрование строк. Расшифровка строк происходит побайтово с помощью XOR. Ключ хранится в виде imm8-значений в коде.
Среди расшифрованных строк встречается скрипт на Python.
import imp
import platform
cc2_g_modules = {}
if (int(platform.python_version()[0]) == 2):
class StringImporter(object):
def __init__(self, modules):
self._modules = dict(modules)
def find_module(self, fullname, path):
if fullname in self._modules.keys():
return self
return None
def load_module(self, fullname):
if not fullname in self._modules.keys():
raise ImportError(fullname)
new_module = imp.new_module(fullname)
code = compile(self._modules[fullname], "", "exec")
exec(code,new_module.__dict__)
return new_module
def StringImport(data):
sys.meta_path.append(StringImporter(data))
elif (int(platform.python_version()[0]) == 3):
import importlib
import types
class StringLoader(importlib.abc.Loader):
def __init__(self, modules):
self._modules = modules
def has_module(self, fullname):
return (fullname in self._modules)
def create_module(self, spec):
if self.has_module(spec.name):
module = types.ModuleType(spec.name)
exec(self._modules[spec.name], module.__dict__)
return module
def exec_module(self, module):
pass
class StringFinder(importlib.abc.MetaPathFinder):
def __init__(self, loader):
self._loader = loader
def find_spec(self, fullname, path, target=None):
if self._loader.has_module(fullname):
return importlib.machinery.ModuleSpec(fullname, self._loader)
def StringImport(data):
sys.meta_path.append(StringFinder(StringLoader(data)))
Из-за обфускации дальнейший анализ затруднен, поэтому попробуем найти обходные пути.
Попытка 2. Заглядываем в genCrossC2
Мы заглянули в сам генератор beacon’ов в надежде найти что-то интересное. В репозитории CrossC2 можно скачать бинарь genCrossC2 для Windows, Linux, MacOS. Для простоты возьмем версию под Windows.
MD5
186eacd22fe98b0ccb7b07994b3f9933
SHA1
a1d1fcf6cef691bab0678ae55df1dd0e2172a24e
SHA256
315a7a92dc639983ee24bf711986fca8eb2c880915a188f1d897029d22f2fc51
File Type
PE32+
File size
3713 kB
File Name
genCrossC2.exe
Это запакованный с помощью UPX файл, написанный на языке Golang. Символы не обфусцированы, однако в генераторе также частично встречаются зашифрованные строки и запутывание CFG.
В файле можно встретить очень длинную закодированную base64 строку, которая при декодировании оказывается .jar файлом. Данный файл вшит в сэмпл genCrossC2 и предназначен для десериализации публичного и приватного ключей (.cobaltstrike.beacon_keys
), которые сгенерировал TeamServer. Публичный ключ затем кодируется в base64 и записывается в файл .sub_bpKey.txt
Похоже, разработчик genCrossC2 не захотел бороться с десериализацией объектов Java, используя Go, и решил написать вспомогательную программу на языке Java. Получился своего рода “костыль”.
Так как статический анализ основного бинарного файла также сильно затруднен, посмотрим работу программы в динамике. Будем использовать API Monitor. Попробуем сгенерировать под ним beacon CobaltStrike 4.8 для Linux, запакованный с помощью upx
> genCrossC2.exe 192.168.17.128 443 .cobaltstrike.beacon_keys null Linux x64 output_upx upx 4.8
В трейсах заметим инициализацию строк с последующим вызовом функций стандартной библиотеки, среди которых увидим следующее:
Данная структура очень похожа на конфиг. При дальнейшем анализе это предположение подтвердится, ведь затем нам попадется memcpy уже с зашифрованными байтами, которому будет предшествовать следующий вызов:
Данный вызов похож на инициализацию ключа или каких-то вспомогательных данных, связанных с шифрованием. Благо API Monitor предоставляет информацию о стеке вызовов. Нас интересует функция по смещению 0xda39
.
При дальнейшей отладке выясняется, что для шифрования используется алгоритм AES-128 CBC, а значения aaaabbbbccccdddd
и abcdefghijklmnop
соответственно представляют собой ключ и вектор IV.
Интересной особенностью является то, что эти параметры, вероятно, одинаковы для всех когда-либо сгенерированных с помощью genCrossC2 полезных нагрузок, а сами нагрузки являются предкомпилированными. Хэши распакованных предкомпилированных нагрузок приведены в приложении.
Собираем всё воедино
В результате динамического анализа мы узнали ключ шифрования и вектор IV для конфига. Попробуем расшифровать overlay, который был присоединен к запакованной нагрузке. Для начала опишем структуру зашифрованного конфига.
struct config_encrypted {
DWORD magic; // HOOK
DWORD firstPartDataSize
BYTE firstPartData[firstPartDataSize];
DWORD secondPartMagic; // 08 FB 80 8F
DWORD secondPartDataSize;
BYTE secondPartData[secondPartDataSize];
};
Она начинается с сигнатуры HOOK. За сигнатурой следует размер данных первой части конфига, за которым следует сигнатура второй части с ее размером и данными. Что за первая и вторая часть? В первой части конфига хранится основная информация о C2.
struct config_decrypted {
DWORD dataSize; // длина последующих данных без учета этого поля
DWORD ip; // IP в виде байтов, напр. C0 A8 11 80 - 192.168.17.128
DWORD port; // то же самое, но с портом
DWORD ipStrSize; // длина строкового представления IP
BYTE ipStr[ipStrSize]; // строковое представление IP
DWORD beaconConfigurationSize; // config.ini
BYTE beaconConfiguration[beaconConfigurationSize]; // config.ini
DWORD publicKeyStrLen; // размер публичного ключа
STR publicKey[publicKeyStrLen];
}
beaconConfiguration
представляет собой глобальную конфигурацию полезной нагрузки, которая настраивается с помощью .ini файла. Пример такой конфигурации приведен на вики-странице репозитория CrossC2:
# Configure the function symbol and global configuration of the beacon itself hook
[c2_config]
# hook function
# initialization stage: void cc2_init() {}
cc2_init = aa1
# Error reconnection stage, incoming reconnection times: void cc2_retryConnect(int retryCount) {}
cc2_retryConnect = bb
# Function name customization for protocol rebinding library
cc2_rebind_get_protocol = cc
cc2_rebind_post_protocol = dd
cc2_rebind_http_get_send = ee3
cc2_rebind_http_get_recv = ff
cc2_rebind_http_post_send = gg
cc2_rebind_http_post_recv = hh
# global configuration
# automatically delete after running
cc2_auto_delete = false
# background process
cc2_daemon = false
# sleep time (10sec)
sleeptime = 10
# heartbeat jitter time
jitter = 37
# data submission jitter time
data_jitter = 100
# create the pipe name of the task(default joblist)
job_pipe_name = joblist
# the name of the pipe to run the task on(default .syspipe)
process_pipe_name = sys_pipe
# requested dns service
dns_server = 8.8.8.8
Если файл конфигурации отсутствует, то при генерации его размер в конфиге будет равен нулю. Пример расшифрованной первой части можно видеть на рисунке ниже.
Вторая часть является опциональной и добавляется при переопределении Malleable Profile. Концепция Malleable Command and Control используется в Cobalt Strike для контроля индикаторов HTTP, в число которых входит URI, различные HTTP заголовки и другое. На вики-странице CrossC2 приведен следующий пример профайла:
http-get {
set uri "/aaaaaaaaa";
set verb "GET";
client {
header "Accept" "accccccc";
header "Host" "www.google.com";
header "Referer" "http://www.google.com/";
header "Accept-Encoding" "gzip, deflate";
metadata {
base64url;
prepend "SESSION=";
header "Cookie";
}
}
server {
header "Server" "nginx";
header "Cache-Control" "max-age=0, no-cache";
header "Pragma" "no-cache";
header "Connection" "keep-alive";
header "Content-Type" "charset=utf-8";
output {
base64;
prepend "ffffffff1";
append "eeeeeeee2";
print;
}
}
}
http-post {
set uri "/bbbbbbbbb";
set verb "POST";
client {
header "Accept" "accccccc";
header "Host" "www.google.com";
header "Referer" "http://www.google.com/";
header "Accept-Encoding" "gzip, deflate";
id {
base64;
parameter "SESSION";
}
output {
base64url;
print;
}
}
server {
header "Server" "nginx";
header "Cache-Control" "max-age=0, no-cache";
header "Pragma" "no-cache";
header "Connection" "keep-alive";
header "Content-Type" "charset=utf-8";
output {
mask;
base64url;
prepend "ffffffff1";
append "eeeeeeee2";
print;
}
}
}
Расшифрованный вариант профайла выше приведен на следующем рисунке.
Как можно видеть, эта часть конфига состоит из двух блоков. Первый содержит размеры полей, а второй - их непосредственные значения.
Для ITW-сэмпла конфиг в расшифрованном виде выглядит следующим образом:
Где 45.153.231[.]244 - адрес C2. Данный IP принадлежит “любимому” хостеру злоумышленников Stark Industries. Если обратиться к Shodan, то можно найти еще один командный сервер тех же злоумышленников - 94.232.247[.]97, так как они забыли поменять заголовок Host в Malleable Profile, который остался равен адресу предыдущего сервера.
Вывод
CrossC2 создает грозное впечатление за счет своей обфускации и сложной структуры, что затрудняет анализ. Однако, как показывает наш случай, даже в таких, казалось бы, безнадежных ситуациях существуют методы, позволяющие извлечь критическую информацию из семплов. С помощью динамического анализа нам удалось изучить механизм шифрования конфига и расшифровать конфигурационные данные и Malleable Profile, что открыло доступ к информации о C2-серверах. Этот пример подчеркивает важность комплексного подхода к анализу вредоносного ПО и демонстрирует, что даже при высоком уровне обфускации анализ семплов не безнадежен.
IOCs
C2
45.153.231[.]244
94.232.247[.]97
File Hashes
MD5
b103b00239a6819abd9d8c69dc9eaec7
Распакованные файлы
e7347a3cb7cef52e728112b372c3d09a
0b32ee72f3c1c2a64ff69158a4346732
8ba0a4627d51cbd97c38a2f74897a873
65f4a19162d8de65616e855a5d0f488d
ce926ee1f669a975c22bf8be0bedfcc7
f02d85e80ca322df19bf9b8efae65f60
d784d9fc0fda2c75355ed7785ac8ea0c
623452111f8e6e745452b7f8496e4ee4
SHA1
3850f7cccbdb3b08d05df6bde09e4396569628f5
Распакованные файлы
364d185fe2cfb55346d1718d2c76d5d955ddaa71
1cd2c10b53cf59fb1c00cf341e3e00adda5fdc89
6399729d00fae2ae1ee98a860cebc6b56f74a1a9
3fc8a341a52659d1bf7eb4db6b33139ab6dc3fac
5bfcdb326f9ca5d18704ffd58a8137f38adb10ba
e1232f0e6fd45a7f861fa3e248ec49c50473d1f1
fd1be901feea0b7f59554bae305e354cd29aaa90
d21c995dfe3dd900dbe8ea219b96d40ba0051920
SHA256
1a8c9b9a953012396ee8140b04b4d2a9a383d68708dea670d0b1396a96e5e2fa
Распакованные файлы
3d1846f254f33e7d03d36491ba5980c715006053dd17a3a67348cc5fa4377739
fdae151fc523334d7b443e14c0fd6dbac59cf0e487598e529c87b33e2d140e4a
09f37abffeb1eced45cdc97165eb873db7a00aec00b5d1a62ea09cb22d791a2f
122755f45ad2860509a402641d9db8555ceee049cc26dd1af747b39e230c8fd5
5769fa8d6da03c0c895186ce896045523ee3acfc7dec8fd49e2955006d5bb81a
7e3f714fc750a6ef8e9f8b7c47ad6beed0d49e3a512b2a367552db21378b8a4b
51f2eb0e8b846d333d6c2168b376f8c3e973ce83c485606d319acf0e9ef799d0
3bd2682928099327f8e186a6d41caddb7a9a4ed00f0eaffe25a859ac28206a38
Приложение 1. Расширенная информация по файловым IOCs
MD5 |
SHA1 |
SHA256 |
Комментарий |
---|---|---|---|
e7347a3cb7cef52e728112b372c3d09a |
364d185fe2cfb55346d1718d2c76d5d955ddaa71 |
3d1846f254f33e7d03d36491ba5980c715006053dd17a3a67348cc5fa4377739 |
Linux-x64 (+ITW) |
0b32ee72f3c1c2a64ff69158a4346732 |
1cd2c10b53cf59fb1c00cf341e3e00adda5fdc89 |
fdae151fc523334d7b443e14c0fd6dbac59cf0e487598e529c87b33e2d140e4a |
MacOS |
8ba0a4627d51cbd97c38a2f74897a873 |
6399729d00fae2ae1ee98a860cebc6b56f74a1a9 |
09f37abffeb1eced45cdc97165eb873db7a00aec00b5d1a62ea09cb22d791a2f |
Linux-x86 |
65f4a19162d8de65616e855a5d0f488d |
3fc8a341a52659d1bf7eb4db6b33139ab6dc3fac |
122755f45ad2860509a402641d9db8555ceee049cc26dd1af747b39e230c8fd5 |
Linux-bind-x86 |
ce926ee1f669a975c22bf8be0bedfcc7 |
5bfcdb326f9ca5d18704ffd58a8137f38adb10ba |
5769fa8d6da03c0c895186ce896045523ee3acfc7dec8fd49e2955006d5bb81a |
Linux-bind-x64 |
f02d85e80ca322df19bf9b8efae65f60 |
e1232f0e6fd45a7f861fa3e248ec49c50473d1f1 |
7e3f714fc750a6ef8e9f8b7c47ad6beed0d49e3a512b2a367552db21378b8a4b |
Linux-lib-x64 |
d784d9fc0fda2c75355ed7785ac8ea0c |
fd1be901feea0b7f59554bae305e354cd29aaa90 |
51f2eb0e8b846d333d6c2168b376f8c3e973ce83c485606d319acf0e9ef799d0 |
Linux-lib-x86 |
623452111f8e6e745452b7f8496e4ee4 |
d21c995dfe3dd900dbe8ea219b96d40ba0051920 |
3bd2682928099327f8e186a6d41caddb7a9a4ed00f0eaffe25a859ac28206a38 |
MacOS-lib |
b103b00239a6819abd9d8c69dc9eaec7 |
3850f7cccbdb3b08d05df6bde09e4396569628f5 |
1a8c9b9a953012396ee8140b04b4d2a9a383d68708dea670d0b1396a96e5e2fa |
ITW семпл t_cc2 |