Все мы знаем, что идеальных преступлений не существует — злоумышленники всегда оставляют следы. Так, в октябре 2023 г. команда PT ESC CSIRT обнаружила признаки компрометации российской энергетической компании с использованием бэкдора Decoy Dog. За инцидентом стояла ранее неизвестная группировка Hellhounds.
В ходе расследования мы узнали и о других жертвах хакеров. Hellhounds успешно атакуют отечественные организации по меньшей мере с 2021 г., а разработку ВПО ведут как минимум с 2019-го. В этой статье мы расскажем об инструментарии и методах маскировки группы.
Атаки на Linux
Ранее о Decoy Dog рассказывали НКЦКИ, Infoblox, CyberSquatting и Solar 4RAYS. Однако образец, обнаруженный нами на хосте жертвы, представлял собой новую модификацию бэкдора: злоумышленники доработали его, чтобы усложнить обнаружение и анализ ВПО.
First Stage (Decoy Dog Loader)
Во время расследования мы обнаружили исполняемый файл /usr/bin/dcrond размером 9 КБ (см. рис. 1).
Он был защищен модифицированной версией упаковщика UPX с измененной сигнатурой 37 13 03 00 вместо UPX!. На момент расследования только один антивирусный движок детектировал пакер, а некоторые образцы и вовсе ничем не детектировались. Для обнаружения модифицированного UPX можно использовать публичное YARA-правило из исследования JPCERT/CC.
В отличие от обычного UPX эта модификация распаковывает не исполняемый файл, а шеллкод, полностью написанный на языке ассемблера и использующий только системные вызовы Linux. После модифицированного UPX-заголовка расположена зашифрованная конфигурация (хранит путь до зашифрованного файла с основной нагрузкой), следом за которой идет сжатый шеллкод (см. рис. 3).
Загрузчик работает в системе и маскируется под легитимный сервис cron. Также мы обнаружили образцы, маскирующиеся под легитимный сервис irqbalance и библиотеку lib7.so.
[Unit]
Description=Daemon to execute scheduled commands
Documentation=man:dcrond(8)
[Service]
Type=forking
ExecStart=/usr/bin/dcrond
Restart=always
В атаках 2022 г. злоумышленники маскировали образцы первых версий ВПО под сервисы atd и systemd-readahead-stop.service. Образцы размещались в директориях /usr/bin/atd или /usr/bin/container:
[Unit]
Description=Deferred execution scheduler
Documentation=man:atd(8)
[Service]
Type=forking
ExecStart=/usr/bin/atd
Restart=always
[Install]
WantedBy=multi-user.target
Сперва загрузчик проверяет, не находится ли он под отладкой. Для этого он читает /proc/self/status и проверяет, что TracerPid равен 0. Если же TracerPid отличен от 0, он подменяет себя на /bin/sh с помощью системного вызова execve.
Убедившись, что он не находится под отладкой, загрузчик пытается по порядку прочитать идентификаторы скомпрометированного хоста из следующих файлов:
- /etc/machine-id;
- /var/lib/dbus/machine-id;
- /var/db/dbus/machine-id;
- /usr/local/etc/machine-id;
- /sys/class/dmi/id/product_uuid;
- /sys/class/dmi/id/board_serial;
- /etc/hostid;
- /proc/self/cgroup.
Загрузчик считает MD5-хеш первого существующего в файловой системе файла и использует полученный хеш как ключ для расшифровки конфигурации и основной нагрузки, которые зашифрованы криптографическим алгоритмом CLEFIA с 128-битным ключом.
На этом этапе исследования стало понятно, что образец ориентирован на работу на конкретном хосте. При этом злоумышленники заранее получили доступ к хосту для получения идентификатора и добавления его в конфигурацию.
Second Stage (Decoy Dog)
Основная нагрузка исследуемого образца расположена в файловой системе по пути /usr/share/misc/pcie.cache. Расшифрованная нагрузка является модифицированной версией Pupy RAT, которую исследователи называют Decoy Dog.
Основные отличия Decoy Dog от Pupy RAT:
- Клиент обновлен с устаревшего Python 2.7 до Python 3.8
- Добавлен ряд новых функций, связанных с внедрением кода в виртуальную машину Java
- Добавлены новые транспорты: BOSH, lc4, lws4, ws4, dfws4
- Добавлена возможность загружать динамический конфигурационный файл и сохранять его на диске в зашифрованном виде
- Добавлен новый канал соединения (launcher) под названием special (локальное соединение с помощью IP-адреса и порта или файлового сокета)
- Повышена отказоустойчивость при помощи DGA и резервных управляющих серверов с указанием конкретных доменов
Исследуемый образец использовал управляющий сервер z-uid.lez2yae2.dynamic-dns[.]net, который находился в конфигурации, встроенной в исполняемый файл. Фрагмент конфигурации представлен на рис. 5.
Кроме того, троянская программа получает динамическую (актуальную) конфигурацию из файла /var/lib/misc/mpci.bin. Он зашифрован криптографическим алгоритмом AES в режиме счетчика (CTR) со 128-битным ключом (который, в свою очередь, зашифрован на эллиптической кривой brainpoolP384r1) и содержит новые управляющие серверы:
- m-srv.daily-share.ns3[.]name;
- f-share.duckdns[.]org.
Открытый ключ для расшифровки ключа AES хранится в конфигурации внутри исполняемого файла. Также в конфигурации исследуемого образца содержится скриплет, который запускается при каждом запуске бэкдора. Он называется telemetry и служит для отправки телеметрии (информации о зараженной системе) в социальную сеть mindly.social (на основе открытого движка Mastodon) через API сервиса. Содержимое телеметрии:
{
'cid': <идентификатор бэкдора из конфигурации>,
'user': <имя пользователя>,
'hostname': <имя хоста>,
'node': <MAC-адрес в виде 48-битного числа>,
'platform': <платформа>,
'node': <MAC-адрес в виде 48-битного числа>,
'pid': <идентификатор процесса бэкдора>,
'ppid': <идентификатор родительского процесса бэкдора>,
'cwd': <рабочая директория>,
'proc_arch': <архитектура запущенного процесса бэкдора>,
'exec_path': <путь к запущенному процессу бэкдора>,
'uac_lvl': <уровень защиты UAC>,
'intgty_lvl': <уровень целостности процесса бэкдора>,
'machine_key': <MD5-хеш от идентификатора системы>,
'proxy': <строка подключения к прокси-серверу по умолчанию>,
'external_ip': <внешний IP-адрес в виде 32-битного числа>,
'internal_ip': <внутренний IP-адрес в виде 32-битного числа>,
'boottime': <дата и время запуска машины (Unix time)>
}
Передаваемые данные шифруются таким же образом, как и файл динамической конфигурации, — с использованием того же открытого ключа. Поэтому даже при перехвате переданных данных их невозможно расшифровать без закрытого ключа.
Отправка данных осуществляется с помощью API-ключа, который хранится в коде в открытом виде. Но злоумышленники ограничили область доступа к нему только записью данных. Таким образом, даже при наличии API-ключа прочитать данные не получится.
Однако нам все же удалось установить, что телеметрия с зараженных хостов отправляется в аккаунт с юзернеймом @lahat, в честь которого мы и назвали исследование.
В Decoy Dog предусмотрен механизм генерации DGA-имен при потере связи по основному каналу управления.
Если в конфигурации включена опция bootstrap-domains (см. рис. 8), для генерации используется один из основных доменов. В противном случае генерируется поддомен для одного из указанных в конфигурации доменов верхнего уровня или домен для одной из указанных зон (по умолчанию используется домен верхнего уровня dynamic-dns.net). В конфигурации исследуемого образца были выбраны домены duckdns.org и dynamic-dns.net.
Резервный домен генерируется как первая половина шестнадцатеричного представления MD5-хеша от строки с текущей датой в формате <год><месяц><день> и открытого ключа, используемого при шифровании коммуникации с управляющим сервером.
Затем от получившегося домена (или одного из основных, если включена опция bootstrap-domains) считается MD5-хеш. После этого от первой половины шестнадцатеричного представления берется по два символа и дописывается слева к домену. В итоге получается набор из девяти доменов, к которым пытается подключиться ВПО. Например, для домена m-srv.daily-share.ns3[.]name получается восемь дополнительных доменов:
- 6cm-srv.daily-share.ns3[.]name
- 78m-srv.daily-share.ns3[.]name
- 7fm-srv.daily-share.ns3[.]name
- b1m-srv.daily-share.ns3[.]name
- 98m-srv.daily-share.ns3[.]name
- d5m-srv.daily-share.ns3[.]name
- 2fm-srv.daily-share.ns3[.]name
- 08m-srv.daily-share.ns3[.]name
Домены генерируются следующим кодом:
import datetime, hashlib
WELL_KNOWN_ZONES = ('dynamic-dns.net', )
def make_emergency_related_domains(domain):
domain_bytes = domain
if isinstance(domain_bytes, bytes):
domain = domain.decode()
else:
domain_bytes = domain.encode()
prefix_hash = hashlib.md5(domain_bytes).hexdigest()[:16]
for x in range(len(prefix_hash) // 2):
yield prefix_hash[x * 2:x * 2 + 2] + domain
class EmergencyDomains(object):
__slots__ = ('key', 'zones', 'beacon_domains', '_zone_id', '_emergency_loop')
def __init__(self, key, beacon_domains=None, zones=None):
self.key = key
self.zones = zones or WELL_KNOWN_ZONES
if not isinstance(self.zones, (list, tuple, set)):
self.zones = tuple((self.zones,))
self.beacon_domains = beacon_domains
self._zone_id = 0
self._emergency_loop = self._emergency_loop_generator()
def _emergency_loop_generator(self):
if self.beacon_domains:
for domain in self.beacon_domains:
yield domain
yield self._domain_of_the_day()
def iterate(self):
try:
while True:
yield next(self._emergency_loop)
except StopIteration:
self._emergency_loop = self._emergency_loop_generator()
def _domain_of_the_day(self):
now = datetime.datetime.utcnow()
ts_formatted = now.strftime('%Y%m%d')
if not isinstance(ts_formatted, bytes):
ts_formatted = ts_formatted.encode()
formatted_key = self.key
if not isinstance(formatted_key, bytes):
formatted_key = formatted_key.encode()
domain_hash = hashlib.md5()
domain_hash.update(ts_formatted)
domain_hash.update(formatted_key)
domain_part = domain_hash.hexdigest()[:16]
zone = self.zones[self._zone_id]
self._zone_id = (self._zone_id + 1) % len(self.zones)
return domain_part + '.' + zone
Атаки на Windows
В процессе реагирования на инцидент в российской транспортной компании команде PT ESC CSIRT также удалось обнаружить следы успешной атаки на Windows-инфраструктуру, о которых не было известно ранее.
First Stage (Decoy Dog Loader for Windows)
Получив доступ в систему, атакующие устанавливали сервис с именем Microsoft Account Service или Microsoft Viewer Service, который запускал исполняемый PE-файл с именем AccSrvX64__STABLE__2016-11-10.exe или R_TARIF.VIEWS_X86.EXE. Пример:
{
"Name": "Microsoft Account Service",
"Caption": "Microsoft Account Service",
"Description": "",
"DisplayName": "Microsoft Account Service",
"PathName": "C:\\[REDACTED]\\accounts64\\AccSrvX64__STABLE__2016-11-10.exe",
"ProcessId": 5092,
"Started": true,
"State": "Running",
"SystemName": "[REDACTED]",
"TimeLine": "2024-01-02T21:14:53.132165Z",
"ModuleName": "Win32_Service"
}
{
"Name": "Microsoft Viewer Service",
"Caption": "Microsoft Viewer Service",
"Description": "",
"DisplayName": "Microsoft Viewer Service",
"PathName": "C:\\[REDACTED] \\R_TARIF.VIEWS_X86.EXE",
"ProcessId": 5548,
"Started": true,
"State": "Running",
"SystemName": "[REDACTED]",
"TimeLine": "2024-01-03T22:04:30.5586058Z",
"ModuleName": "Win32_Service"
}
Интересно, что злоумышленники начали предпринимать активные действия в инфраструктуре скомпрометированной организации в новогодние праздники — второго и третьего января.
Размер исполняемого файла составляет 17 КБ. После успешного запуска сервиса образец расшифровывает список доменов, которые содержатся в секции .rdata, и пытается резолвить полученные доменные имена.
Каждый зашифрованный домен начинается с байта FF. Используется простой алгоритм шифрования на основе операций Xor и вычитания. В расшифровке участвует номер символа в строке и номер строки (символы в строках нумеруются с нуля).
Домены имеют следующий формат:
[-][!][!][...]<domain>
Параметр «-» означает, что успешно резолвить домен необязательно. Если его не удалось разрезолвить, загрузчик переходит к следующему домену из списка. Параметр «!» используется только совместно с параметром «-» и показывает количество попыток резолвов, после которых домен будет пропущен. Оно рассчитывается по формуле 2^n, где n — это количество указанных подряд «!». Если параметр не задан, будет сделана всего одна попытка резолва.
Домены из конфигурации используются при получении одной из частей ключа для расшифровки полезной нагрузки. Они также могут применяться для создания схожего с легитимным трафика и обхода песочниц.
В результате поверхностного динамического анализа образца можно подумать, что домены, которые используются на этом этапе, являются управляющими серверами. Но детальный анализ показывает, что домены и полученные впоследствии IP-адреса применяются не только для генерации ключа, но и, предположительно, для маскировки под легитимные утилиты. Кроме того, ВПО может использовать несуществующие поддомены на реально существующих доменах (например, mp0.ptsecurity.com). Это позволяет создавать видимость легитимности, при этом поддомен гарантированно не будет разрезолвен.
Обычно домен с параметром «-» используется только для создания трафика — не для генерации ключа. Такой домен не должен резолвиться, или же он будет разрезолвен после нужного домена. При этом один из доменов обязательно должен резолвиться и иметь фиксированный IP-адрес — он и будет использован для генерации ключа. Примечательно, что эту особенность злоумышленники могут использовать как kill-switch-механизм для прекращения функционирования ВПО в целевой системе.
После расшифровки и резолва всех доменов в конфигурации загрузчик расшифровывает следующий блок в секции .rdata (см. рис. 11).
Этот блок имеет фиксированный размер 256 байт и зашифрован алгоритмом CLEFIA в режиме сцепления блоков (CBC). Он содержит путь к основному бэкдору. Ключ формируется следующим образом: имя исполняемого файла без завершающего нуля приводится к верхнему регистру, затем к нему добавляется IP-адрес в байтовом представлении. После этого от получившейся байтовой строки считается 256-байтовый хеш SHA-3, первые 16 байт которого используются как ключ, а байты с 5-го по 20-ый используются в качестве вектора инициализации. Пример формирования ключа представлен на рис. 12.
Расшифровав путь к бэкдору, загрузчик читает его и расшифровывает тем же образом (используя тот же ключ и вектор инициализации), а затем передает управление на его точку входа. Интересно, что в отличие от образца под Linux никакой проверки целостности расшифрованных данных здесь нет.
В бэкдоре сигнатура MZ заменена на сигнатуру HE, а сигнатура PE перезаписывается в виде случайной 4-байтной последовательности.
Злоумышленники прикладывали много усилий, чтобы замаскировать свою активность на скомпрометированных узлах. К примеру, мимикрировали под MaxPatrol SIEM и сервисы Microsoft.
Примечательно, что образцы под Linux фактически не работали, если не проходили проверку идентификатора machine-id. То есть воспроизвести запуск ВПО без корректного идентификатора невозможно. При этом в образцах под Windows эта проверка отсутствует, зато имеется проверка имени исполняемого файла, которое никогда не совпадает с original filename из метаданных (это более слабая проверка). В случае изменения IP-адреса исследователи могут воспользоваться PDNS-сервисами (Passive DNS).
Second Stage (Decoy Dog for Windows)
Расшифрованная нагрузка практически не отличается от рассмотренной ранее версии Decoy Dog под Linux. Бэкдор основан на open-source-проекте Pupy RAT. Все образцы, которые нам удалось обнаружить, имели управляющий сервер net-sensors[.]net и DGA-домен dynamic-dns[.]net. Динамическая конфигурация отсутствовала во всех образцах.
Пример конфигурации:
{'debug': False, 'launcher': 'dnscnc', 'launcher_args': ['--domain', 'net-sensors.net', '-E', 'dynamic-dns.net'], 'delays': [(10, 5, 10), (50, 30, 50), (-1, 150, 300)] [REDACTED] 'cid': 61336226}
В ходе исследования мы выявили образцы под Windows, самый старый из которых был скомпилирован 29.11.2019, а самый свежий — 03.01.2024. Помимо Decoy Dog, злоумышленники использовали хорошо известный фреймворк Sliver с управляющим сервером 31.184.204[.]42 (ns2.maxpatrol[.]net). Детальный разбор аналогичных образцов смотрите в материале «Импланты Sliver под микроскопом: извлечение конфигурации и других полезных данных». В табл. 1 представлены все полученные нами образцы под ОС Windows.
Date | Description | SHA-256 | Name | Payload path | Domains |
29.11.2019 | First Stage (Decoy Dog Loader for Windows) — test version | 9a977571296ae1548c32df94be75eec2a414798bee7064b0bf44859e886a0cfa | testvec.exe | − | azure.microsoft.com |
14.07.2022 | First Stage (Decoy Dog Loader for Windows) | 4d30fd05c3bdac792e0a011892e2cad02818436484e81b6de6a02928149bc92d | MaxPatrol SIEM Agent.exe | fwtsqmfilefrwi | mp0.ignorelist.com -mp0.ptsecurity.com |
30.11.2022 | First Stage (Decoy Dog Loader for Windows) | e27d1bab901c1bb414d0849c5c132faa8c7c6a61357d9627a7d2785270034793 | Microsoft.exe | exmimeL5rq | − |
29.01.2023 | First Stage (Decoy Dog Loader for Windows) | 31b21de71f2162e8da1be8483f3a5d019b0c817832bc11a9f307b6b36821ca54 | − | − | act0.microsoft.com dns.msftncsi.com |
16.04.2023 | First Stage (Decoy Dog Loader for Windows) | 18d4a3a92b24b2ad75115a44fe2727081316eca346499a4aa00aa13713cf00cb | − | − | − act0.microsoft.com dns.msftncsi.com |
06.05.2023 | First Stage (Decoy Dog Loader for Windows) | 9a96c7b0595f628027c4f4caeece475ef742c420adf2fde8df934c6ce6481fb5 | − | − | − act0.microsoft.com dns.msftncsi.com |
16.08.2023 | First Stage (Decoy Dog Loader for Windows) | d9a8151aff9d1c061826a9812ed9a6600805c74a519df333513fd4a79d2d4e61 | NtpService.exe | C:\Users\Public\JProf\JProf.hbin | − act0.microsoft.com dns.msftncsi.com |
06.11.2023 | First Stage (Decoy Dog Loader for Windows) | 07fe71b256c1c913b0f3e3fa67e53d21a3d1f499beb4e550597f5743797a77c4 | Apache ActiveMQ.exe | − | act0.microsoft.com mvs05.zyns.com |
08.11.2023 | Second Stage (Decoy Dog for Windows) | e19dc185e99cfdc0c25f18fb34ffabff2a4877d6d5843e4c67c05ce182f9780e | NPipeX64_32.dll | − | C2: net-sensors.net DGA domains: dynamic-dns.net |
08.11.2023 | Second Stage (Decoy Dog for Windows) | 106436a4fafe00112b19b1374456c1746b988950b71d700680088d74494e4936 | r_tarif.dll2Qur | − | C2: net-sensors.net DGA domains: dynamic-dns.net |
27.12.2023 | Sliver | 510da6d88ae4dd51d62796023a18b39db08a016ee4ee7178b1afdc91c58f9e1e | − | − | C2: 31.184.204.42 |
27.12.2023 | Sliver | 6cb2979aa1fddd42df2ba596f705ce9bbdb2ec246649218d598d779769857c21 | − | − | C2: 31.184.204.42 |
02.01.2024 | First Stage (Decoy Dog Loader for Windows) | 1b8b4be020d3350d025c7a245eb0d7166ff2c329dc92af175ef0499cba583071 | AccSrvX64__STABLE__2016-11-10.exe | C:\ [REDACTED]\accounts64\NPipeX64_32.dll | − act0.microsoft.com dns.msftncsi.com |
03.01.2024 | First Stage (Decoy Dog Loader for Windows) | a03e2ca143e867a99e2bc73bd4e5c2dd078a9f671aa0a4ce9611a8bc39a769e2 | R_TARIF.VIEWS_X86.EXE | C:\ [REDACTED]\rtarif\r_tarif.dll2Qur | − act0.microsoft.com dns.msftncsi.com |
Примечательно, что большинство образцов содержат в конфигурации домен dns.msftncsi.com, который является тестовым сервером компонента индикатора состояния сетевого подключения (Network Connectivity Status Indicator, NCSI) в Windows.
Изучив конфигурации всех полученных образцов, мы выявили SSL-сертификаты, которые используются в бэкдоре для шифрования соединений с удаленными узлами. Сертификаты содержали самый ранний параметр notBefore от 26.12.2021 (21:51:52), а самый свежий параметр — от 08.11.2023 (13:48:36). Исходя из этого, можно предположить, что кампания началась как минимум в конце 2021 г. Сертификаты выпускались на один и три года с момента генерации образцов: подобный алгоритм реализован в публичном проекте Pupy RAT.
Initial Access
В двух инцидентах атакующим удалось проникнуть в инфраструктуру жертв через подрядчика. Скомпрометировав учетные данные для входа по протоколу SSH, злоумышленники установили жертвам Decoy Dog.
Нам также удалось получить содержимое с управляющего сервера net-sensor[.]net и обнаружить, что злоумышленники маскировали образцы Decoy Dog под ISO-образы сервиса iMind, который используется для проведения видеоконференций и вебинаров (см. рис. 17). К сожалению, нам не удалось выяснить, как злоумышленники вынуждали жертв запускать ISO-образы. При этом в сентябре 2023 г. НКЦКИ уведомляли об участившихся компьютерных инцидентах, связанных с эксплуатацией уязвимости в iMind, и рекомендовали обновиться до версии 3.19.
Жертвы
По нашим данным, как минимум 48 российских организаций были скомпрометированы с использованием Decoy Dog. Мы не знаем конечных целей атакующих, но в одном из инцидентов злоумышленники вывели из строя ряд сервисов крупного телеком-оператора (об этом рассказывали исследователи из Solar 4RAYS).
Анализ показывает, что злоумышленники фокусируются не только на госсекторе, но и активно атакуют российские ИТ-компании, большинство из которых являются подрядчиками критически важных организаций. Можно предположить, что хакеры нацелены на проведение атак типа Trusted Relationship. Распределение жертв по отраслям выглядит следующим образом (см. рис. 18).