РАЗБОР Reverse На проверке

Я не взломал их C2. Я заставил бота сдать его сам | РАЗБОР ТОГО КАК Я РЕВЕРСНУЛ КАСТОМНЫЙ ПРОТОКОЛ С2 ГРУППИРОВКИ 2137GANG

S
startrek2008qawsed Newbie · 1 сообщений
Подписаться
813
[ обложка статьи ]
Режим чтения
Обложка: реверс C2-протокола 2137GANG RedTail - терминал с дизассемблированием x25519 и nop-патчем, красный сетевой кабель со сниффером перехваченного трафика


У Linux-майнера RedTail канал управления закрыт самописным протоколом: X25519, ChaCha20-Poly1305, сессионные ключи, маскировка под DoT. Отладку он не детектит, а просто убивает - сносит соседние процессы в своём пространстве, попутно зачищая конкурентов с машины. Поймать «в лоб» не выходит.

Ключ снять мне не удалось. И, как выяснилось, не зря - нужен он не был. Я взял то, что бот отправляет наверх, на шаг раньше шифрования. Ниже весь путь: где тупик, где разворот, что нашлось внутри.

С чего началось​

У меня стоит Cowrie на 22-м и веб-панель, которая притворяется простаивающей нодой валидатора Solana - скучная, но для брутфорса лакомая. Мировой поток логинов идёт мимо круглосуточно, иногда приносит что-то крупное.

В этот раз выпал комплект: bash-загрузчик loader.bin и пять ELF под x86_64, i686, aarch64, arm7 и riscv, все под UPX. Опозналось по строкам: внутреннее имя libredtail, путь сборки /var/build/redtail/scripts/, systemd-юнит your-redtail.service. Внутри - XMRig 6.26.0.

Дальше только изолированный стенд, наружу ни одного пакета. Меня интересует исключительно то, что бот отправляет сам.

Что за зверь​

Семейство - RedTail, он же libredtail. Майнер Monero плюс самораспространяющийся SSH-червь. Сборка статическая, stripped, UPX, не PIE, база 0x400000 - эту деталь запомни, она потом сработала. Примерно 95% кода открытое: BoringSSL, libssh2, hwloc, snappy, libuv, abseil. Своё - форк libevent, протокол dcb и червь.

Маскируется под живые процессы: php-fpm: pool www, nginx: worker process, postgres: user dbname 127.0.0.1 idle, systemd-worker, /bin/-bash. Закрепляется сервисом и строкой в crontab, причём чужой crontab перед этим затирает: crontab -r, потом @reboot со своей командой.

«Не PIE» здесь принципиально: адреса в файле совпадают с адресами в памяти.

Инфраструктура​

IPпортрольхостингASN
130.12.180.5143782C2, канал dcbOmegatech/VirtualineAS202412
130.12.180.512137майнинг-проксиOmegatech/VirtualineAS202412
31.56.209.1652137C2 / проксиSWISSNET LLCAS209373
217.60.103.5680/443раздачаSWISSNET LLCAS209373
95.215.19.53853C2 под DNS-over-TLSMaterialism s.r.l.AS39287
Зацепился взглядом за одно совпадение: управляющий узел и сервер раздачи сидят на одном хостинге, SWISSNET, AS209373. Управление и доставка - одна инфраструктура, значит и оператор, скорее всего, один.

Протокол dcb, и это не TLS​

Заголовок кадра ровно 32 байта: magic 0x1B0D6840, версия 5, тег dcb, флаги, длина payload в big-endian, nonce на 12 байт. В перехваченном nonce был нулевой.

На TLS-сканере порт C2 отвечает WRONG_VERSION_NUMBER, потому что это не TLS, а свой бинарный формат. Получается аккуратно: аналитик запускает TLS-сканер, видит мусор и идёт дальше.

Крипто вокруг такой: X25519 для обмена со статическим ключом сервера, ChaCha20-Poly1305 на записи, транспорт - тот самый форк libevent поверх libuv. Метки ключей, которые я вытащил из дампа: server-key, client-key, server-nonce, client-nonce.

Резолвер, который мне доверился​

Свои C2 бот резолвит через DNS-over-TLS на 853, ротируя публичные резолверы - снаружи это выглядит как обычный трафик.

Но сертификат TLS он не проверяет. Я поднял свой DoT с самоподписанным сертификатом и получил полный MITM: бот спокойно отдавал имена, которые резолвил. Так собрался весь список доменов, одиннадцать штук, и три из них в публичных отчётах не встречались: proxy.identities.network, proxy.insanitycpp.cx, proxy.insanecppdev.com. Плюс ротация TLD - p.2137gang.{st,pl,net,shop,store} - и парные proxies.* на той же базе. Разделение простое: proxy.* - команды, proxies.* - вход в майнинг-прокси. В отчётах этого деления нет, а оно заметное.

Тупик​

Дальше начинается самое неудобное. В бинарнике есть ChaCha20 от CRYPTOGAMS, и я нашёл единую обёртку, через которую проходят все SIMD-варианты: chacha20_ctr32(out, in, len, key /* rcx */, nonce /* r8 */). Выглядит как подарок - ставим брейкпоинт и читаем ключ из регистра.

Не вышло. Малварь убивает всё в своём PID-пространстве, и отладчик для неё просто ещё один сосед. Перебрал по очереди: gdb напрямую - бот его снимает; unshare с net-ns - harness гибнет вместе с отладчиком, даже dpkg в сборке образа падал; gdb как PID 1 в контейнере - выживает, но бот форкается и перезапускает себя, и ты оказываешься не в том процессе; seccomp с запретом kill - отладчик больше не убивают, зато падает он сам, внутренняя ошибка; eBPF и uprobe - ядро в этом контейнере не даёт то, что нужно.

Отдельно про ключ: он генерируется в рантайме из сильной энтропии и затирается. Перебор по LCG, Mersenne Twister, RC4 со словом redtail - ноль, ровный шум.

Разворот​

И тут стоит переформулировать задачу. Мне не нужен ключ. Мне нужен текст, который бот отправляет. А значит, взять его надо там, где запись ещё открыта.

По декомпиляции находится подходящая сигнатура: dcb_send_record(ctx, type, record, len, ...), где record - ещё плейнтекст. Достаточно дампа по rdx длиной rcx. Отладчика у нас нет и не надо: патчим бинарь.

Логика в три хода. Раз бинарь не PIE, адрес в файле равен адресу в памяти. Между функциями в .text есть щели из nop'ов - туда влезает стаб. Пролог целевой функции переписываем на прыжок в стаб, в конце стаба возвращаемся обратно.

Щель нашлась между .fini и .rodata, на 0x7f8300. Стаб открывает файл через sys_openat (257), пишет длину, пишет саму запись, восстанавливает регистры, повторяет перезаписанный пролог и прыгает назад. Точка входа превращается в jmp на семь байт - E9 с относительным смещением и два 90.

Грабли, которые съели полдня: первая версия стаба писала мусор. Причина в одной строке - mov edx, 8 для записи длины затирало rdx, в котором лежал указатель на данные. Пришлось сохранять указатель в r15 заранее. Ошибка на ровном месте, заметная только по факту.

Почему это работает: бот умеет убивать процессы, а патч процессом не является. Это байты в нём самом. Ты не подходишь снаружи с отладчиком, ты уже внутри его кода.

Что оказалось внутри​

Прогон дал 25 МБ плейнтекста, 991 сессия. Бот складывал содержимое канала сам.

Конфиг майнера, слово в слово: autosave включён, OpenCL и CUDA выключены, CPU со всеми потоками, huge pages, randomx в авторежиме, и три пула - proxies.identities.network:2137, proxies.insanitycpp.cx:2137, proxies.insanecppdev.com:2137, все с nicehash. Команды распространения такие: crontab -r, затем @reboot со своей строкой, и зачистка iptables - iptables -F, потом разрешающий INPUT на нужный порт.

Пути, по которым он лезет, - это и есть его карта: /vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php (CVE-2017-9841), обход через /cgi-bin/.%2e/... (CVE-2021-41773 и 42013), PHP-CGI с allow_url_include и php://input (CVE-2024-4577), плюс открытый Docker API на 2375 и брутфорс SSH.

С телеметрией оператор тоже аккуратен: модель CPU, cgroups, ProcessName, LinuxCgroup - собирает паспорт хоста, чтобы понимать, куда попал.

Что из этого стоит забрать​

Первое и главное: «не могу снять ключ» не значит «не могу прочитать канал». Часто достаточно взять плейнтекст на входе шифрования, на один шаг раньше.

Второе: против малвари, которая убивает отладчики, работает не отладчик, а патч. Процесс можно снять, байты в код не снимешь.

Третье: не PIE плюс nop-щели в .text - готовые условия для стаба. Флаги сборки стоит смотреть раньше, чем разбираться с крипто.

Четвёртое: MITM на доверчивом транспорте бесплатен. DoT без проверки сертификата не безопаснее обычного DNS, просто выглядит приличнее.

И общий вывод, который я вынес для себя: в разборе малвари выигрывает не самый громкий инструмент, а тот, кто раньше переформулировал задачу. Я не ломал их канал. Я дал боту возможность сдать его самому.

Атрибуция не утверждается: RedTail - финансовая группировка, не APT.

Разбираю малварь, которая считает себя умнее аналитика. Пишите, если есть что разобрать вместе. Telegram: @SIMCARDER22
Полезно

Комментарии

1
Привет, можешь показать все 40, пожалуйста? Интересно
/vendor/phpunit/phpunit/src/Util/PHP/eval-stdin.php CVE-2017-9841
/cgi-bin/.%2e/.%2e/…/bin/sh CVE-2021-41773 / CVE-2021-42013
/index.php?-d+allow_url_include=on+…+php://input CVE-2024-4577
+ Docker Engine API :2375 (без auth) и SSH-брутфорс