Sliver (или Sliver C2 framework) – это пентестерский фреймворк на языке Go с открытым исходным кодом, разработанный и поддерживаемый BishopFox – компанией, занимающейся кибербезопасностью. Он позиционируется как альтернатива коммерческому CobaltStrike, предоставляющая возможности постэксплуатации на уже скомпрометированной системе жертвы, и так же, как и CobaltStrike, активно используется злоумышленниками. В этом отчете мы расскажем о некоторых примечательных особенностях Sliver и о его эволюции, дадим рекомендации по извлечению конфигурации и других полезных метаданных, поделимся индикаторами компрометации, а также синтетическими образцами Sliver-имплантов для самостоятельного изучения, которые мы загрузили на VirusTotal и GitHub .
Архитектурно фреймворк состоит из клиентской консоли, которая управляется атакующими, имплантов (полезной нагрузки, запускаемой в системе жертвы) и сервера управления, который служит связующим звеном между клиентской консолью и имплантами.
С точки зрения реверс-инженера, самым интересным для анализа является имплант, выполняющийся на целевой системе.
Хронология расследуемых инцидентов и ключевых изменений импланта
До определенного момента у всех поступающих к нам образцов имплантов Sliver адрес командного центра (С2) тривиально расшифровывался в pre - main -функции, то есть до запуска основных функций импланта. Однако недавно к нам на анализ поступил образец, загруженный одному из наших клиентов с помощью другого вредоносного ПО - DarkGate Loader, в котором C2 расшифровывались неглубоко в main-функции.
Анализ коммитов в репозитории Sliver показал, что, начиная с версии 1.5.27 (20.09.2022), механизм расшифровки С2 претерпел существенные изменения:
Для сравнения, в более ранних версиях С2 хранились в массиве строк c2Servers и расшифровывались в одной из функций, вызываемой runtime_doInit . Это изменение побудило нас искать новый способ извлечения конфигурации из актуальной версии.
Стоит также отметить, что все обнаруженные нами образцы были обфусцированы garble, который заменил используемый с версии 1.4.0 (06.03.2021) gobfuscate . Возможная причина - проблемы при кросс-компиляции под MacOS.
На этом таймлайне мы совместили хронологию ключевых изменений в официальном репозитории Sliver и хронологию исследуемых образцов, связанных с крупными атаками на наших клиентов.
Особенности генерации импланта
Перед тем как описывать способы извлечения C2, необходимо выделить ключевые особенности генерации импланта, непосредственно влияющие на успех как динамического, так и статического анализа найденных образцов.
Ограничения запуска
При создании импланта можно задать различные условия для ограничения его запуска на целевой системе:
Аргумент |
Описание |
---|---|
-w, --limit-datetime
|
Запускаться только до определенной даты
|
-x, --limit-domainjoined
|
Запускаться только на хостах в домене
|
-F, --limit-fileexists
|
Запускаться только на хостах, где имеется указанный файл
|
-z, --limit-hostname
|
Запускаться на хостах с заданным hostname
|
-L, --limit-locale
|
Запускаться на хостах с заданной локалью
|
-y, --limit-username
|
Запускаться на хостах с заданным пользователем
|
Параметры генерации импланта, ограничивающие его запуск
Помимо вышеперечисленных опциональных условий, имплант всегда проверяет наличие отладчика с помощью syscall kernel32.dll.IsDebuggerPresent . Все эти проверки выполняются в функции limits_ExecLimits , вызываемой в функции main_main . Инструкция по нахождению описываемых функций в теле In The Wild (далее – ITW ) имплантов находится в приложении к этому отчету (Приложение 3. Как найти основные функции ).
Таким образом, если по неизвестной причине имплант не запускается, то можно просто пропустить функцию limits_ExecLimits любым доступным способом (например, пропустить в отладчике или запатчить имплант).
Поддельные C2
Также интересной особенностью является опция -c , –canary по добавлению Canary domains. Canary domains – это поддельные C2-домены, которые в коде не используются и добавляются в имплант на стадии обфускации строк. Они намеренно не обфусцируются, а значит, будут первыми из тех, что увидит реверс-инженер при анализе импланта. Кроме того, они уникальны для каждого образца, а это значит, что можно отслеживать резолвы таких доменов на стороне атакующих и выявлять конкретные семплы, которые «попались» на анализ исследователям. Более подробная информация – в разделе DNS C2 в документации Sliver .
Генерация имплантов без обфускации
Существует много общедоступных материалов по созданию имплантов Sliver (например, хороший цикл статей из 12 частей). Мы в свою очередь отметим некоторые особенности, которые, например, могут помочь исследователям быстро сгенерировать «боевой» имплант без обфускации garble или посмотреть исходный код созданного обфусцированного импланта.
После выполнения команды generate для создания импланта его исходный необфусцированный код сохраняется в каталоге:
~<username>/.sliver/slivers/<GOOS>/<GOARCH>/<implant_name>/src/github.com/bishopfox/sliver
Этот код можно использовать для компиляции образца без обфускации, так как все-таки билды с опцией -d , --debug у команды generate отличаются от обычных билдов наличием дополнительных функций логирования. Для сборки без обфускации нужно перейти в указанный выше каталог, задать переменные окружения GOOS и GOARCH (должны соответствовать команде generate) и добавить в PATH каталог с golang, который поставляется с фреймворком Sliver, например:
GOOS=<GOOS> GOARCH=<GOARCH> PATH=$PATH:~<username>/.sliver/go/bin/ go build -trimpath -o <desired_implant_name>
-
<GOOS> , <GOARCH> - операционная система (-o , --os ) и архитектура ( -a , --arch ) соответственно;
-
~<username>- путь до домашнего каталога пользователя;
-
<desired_implant_name> – любое имя импланта .
Также есть ситуации, в которых может пригодиться способ получения исходного кода обфусцированной версии импланта. Для этого можно использовать один интересный аргумент командной строки garble – debugdir , который позволяет указать каталог, куда будет сохраняться исходный код обфусцированного импланта.
Пример команды для этого:
GOOS=windows GOARCH=386 PATH=$PATH:/home/<username>/.sliver/go/bin/ garble -seed=random -literals -debugdir <path-to-implant-obf-source-code-dir> build -o <desired_implant_name>
После выполнения этой команды в <path-to-implant-obf-source-code-dir> будет отображаться исходный код обфусцированного импланта.
Извлечение конфигурации
В данном разделе мы рассмотрим способы получения C2 из имплантов, а также расскажем, какие еще ценные данные можно из них извлечь.
Определение режима связи импланта с С2: beacon vs session
Импланты могут работать в двух режимах: beacon и session – от них зависит способ связи с С2-сервером. В двух словах, имплант в режиме beacon периодически связывается с C2, забирает задачи и выполняет их. В этом режиме команды от злоумышленников выполняются с задержками, так как имеет место временной лаг между отправкой команды и очередным обращением импланта к С2.
В режиме session имплант постоянно подключен к C2, отклик от команд практически мгновенный (интерактивно). Именно в режиме session доступны подключения типа namedpipe и tcppivot для перенаправления трафика через цепочку других активных имплантов в условиях, когда на зараженной системе отсутствует возможность прямого подключения к серверу управления (нет связи с интернетом).
Режим работы импланта можно определить по функции, которая в исходном коде называется transports_StartBeaconLoop – для режима beacon или transports_StartConnectionLoop – для режима session. Функции имеют незначительные отличия в коде:
Для того чтобы определить режим работы уже скомпилированного боевого импланта, нужно найти эту функцию в дизассемблере. В ITW-образцах имена функций обфусцированы, поэтому можно воспользоваться нашей инструкцией по поиску в приложении (transports_StartBeaconLoop_func1). Далее нам поможет интересная особенность garble: в оптимизационных целях он обфусцирует только те строки, длина которых > 7 . Это приводит к тому, что в искомой функции в switch-case-блоке обфусцируются только строки «namedpipe» и «tcppivot». Таким образом, если в обнаруженной функции есть операции сравнения с регистром (cmp r32, r32 или cmp r64, r64 ), то это – функция transports_StartConnectionLoop_func1 , а имплант сконфигурирован в режиме session, так как имеются сравнения с обфусцированными строками «namedpipe» и «tcppivot». В противном случае это beacon-имплант, а функция – transports_StartBeaconLoop_func1 .
Самый быстрый способ получения адресов С2
Следуя академическому подходу, нужно найти функцию расшифровки С2, чтобы в дебагере поставить breakpoint и динамически получить адреса С2. Однако практика показывает, что самый простой и быстрый способ получения C2 на данный момент – это тривиальный запуск импланта в безопасном окружении (не в отладчике) и снятие дампа памяти его процесса, например, через Process Hacker или Sysinternals ProcDump (есть версия и для Linux). Далее в дампе можно легко найти C2 по протоколам, используемым Sliver:
wg://
mtls://
http://
https://
dns://
tcppivot://
namedpipe://
В случае использования протокола MTLS из дампа памяти импланта, кроме С2-адресов, можно также извлечь:
-
цифровые сертификаты и приватный ключ, используемый в коммуникации с С2, по строкам «MII » или «BEGIN CERTIFICATE » или «BEGIN EC PRIVATE KEY » (поиск чувствителен к регистру);
-
поле Subject в сертификате (certPEM) содержит имя импланта, заданное злоумышленником при его создании, в противном случае оно генерируется случайным образом.
В определенных обстоятельствах эта информация может быть полезна для атрибуции семплов, указывающих на определенную вредоносную кампанию или группу злоумышленников.
Определение числа C2 в импланте
Опытным путем установлено, что можно узнать общее количество реальных C2 в импланте по функции transports_C2Generator , в которой число зашифрованных C2 указывается в выделенном на рисунке блоке с единственной инструкцией mov r32. imm как раз перед созданием канала (runtime_makechan) ближе к концу функции. Это единственный блок в функции с одной инструкцией.
Этот «трюк» помогает определять число реально используемых С2 в импланте, что, в свою очередь, поможет косвенно выявить Canary domains – поддельные С2, описанные выше, которые не входят в это число.
Получение C2 в отладчике
Как говорилось выше, с 20.09.2022 был изменен механизм работы с C2. Это привело к тому, что функция расшифровки C2 переместилась вглубь main импланта. В старых версиях Sliver список C2 задавался в глобальной переменной вне main и функция по их расшифровке выполнялась в main_inittask , которая запускалась функцией runtime_doInit еще до запуска функционала самого импланта:
Для извлечения адресов С2 из импланта методом отладки мы должны найти функцию transports_C2Generator_func8 (инструкция по поиску ).
Далее внутри этой функции мы должны установить breakpoint на вызов net_url_Parse, который парсит строки C2. Здесь стоит отметить, что при тестировании у нас был имплант с тремя C2 (mtls, https, dns). На первых двух C2 в этой функции breakpoint срабатывал сразу же, а вот срабатывания breakpoint на третьем C2 (dns) приходилось ждать секунд 15– 20. Возможно, это из-за распределения работы потоков, но при разных запусках всегда нужно было ждать третьего C2, то есть перед тем, как вот таким способом вылавливать C2, нужно знать, сколько их вообще в импланте.
Получение C2 в отладчике для старых имплантов
В старых версиях имплантов (< 1.5.27) расшифровка списка адресов С2 выполняется в одной из функций main_inittask , которые запускаются функцией runtime_doInit . Стоит отметить, что main_inittask выполняется во втором вызове runtime_doInit (при первом вызове выполняется runtime_inittask).
Перед установкой breakpoint нужно перейти в функцию расшифровки списка C2 – предпоследняя функция в main_inittask (инструкция по поиску ).
Обычно она не такая большая, как на картинке, так как случаи использования нескольких С2 в одном импланте довольно редки. На рисунке видны названия функций расшифровки очередного C2. Если поставить breakpoint’ы после функции расшифровки С2 (выделены на рисунке), то в rax будет адрес строки с расшифрованным С2.
Функции расшифровки могут меняться и выглядеть в дизассемблере иначе из-за особенностей garble.
Вывод
Как указывалось ранее, в июле 2023-го в ходе расследования инцидента мы впервые столкнулись с имплантом Sliver, использующим новый механизм обработки С2. Это говорит о том, что злоумышленники не сидят на месте и постоянно обновляют используемый инструментарий. Трудно сказать, каков на данный момент процент атак с использованием новой схемы распаковки С2, но совершенно точно, он будет увеличиваться с каждым днем, как будет расти и число атак с использованием других возможностей Sliver, усложняющих расследование, таких как защита от запуска в песочницах и нецелевых системах, расширение списка используемых С2 путем добавления в него поддельных адресов и так далее. Мы надеемся, что данная статья и синтетические образцы имплантов Sliver будут полезны исследователям и позволят не только ускорить извлечение C2, но также помогут быстро разобраться в кастомных конфигурациях имплантов.
Приложения
Приложение 1. Индикаторы компрометации
В этом разделе представлены как боевые ITW-образцы, изученные нами в рамках расследований инцидентов, так и искусственно сгенерированные нами импланты для самостоятельного изучения и отработки описанных в статье техник.
Синтетические образцы были загружены как на VirusTotal, так и на Github.
ITW- образцы
b08b28f091e0fb4b776a338ec4029cd6 – x86 windows mtls implant steel industry company attack
18f995451db0df9687b3ee100ad9a67f - x64 windows https government entity attack
5b74f8e4e08afdf2dec0f9b7164ce5ce - x64 linux dns implant telecom company A attack
5c6707ba8de07b7de03052eefae468cf - x64 linux mtls implant telecom company B attack
27105de8e91c864fb04a7da9546f1e53 - x86 windows mtls implant telecom company C attack
Синтетические образцы
Имя |
|
Команда для генерации |
generate beacon -a 386 --http "10.10.10.1,10.10.10.2" --name sliver_win_386_beacon_2httpc2_for_education_only |
Описание |
Имплант старой версии 1.5.22 (С2 расшифровывается до main) с двумя http C2 с обфускацией |
Имя |
|
Команда для генерации |
generate beacon -a amd64 --http "http://10.10.10.1,http://10.10.10.2"; --mtls "10.10.10.3,10.10.10.4" --wg "10.10.10.5" --dns "10.10.10.6" --limit-datetime "2023-11-14T07:00:00.000Z" --limit-domainjoined --limit-fileexists "C:\Windows\Temp\flag_file.bin" --limit-hostname "sliver-implant-test-vm" --limit-locale "en-US" --limit-username "test-user" --name sliver_win_amd64_beacon_6c2_with_limits_for_education_only |
Описание |
Имплант версии 1.5.41 с множественными C2 и со всеми видами ограничений |
Имя |
|
Команда для генерации |
generate -a amd64 --named-pipe "10.10.10.1/pipe/sliver_pipe" --name sliver_win_amd64_session_namedpipe_for_education_only |
Описание |
Session имплант версии 1.5.41 с named pipe |
Имя |
|
Команда для генерации |
generate -a amd64 --mtls "10.10.10.1" --debug --name sliver_win_amd64_session_mtlsc2_debug_for_education_only |
Описание |
Имплант версии 1.5.41, собранный в debug-режиме, с mtls C2 |
Имя |
sliver_win_amd64_beacon_6c2_with_limits_not_stripped_wo_garble.zip |
Команда для генерации |
cd ~user/.sliver/slivers/windows/amd64/sliver_win_amd64_session_mtlsc2_debug_for_education_only/src/github.com/bishopfox/sliver/ GOOS=windows GOARCH=amd64 PATH=$PATH:/home/user/.sliver/go/bin/ go build -trimpath -o sliver_win_amd64_beacon_6c2_with_limits_not_stripped_wo_garble.exe |
Описание |
Необфусцированный имплант версии 1.5.41 sliver_win_amd64_beacon_6c2_with_limits.exe not stripped |
Имя |
|
Команда для генерации |
cd ~user/.sliver/slivers/windows/amd64/sliver_win_amd64_beacon_6c2_with_limits/src/github.com/bishopfox/sliver/ GOOS=windows GOARCH=amd64 PATH=$PATH:/home/user/.sliver/go/bin garble -seed=random -literals -debugdir ~/Downloads/garbled_source_code_sliver_win_amd64_beacon_6c2_with_limits build -o sliver_win_amd64_beacon_6c2_with_limits2.exe |
Описание |
Исходный код импланта 5a8b4a7e69169d16447674863ffcc158 (архив sliver_win_amd64_beacon_6c2_with_limits.zip), обфусцированный garble |
Приложение 2. Как найти main_main и main_inittask
Данные рекомендации применимы не только к имплантам, но и к любым golang-бинарям.
_rt0_amd64_windows : это Entry Point.
_rt0_amd64 : содержит jmp на runtime_rt0_go_abi0 .
runtime_rt0_go_abi0 : в начале несколько cmp-инструкций со строками "uneG" , "Ieni" , "letn" . В последнем блоке в регистре последнего push хранится адрес runtime_mainPC .
runtime_mainPC : хранит адрес runtime_main .
runtime_main : где-то в середине функции найти два косвенных вызова (indirect call: call r32 или call r64). В одном из регистров будет адрес main_main , обычно с комментарием IDA об offset. Также на рисунке слева показано, как найти main_inittask , который выполняется функцией runtime_doInit . Именно в main_inittask предпоследняя функция расшифровывает список C2 старых версий Sliver-имплантов (< 1.5.27).
Приложение 3. Как найти основные функции
В отчете мы часто упоминаем конкретные названия функций, но в реальности импланты Sliver обфусцируются при генерации по умолчанию. Чтобы понимать, где и как быстро находить основные функции ITW-импланта, мы разработали небольшую карту Sliver-импланта, скомпилированного под Windows (формат .exe). По внешнему виду эти функции не должны сильно различаться, и их можно находить без знания имен, используя карту и предложенные советы. Код импланта может отличаться в зависимости от его формата. Если вам встретится имплант другого формата, то всегда можно вручную собрать имплант нужного формата с debug-символами для сравнения (генерация имплантов без обфускации ).
main_main : во втором блоке предпоследняя вызываемая функция – limits_ExecLimits , последняя вызываемая функция – main_beaconStartup (main_sessionStartup).
main_beaconStartup ( main_sessionStartup ): последняя функция во втором блоке – transports_StartBeaconLoop ( transports_StartConnectionLoop ).
transports_StartBeaconLoop ( transports_StartConnectionLoop ) : последняя функция во втором блоке – transports_C2Generator . Последняя инструкция lea содержит адрес функции transports_StartBeaconLoop_func1 ( transports_StartConnectionLoop_func1 ).
transports_C2Generator : сначала найти единственный блок с одной инструкцией mov r32, imm (в imm – количество C2). В следующем блоке в последней инструкции lea – transports_C2Generator_func8 .