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

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

S
startrek2008qawsed Newbie · 1 сообщений
Подписаться
70
[ обложка статьи ]
Режим чтения
Я не взломал их C2. Я заставил бота сдать его сам

## Как вскрыть командный канал майнера, который убивает твою отладку, — и почему ключ для этого не нужен

*Автор: SKYNET · Telegram @SIMCARDER22 · *

TL;DR. Linux-майнер RedTail (XMRig внутри, SSH-червь вокруг) прячет свой C2 за самописным протоколом dcb: X25519, ChaCha20-Poly1305, сессионные ключи, DoT-маскировка. Отладку он сносит вместе с «конкурентами». Ключ снять не удалось — и не понадобилось: я расшифровал канал, не трогая шифр, и вытащил всё содержимое. Кошелёк, впрочем, не достать в принципе — и это тоже часть истории.

---

## Почему это вообще интересно

Обычная малварь боится отладчика. Эта — не боится, потому что убивает его. Не «детектит gdb и выходит», а просто выпиливает соседние процессы в своём namespace, попутно снося с машины другой майнер-конкурент. Поймать её «в лоб» не выходит. А значит, чтобы понять, что она отправляет наверх, приходится придумывать что-то умнее стандартного набора: gdb, strace, DynamoRIO, Frida — мимо.

Дальше — весь путь: от ловушки до расшифрованного канала. С кодом, граблями и выводами.

---

## 1. Как всё началось

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

Из одного заражения выпал комплект:

Код:
loader.bin                                   (bash-загрузчик)
x86_64  i686  aarch64  arm7  riscv            (ELF, UPX)

Семейство опозналось по строкам: libredtail, путь сборки /var/build/redtail/scripts/, systemd-юнит your-redtail.service. Внутри — XMRig 6.26.0.

Железное правило сразу: всё дальше — только на изолированном стенде. Никаких обращений к чужим серверам. Строго пассивный перехват того, что бот отправляет сам.

---

## 2. Что это за зверь

| Параметр | Значение |
|---|---|
| Семейство | RedTail / libredtail (внутреннее имя) |
| Тип | Linux-криптомайнер Monero + самораспространяющийся червь |
| Майнер | XMRig 6.26.0 (GCC 15.1.0) |
| Сборка | статическая линковка, stripped, UPX, не PIE (база 0x400000) |
| Архитектуры | x86_64, i686, aarch64, arm7, riscv |
| ~95% кода | открытый: BoringSSL, libssh2 1.11.1, hwloc 2.14.0, snappy 1.2.2, libuv 1.52.1, abseil |
| Кастом | libredtail (форк libevent) + протокол dcb + червь |

Маскируется под что угодно живое:

Код:
php-fpm: pool www
nginx: worker process
postgres: user dbname 127.0.0.1 idle
/bin/mariadbd
systemd-worker   /bin/systemtd   /bin/-bash   /usr/bin/.sh

Персистенс — сервис your-redtail.service + @reboot в crontab (при этом он затирает чужие crontab: crontab -r; echo "@reboot {}" | crontab -).

«Не PIE» — это ключевая деталь. Адреса в файле совпадают с адресами в памяти. Запомни, это нам пригодится.

---

## 3. Инфраструктура

| IP | порт | роль | хостинг | ASN |
|---|---|---|---|---|
| 130.12.180.51 | 43782 | C2, канал dcb | Omegatech/Virtualine | AS202412 |
| 130.12.180.51 | 2137 | майнинг-прокси (стратум) | Omegatech/Virtualine | AS202412 |
| 31.56.209.165 | 2137 | C2 / прокси | SWISSNET LLC | AS209373 |
| 217.60.103.56 | 80/443 | сервер раздачи | SWISSNET LLC | AS209373 |
| 95.215.19.53 | 853 | C2 под DNS-over-TLS | Materialism s.r.l. | AS39287 |

Корреляция, за которую зацепился взгляд: C2 (31.56.209.165) и сервер раздачи (217.60.103.56) сидят на одном хостинге (SWISSNET, AS209373). Управление и раздача — одна инфраструктура, один оператор.

---

## 4. Протокол dcb (не TLS!)

Заголовок кадра — ровно 32 байта:

Код:
40 68 0d 1b   magic (0x1B0D6840)
05 00 00 00   версия = 5
64 63 62 00   tag "dcb"
00 00 00 00   флаги
00 00 05 18   длина payload (big-endian) = размер файла − 32
.. .. .. ..   nonce[12] (в перехваченных — нули)

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

Крипто-стек вокруг:

  • X25519 (ECDH) — обмен со статическим публичным ключом сервера;
  • ChaCha20-Poly1305 (AEAD) — защита записей;
  • транспорт — libredtail (форк libevent: evbuffer + TLS) поверх libuv;
  • метки ключей (вытащил из дампа): server-key, client-key, server-nonce, client-nonce.

---

## 5. Фокус с DoT: резолвер, который мне доверился

Бот резолвит свои C2 через DNS-over-TLS на порт 853, ротируя публичные резолверы (8.8.8.8, 1.1.1.1 и т.п.) — выглядит как обычный трафик. Скучно? Нет.

Сертификат TLS он не проверяет.

Я подсунул ему свой DoT-сервер с самоподписанным сертификатом — и получил полный MITM: бот спокойно отдавал мне имена, которые резолвит. Так родился полный список C2-доменов — 11 штук, из которых три оказались не в публичных отчётах:

Код:
proxy.identities.network    proxy.insanitycpp.cx    proxy.insanecppdev.com
p.2137gang.{st, pl, net, shop, store}          ← ротация TLD
proxies.identities.network  proxies.insanitycpp.cx  proxies.insanecppdev.com
                                              ↑ эти три — оператор держит отдельно

proxy.* — командный канал; proxies.* — точка входа майнинг-прокси. Одна и та же база, разные роли. Такое разделение в отчётах не описано.

---

## 6. Тупик: он убивает отладку

Дальше — самое интересное, ради чего стоит читать.

В бинарнике есть ChaCha20 (классический CRYPTOGAMS). Я нашёл единую C-обёртку, через которую проходят все SIMD-варианты:

Код:
// 0x614ce0
chacha20_ctr32( out, in, len, key /* rcx */, nonce /* r8 */ )

Кажется, всё просто: ставим брейкпоинт, читаем ключ из rcx. И вот тут малварь показала характер.

Она убивает всё в своём PID-пространстве. При старте сносит конкурентов (включая другой майнер — kdevtmpfsi, кстати из ботнета Kinsing) и заодно любую отладку, потому что отладчик — это просто ещё один сосед-процесс.

Как я ни подходил:

| Подход | Результат |
|---|---|
| gdb напрямую | бот его убивает |
| unshare + net-ns | harness гибнет вместе с gdb (даже dpkg при сборке образа умирал!) |
| gdb как PID 1 контейнера | выжил, но бот форкается и re-exec'ит себя — уходишь не в тот процесс |
| seccomp-запрет kill | gdb больше не убивают, но теперь падает сам gdb (внутренний баг) |
| eBPF/uprobe | ядро не даёт как надо в этом контейнере |

И вишенка: ключ ChaCha20 генерируется в рантайме из сильной энтропии и затирается. Классический перебор (LCG, Mersenne Twister, RC4 со словом «redtail») — ноль, шум.

Классический тупик. Пока не переформулируешь задачу.

---

## 7. Разворот: зачем мне ключ, если нужен текст?

Смотрите. Ключ нужен, чтобы расшифровать то, что бот отправляет. А что, если снять запись до шифрования?

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

C:
// FUN_004fdb20 @ 0x4fdb20
int dcb_send_record(ctx, type, const void *record /* rdx */, size_t len /* rcx */, ...);

record здесь — ещё плейнтекст. Значит дампим rdx длиной rcx.

Отладчика у нас нет (см. выше). Отладчик нам не нужен. Патчим бинарь.

### Патч, который бот не убьёт

Логика в три хода:

1. Не PIE → адрес в файле = адрес в памяти.
2. В .text есть «щели» из nop'ов между функциями → кладём туда стаб.
3. Пролог целевой функции переписываем на jmp в стаб; в конце стаба возвращаемся в функцию.

Находим щель (у меня — между .fini и .rodata, 0x7f8300), пишем стаб:

Код:
; --- simplification ---
push rax; push rcx; push rdx; push rsi; push rdi; push r11; push r14
mov  r14, rdx                 ; rdx = указатель на запись
mov  eax, 257                 ; sys_openat
lea  rsi, [rip+path]          ; путь к файлу-дампу
mov  edx, 0x441               ; O_WRONLY|O_APPEND|O_CREAT
mov  r10d, 0x1a4              ; 0644
syscall                       ; open
...                           ; write(fd, &len, 8)
...                           ; write(fd, record, len)
pop  r14; pop r11; pop rdi; pop rsi; pop rdx; pop rcx; pop rax
push r15; mov r15, rdx; push r14   ; восстановленный пролог
jmp  back

И точка входа превращается в:

Код:
0x4fdb20:  E9 <rel32 → 0x7f8300>   ; jmp в стаб
          90 90                      ; nop x2

Грабли, стоившие полдня: первая версия стаба давала мусор. Причина — в одну строку: mov edx, 8 (запись длины) затирал rdx с указателем на данные. Лечится сохранением указателя в r15 до этого. Классика, о которой узнаёшь только по факту.

Почему это работает: бот умеет убивать процессы. Отладчик — процесс. А патч — это байты внутри самого бота. Убивать нечего. Ты против него не «снаружи с отладчиком», ты уже внутри его кода.

---

## 8. Что оказалось внутри

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

Конфиг майнера — слово в слово:

JSON:
{
  "autosave": true, "opencl": false, "cuda": false,
  "cpu": { "enabled": true, "huge-pages": true, "max-threads-hint": 100 },
  "randomx": { "mode": "auto", "1gb-pages": true, "rdmsr": false, "wrmsr": true },
  "pools": [
    { "nicehash": true, "url": "proxies.identities.network:2137" },
    { "nicehash": true, "url": "proxies.insanitycpp.cx:2137" },
    { "nicehash": true, "url": "proxies.insanecppdev.com:2137" }
  ]
}

Команды распространения (то, чем червь «закрепляется» и чистит место):

Код:
crontab -r ; echo "@reboot {}" | crontab -
iptables -F ; iptables -I INPUT -p tcp --dport {} -j ACCEPT

~40 путей RCE — это и есть карта его атаки:

Код:
/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-брутфорс

Телеметрия бэкона: модель CPU, cgroups, ProcessName, LinuxCgroup — оператор собирает «паспорт» хоста.

---

## 9. Про кошелёк: почему его нет и не будет

Самый частый вопрос — «а кошелёк-то где?». Отвечаю окончательно: его у бота нет.

В конфиге майнера — только url пула и nicehash. Логина с XMR-адресом нет. Бот логинится на прокси оператора фиктивными кредами x:x, а настоящий кошелёк подставляется на стороне прокси — где-то там, куда нам хода нет.

Проверил всё: все пять архитектур, дампы памяти живого процесса, весь перехваченный трафик XOR-брутфорсом и не только. Ноль адресов Monero. Это не «мыло не докрутили» — это бай-дизайн. Приватный пул с IP-based auth: сеть майнеров одна, а кошелёк один и живёт только у оператора.

Вывод практический: видишь nicehash: true и login: "x" — не трать время на охоту за кошельком. Его там нет.

---

## 10. Что забрать с собой (выводы)

1. «Не могу снять ключ» ≠ «не могу прочитать трафик». Часто достаточно взять плейнтекст на входе шифрования — на один шаг раньше.
2. Против малвари, убивающей отладчики, работает не отладчик, а патч. Ты входишь внутрь её же кода.
3. Не PIE + nop-щели в .text — идеальные условия для инъекции стаба. Проверяй флаги сборки первым делом.
4. MITM на «доверчивом» транспорте бесплатен, если он не проверяет сертификат. DoT «под Chrome» — не значит безопасный.
5. Приватный пул = кошелёк недостижим по определению. Не гоняйся.

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

---

## 11. Честно о границах

  • Всё — на изолированном стенде, без контакта с инфраструктурой оператора.
  • Никаких эксплойтов и рабочего вооружения — только защитный разбор и методика.
  • IOC и YARA — по запросу.
  • Атрибуция не утверждается: RedTail — финансовая группировка, не APT.

---

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

Telegram: @SIMCARDER22 · SKYNET

---
Полезно

Комментарии

0