Это ты на фото? SMS-RAT. Методы обфускации

Привет, Хабр! После короткого перерыва на связи снова команда uFactor и я, Иван Князев.

Угрозы для устройств на базе Android хорошо изучены, но вместе с тем вариантов их реализации становится всё больше. Если ранее злоумышленники делали акцент на сложный функционал и полный контроль над устройством, то сегодня они всё чаще выбирают облегчённые версии, которые проще распространять и сложнее детектировать.

В данный момент наибольший масштаб на территории РФ приобрели следующие классы ВПО под Android:

  • NFCGate

  • Mamont

NFCGate — реализация атаки типа Relay Attack, где устройство жертвы выступает ретранслятором NFC‑сигнала. Первоначально проект был создан в исследовательских целях (ссылка).

В большинстве случаев ВПО распространяется под видом «приложения ЦБ для перевода средств на безопасный счёт». Выделяют два вектора атаки:

Примечание: приложение должно быть выбрано как платёжное по умолчанию либо пользователь должен вручную выбрать его при поднесении устройства к терминалу.

Прямой: Жертву убеждают приложить карту к телефону (а также ввести пин‑код в приложении) — в результате данные считываются через NFC и передаются злоумышленнику для операций в его интересах.

Цель: завладеть картой жертвы.

Рисунок 1. Прямой NFCGate. Схема работы
Рисунок 1. Прямой NFCGate. Схема работы

Обратный: Жертву убеждают приложить телефон к банкомату и внести свои наличные средства на указанный счет (к примеру, «безопасный счет»). В данном случае в приложении присутствует информация о карте злоумышленника (так называемого дропа). То есть происходит эмуляция карты злоумышленника.

Цель: пополнение подконтрольного счета с помощью наличных.

В отличие от NFCGate, который реализует атаку на платёжный интерфейс, семейство Mamont представляет собой классический Remote Access Trojan (RAT), нацеленный на контроль над устройством жертвы.

Классификация реализаций Mamont

В рамках данного семейства были выделены две актуальные ветви Android RAT, различающиеся по требуемым привилегиям и, в зависимости от них, — по сложности установки и устойчивости к детектированию:

Параметр

Полноценный RAT

SMS‑RAT

Примеры

CraxsRAT, BTMOB, Anubis, ClayRat

Облегчённые модификации Mamont RAT

Функционал

Полный контроль: экран, камера, микрофон, файловая система, захват клавиатуры, в том числе доступ к телеметрии

 

Сфокусирован на телеметрии: SMS, звонки, USSD, базовая информация об устройстве

 

В данной статье сосредоточимся на SMS‑RAT — облегчённой, но наиболее распространённой реализации семейства Mamont. В настоящее время такие APK массово распространяются через спам‑рассылки и фишинговые кампании, а минимальный набор требуемых разрешений существенно упрощает установку приложения на устройство.

Базовые (минимальные) разрешения для SMS‑RAT:

1.     SMS‑разрешения:

  • RECEIVE_SMS — получение SMS

  • READ_SMS — чтение SMS

  • SEND_SMS — отправка SMS

2.     Разрешения к телефону:

  • READ_PHONE_STATE — чтение состояния телефона (IMEI, сеть, вызовы)

  • READ_PHONE_NUMBERS — чтение номеров телефонов

3.     Системные разрешения:

  • RECEIVE_BOOT_COMPLETED — автозапуск после загрузки

  • POST_NOTIFICATIONS — отправка уведомлений

4.     Сетевые разрешения:

  • INTERNET — доступ к интернету

  • ACCESS_NETWORK_STATE — информация о сетевом подключении

5.     Фоновые службы:

  • FOREGROUND_SERVICE — запуск фоновых служб

  • FOREGROUND_SERVICE_DATA_SYNC — синхронизация данных в фоновом режиме

В некоторых реализациях SMS‑RAT запрашиваются также READ_CALL_LOG (история звонков) + QUERY_ALL_PACKAGES (список всех установленных приложений), CALL_PHONE (совершение звонков и отправка USSDкоманд).

Архитектура вредоносного приложения строится вокруг двух ключевых модулей и двух дополнительных, каждый из которых реализует конкретный функционал по сбору данных или управлению устройством: 

Модуль

Назначение

Как используется злоумышленником

Сбор информации о приложениях

 

Анализ списка установленных на устройстве программ

 

Выявление банковских клиентов, криптокошельков и антивирусов для определения «ценности» жертвы и адаптации дальнейших атак

 

Перехват и управление SMS

 

Чтение входящих сообщений, отправка исходящих, блокировка уведомлений

 

Получение одноразовых кодов подтверждения (OTP) для доступа к банковским счетам, подписка на платные услуги

Сбор журнала вызовов (не во всех эта реализация присутствует)

 

Копирование истории вызовов

 

Подготовка целевых фишинговых атак на контакты, в том числе с помощью отправки SMS с телефона жертвы

 

Выполнение USSD‑команд

(не во всех эта реализация присутствует)

Автоматический запуск сервисных запросов оператора

 

Проверка баланса, подключение платных опций, перевод средств через мобильный счёт без подтверждения пользователем или настройка переадресации вызовов

 

Как SMS-RAT попадает на устройство?

Часто пользователи могут увидеть APK в домовых чатах, в комментариях под постами (к примеру, к сервисам для обхода блокировок, а также к мобильным играм наподобие Standoff 2).

Рисунок 2. Примеры названий распространяемых SMS-RAT
Рисунок 2. Примеры названий распространяемых SMS-RAT

Часто веерная рассылка может осуществляться по контактам, в случае если аккаунт в мессенджере был скомпрометирован.

Рисунок 3. Пример рассылки в MAX с использованием бота в Telegram
Рисунок 3. Пример рассылки в MAX с использованием бота в Telegram
Рисунок 4. Пример, как выглядит канал в Telegram, где распространяется APK
Рисунок 4. Пример, как выглядит канал в Telegram, где распространяется APK

В отдельных кампаниях злоумышленники настраивают таргетированную рекламу на фишинговые лендинги, имитирующие официальные страницы сервисов. Переходя по рекламе в поиске, пользователь попадает на сайт с призывом скачать «необходимое» приложение.

Что происходит после установки SMS‑RAT?

После установки и запуска ВПО пользователь не наблюдает явных признаков компрометации.

Для маскировки вредоносной активности злоумышленники не усложняют реализацию и интегрируют стандартный компонент WebView, встраиваемый в SMS‑RAT. Фактически приложение функционирует как браузерная оболочка с жёстко зашитым URL. Ключевой принцип здесь — контекстное соответствие: содержимое страницы подбирается в зависимости от того, под каким предлогом приложение было установлено. Это позволяет легитимизировать присутствие APK на устройстве в глазах пользователя.

  • Сценарий «Новость/Сенсация»: Если приложение распространялось под видом «видео с места ДТП», внутри WebView открывается ссылка на новостную статью или видеохостинг с соответствующим контентом. Пользователь видит то, что ожидал.

  • Сценарий «Утилита/Загрузка»: В более универсальных случаях используется имитация бесконечного процесса загрузки. Например, часто (ссылка) встречается адрес photricity[.]com/flw/ajax/, который отображает бесконечную загрузку.

Домен photricity[.]com принадлежит легитимной студии веб‑дизайна (США). Однако путь /flw/ajax/ (также /flw/ni/) ведёт на страницу с PRANK‑контентом — «No Internet Prank ∞ Forever Loading Website» photricity.com. Это легальный веб‑ресурс, созданный для розыгрышей, который эмулирует зависшую загрузку данных.

После установки и первого запуска SMS‑RAT инициирует связь с командным сервером (или альтернативным каналом — например, Telegram‑ботом) и передаёт первичный пакет телеметрии: список установленных приложений, информацию о SIM‑картах, архив SMS‑сообщений и журнал вызовов. Дополнительно может отправляться содержимое буфера обмена, которое часто содержит скопированные пароли, адреса кошельков или другую чувствительную информацию.

После регистрации на командном сервере устройство переходит в режим постоянного мониторинга: поступления новых SMS и звонков, перехватывая их в реальном времени. Злоумышленник получает возможность не только пассивно собирать данные, но и активно управлять устройством — отправлять SMS от имени жертвы (в том числе на короткие номера), выполнять USSD‑команды для проверки баланса.

Рисунок 5. Пример информации, получаемой с заражённого устройства при инициализации SMS-RAT
Рисунок 5. Пример информации, получаемой с заражённого устройства при инициализации SMS-RAT

Mamont as a Service

Количество вариаций SMS-RAT на чёрном рынке растёт семимильными шагами. Но в большинстве случаев они отличаются только UI (или панелями управлений, которых в результате мониторинга было обнаружено больше 10), а также методами обфускации исходного кода или методом доставки полученной информации до злоумышленника.

Рисунок 6. Пример веб-панели SMS-RAT
Рисунок 6. Пример веб-панели SMS-RAT
Рисунок 7. Пример веб-панели SMS-RAT
Рисунок 7. Пример веб-панели SMS-RAT
Рисунок 8. Пример веб-панели SMS-RAT
Рисунок 8. Пример веб-панели SMS-RAT

Почему так много различных реализаций?

Многообразие реализаций SMS-RAT на теневом рынке обусловлено в первую очередь спецификой монетизации и распределением ролей злоумышленников. На основе анализа инфраструктуры и коммуникаций злоумышленников мы выделили следующую модель, используя терминологию, принятую в самой среде:

Рисунок 9. Схема взаимодействия злоумышленников при распространении Android RAT
Рисунок 9. Схема взаимодействия злоумышленников при распространении Android RAT

Crypter распространяет свой сервис по подписке, и в его зону ответственности входит обновление механизмов обфускации и антидетекта RAT.

Рисунок 10. Сообщения из Telegram-каналов, предоставляющих услуги по обфускации Android RAT
Рисунок 10. Сообщения из Telegram-каналов, предоставляющих услуги по обфускации Android RAT

Coder поддерживает инфраструктуру и пишет базовый функционал RAT (обычно меняется лишь транспорт до контрольного сервера), также предоставляет веб‑панель или Telegram‑бота. Доступ к ресурсам продаётся по подписке, а также за процент, полученный с выгоды. Примеры того, как выглядят веб‑панели, можно посмотреть выше.

Рисунок 11. Сообщения из Telegram-каналов, предоставляющих услуги RAT по подписке
Рисунок 11. Сообщения из Telegram-каналов, предоставляющих услуги RAT по подписке

Team — это набор Worker’ов (о них ниже). Напрямую доступ к панели не предоставляется, в связи с этим Worker должен вступить в какую‑то команду, которая непосредственно купила подписку на SMS‑RAT. Владельцы команд берут процент с полученной выгоды.

Рисунок 12. Реклама по набору в команду для распространения RAT
Рисунок 12. Реклама по набору в команду для распространения RAT

Worker — конечное звено, который непосредственно распространяет APK.

Worker’ы подразделяются на два вида — Вбивер и Траффер. Траффер с помощью веб‑панели или Telegram‑бота осуществляет настройку ВПО под свою фишинговую компанию (добавляет название, меняет картинку, вставляет URL) и распространяет через имеющиеся каналы. Вбивер агрегирует и реализует информацию, полученную из логов, с целью дальнейшей монетизации.

Рисунок 13. Процесс создания APK в Telegram-боте
Рисунок 13. Процесс создания APK в Telegram-боте

Препятствия на пути исследования SMS‑RAT: статический разбор APK

Статический анализ Android SMS-RAT обычно начинается с извлечения содержимого APK: AndroidManifest, ресурсов и classes.dex, — с последующей декомпиляцией (например, с помощью JADX (ссылка)).

В исследованных образцах встречаются как примитивные реализации, где важные данные (адреса C2, токены, ключи) хранятся в открытом виде — в ресурсах или непосредственно в коде (константы в classes.dex), — так и более продвинутые варианты. В последних наблюдается переход к обфускации, сокрытию строк и техникам, намеренно усложняющим анализ APK.

Как отмечалось ранее, реализации SMS‑RAT могут существенно различаться в зависимости от подходов разработчиков и применяемых методов сокрытия. Чтобы избежать прямой привязки к конкретным авторам и упростить ход повествования, далее условно разделим рассмотренные образцы на два класса: SMS‑RAT-CLASS‑1 и SMS‑RAT-CLASS‑2.

В данной главе акцент сделан на практическом исследовании — от корректной обработки APK до анализа кода и извлечения параметров C2.

ZIP poisoning в APK

Поскольку APK формально представляет собой ZIP‑архив, на первом этапе чаще всего достаточно воспользоваться apktool или одним из стандартных ПО для распаковки архивов. Однако на практике этот процесс может быть намеренно усложнён. В рассматриваемых образцах ВПО SMS‑RAT содержатся намеренно модифицированные ZIP‑заголовки (например, установка флага шифрования, что не связано с реальным шифрованием, а используется как обфускация).

Такие модификации приводят к некорректной работе стандартных инструментов, которые в первую очередь ориентируются на метаданные архива и ожидают согласованную структуру. В то же время Android в ряде случаев продолжает корректно работать с такими APK. Это связано с тем, что система не выполняет «полную распаковку» архива, а обращается к отдельным записям по мере необходимости, используя собственную реализацию парсинга (libziparchive). В результате один и тот же APK может корректно устанавливаться и запускаться на устройстве, но при этом в рамках статического анализа извлечение или декомпиляция могут производиться с ошибками.

Иногда такие APK удаётся открыть напрямую с использованием JADX, однако это не гарантирует корректного извлечения всех артефактов (например, часть файлов может быть повреждена или пропущена). Поэтому для анализа необходимо учитывать особенности структуры архива и применять более контролируемые методы извлечения.

Простой ZIP poisoning

В большинстве случаев ZIP poisoning реализуется через установку бита шифрования (0x0001) в поле general purpose flag для отдельных файлов (например, AndroidManifest.xml, classes.dex).

Так, в SMS‑RAT-CLASS‑1 в ZIP‑архиве существует намеренная модификация флага шифрования. Согласно спецификации (ссылка) формата ZIP, метаинформация о каждом файле присутствует в двух структурах:

  • Local File Header (PK 03 04)

    • 50 4B 03 04 — сигнатура (PK\x03\x04)

    • 14 00 — version needed

    • 03 00 — general purpose flag

Рисунок 14. Структура Local File Header в ZIP
Рисунок 14. Структура Local File Header в ZIP
  • Central Directory Header (PK 01 02)

    • 50 4B 01 02 — сигнатура (PK\x01\x02)

    • 1E 03 — version made by

    • 14 00 — version needed

    • 03 00 — general purpose flag

Рисунок 15. Структура Central Directory Header в ZIP
Рисунок 15. Структура Central Directory Header в ZIP

В рассматриваемом случае значение поля general purpose flag равно 0x0003, что является битовой маской и включает, в частности, бит шифрования (0x0001), а также 0x0002 (deflate flag).

Именно наличие этого бита шифрования приводит к тому, что инструменты анализа интерпретируют запись как зашифрованную.

Пример сброса бита шифрования в ZIP для всех файлов в архиве
import sys

data = bytearray(open(sys.argv[1], "rb").read())

i = 0
while i < len(data) - 10:
    if data[i:i+4] == b'PK\x03\x04':
        flag = int.from_bytes(data[i+6:i+8], "little")
        flag &= ~0x0001
        data[i+6:i+8] = flag.to_bytes(2, "little")

    if data[i:i+4] == b'PK\x01\x02':
        flag = int.from_bytes(data[i+8:i+10], "little")
        flag &= ~0x0001
        data[i+8:i+10] = flag.to_bytes(2, "little")

    i += 1

open("fixed.apk", "wb").write(data)

Для обхода достаточно сбросить только бит 0x0001 в заголовках ZIP (как в local header, так и в central directory), не затрагивая остальные флаги: после этого архив корректно обрабатывается стандартными инструментами и его можно распаковать обычным способом, например:

Команды для распаковки APK

unzip fixed.apk (bash)
apktool d -s fixed.apk -o out

Таким образом, в базовом варианте ZIP poisoning задача сводится к исправлению метаданных архива, после чего дальнейший анализ выполняется стандартными средствами.

Комплексный ZIP poisoning

В более сложных вариантах ZIP poisoning злоумышленники не ограничиваются установкой бита шифрования (0x0001). Архив намеренно формируется таким образом, чтобы разные инструменты интерпретировали его по‑разному. Для этого используются нестандартные имена и пути файлов, добавляются дублирующие записи одного и того же файла (например, classes.dex и /classes.dex), модифицируется поле general purpose flag, а также допускаются различия между данными в local header и central directory.

Такие конструкции приводят к так называемым parser differentials. В результате набор извлечённых файлов становится недетерминированным: часть данных теряется, часть перезаписывается, либо архив не извлекается вовсе. Более подробно с этим можно ознакомиться в «My ZIP isn’t your ZIP: Identifying and Exploiting Semantic Gaps Between ZIP Parsers» (ссылка).

В SMS‑RAT-CLASS‑2 в исследуемых образцах архив содержит большое количество записей с аномальными именами, например:

Рисунок 16. Фрагмент списка файлов архива SMS‑RAT-CLASS‑2
Рисунок 16. Фрагмент списка файлов архива SMS‑RAT-CLASS‑2

Подобные записи формально допустимы в ZIP, однако их интерпретация зависит от конкретной реализации парсера. При распаковке «в директорию» это может приводить к:

  • перезаписи файлов с одинаковыми именами;

  • извлечению некорректных данных вместо реальных артефактов.

Дополнительно к описанному выше также используется модификация поля general purpose flag. В отличие от базового случая, здесь встречаются другие значения:

  • Local File Header:

Рисунок 17. Структура Local File Header в ZIP
Рисунок 17. Структура Local File Header в ZIP
  • Central Directory Header:

Рисунок 18. Структура Central Directory Header в ZIP
Рисунок 18. Структура Central Directory Header в ZIP

В данном примере general purpose flag 0x0809 = 0x0001 (encrypted) + 0x0008 (data descriptor) + 0x0800 (UTF-8).

Практический подход к анализу APK с ZIP poisoning

С учётом описанных особенностей структуры архива анализ таких APK целесообразно выполнять в два этапа:

  1. 1. На первом этапе необходимо устранить искусственное ограничение доступа к данным — корректно снять бит шифрования (0x0001) в заголовках ZIP.

  2. На втором этапе не следует распаковывать APK целиком. Более надёжный подход заключается в адресном извлечении ключевых артефактов (AndroidManifest.xml, classes.dex, файлы из assets) и их последующем анализе по отдельности.

Например, извлечение DEX может быть выполнено напрямую:

unzip -p fixed.apk classes.dex > classes.dex 

Такой подход позволяет избежать подмены и перезаписи файлов при распаковке.

В ряде случаев можно обойтись без корректировки ZIP‑структуры, используя сигнатурный анализ. Например, ПО binwalk (ссылка) позволяют обнаружить и извлечь вложенные DEX и другие бинарные артефакты непосредственно из APK, игнорируя некорректные заголовки:

binwalk -Me sample.apk

Однако в сложных вариантах ZIP poisoning этого также может быть недостаточно. При наличии нестандартных и конфликтующих путей (например, \\, /., дублирующихся имён и псевдорасширений) извлечённая структура может оказаться повреждённой: файлы получают некорректные имена, смешиваются между собой или раскладываются по вложенным каталогам с артефактами. На практике это выглядит как появление большого количества мусорных файлов и директорий (например, classes.dex/, \\\\.xml, .9.png и т.д.), что затрудняет автоматический анализ и делает результат извлечения непригодным без дополнительной обработки.

Аналогично часть данных можно получить без полной распаковки архива. Например, AndroidManifest.xml и ресурсы могут быть извлечены с помощью aapt2 (ссылка), который использует нативную реализацию разбора APK:

 aapt2 dump xmltree --file AndroidManifest.xml sample.apk

Это особенно полезно в случаях, когда стандартные инструменты не могут корректно обработать архив.

Динамическая загрузка DEX

После устранения проблем с распаковкой APK переходим к следующему этапу — анализу его содержимого. На этом этапе основные трудности связаны уже не со структурой архива, а с обфускацией кода и сокрытием ключевых артефактов (payload, строки, C2).

Рассмотрим это на примере SMS‑RAT-CLASS‑1.

После извлечения содержимого APK переходим к анализу AndroidManifest.xml, так как он задаёт точку входа приложения и часто содержит косвенные указания на дальнейшую логику.

AndroidManifest.xml
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    android:compileSdkVersion="34"
    android:compileSdkVersionCodename="14"
    package="com.xiaomdi.kjdgzsc"
    platformBuildVersionCode="34"
    platformBuildVersionName="14">

    <uses-feature android:name="android.hardware.telephony" android:required="false"/>

    <!-- Базовые разрешения -->
    <uses-permission android:name="android.permission.INTERNET"/>
    <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
    <uses-permission android:name="android.permission.WAKE_LOCK"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
    <uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
    <uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"/>

    <!-- SMS разрешения -->
    <uses-permission android:name="android.permission.RECEIVE_SMS"/>
    <uses-permission android:name="android.permission.READ_SMS"/>
    <uses-permission android:name="android.permission.SEND_SMS"/>

    <!-- Телефония -->
    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>
    <uses-permission android:name="android.permission.READ_PHONE_NUMBERS"/>

    <permission android:name="com.xiaomdi.kjdgzsc.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
        android:protectionLevel="signature"/>
    <uses-permission android:name="com.xiaomdi.kjdgzsc.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"/>

    <application
        android:allowBackup="false"
        android:appComponentFactory="androidx.core.app.CoreComponentFactory"
        android:extractNativeLibs="false"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/str_ttru21"
        android:name="Ta1d2758YioY.Td300f62YioY.T38d8813YioY.T829464eYioY"
        android:supportsRtl="true"
        android:theme="@style/Theme.Defender"
        android:usesCleartextTraffic="true">

        <activity
            android:name="com.xiaomdi.kjdgzsc.MainActivity"
            android:excludeFromRecents="true"
            android:exported="true"
            android:launchMode="singleTask"
            android:theme="@style/Theme.Defender">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.INFO"/>
            </intent-filter>
        </activity>

        <activity
            android:name="com.xiaomdi.kjdgzsc.SmsActivity"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.SEND"/>
                <action android:name="android.intent.action.SENDTO"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <category android:name="android.intent.category.BROWSABLE"/>
                <data android:scheme="sms"/>
                <data android:scheme="smsto"/>
                <data android:scheme="mms"/>
                <data android:scheme="mmsto"/>
            </intent-filter>
        </activity>

        <service
            android:name="com.xiaomdi.kjdgzsc.BootstrapService"
            android:exported="false"/>

        <service
            android:name="com.xiaomdi.kjdgzsc.SmsService"
            android:exported="true"
            android:permission="android.permission.SEND_RESPOND_VIA_MESSAGE">
            <intent-filter>
                <action android:name="android.intent.action.RESPOND_VIA_MESSAGE"/>
                <category android:name="android.intent.category.DEFAULT"/>
                <data android:scheme="sms"/>
                <data android:scheme="smsto"/>
                <data android:scheme="mms"/>
                <data android:scheme="mmsto"/>
            </intent-filter>
        </service>

        <service
            android:name="com.xiaomdi.kjdgzsc.CoreService"
            android:enabled="true"
            android:exported="false"
            android:foregroundServiceType="dataSync"/>

        <service
            android:name="com.xiaomdi.kjdgzsc.RegistrationService"
            android:enabled="true"
            android:exported="false"/>

        <receiver
            android:name="com.xiaomdi.kjdgzsc.PXl9RTViW8u16"
            android:exported="true"
            android:permission="android.permission.BROADCAST_SMS">
            <intent-filter android:priority="2147483647">
                <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
                <action android:name="android.provider.Telephony.SMS_DELIVER"/>
            </intent-filter>
        </receiver>

        <receiver
            android:name="com.xiaomdi.kjdgzsc.PXl9RTViW8u12"
            android:exported="true"
            android:permission="android.permission.BROADCAST_WAP_PUSH">
            <intent-filter>
                <action android:name="android.provider.Telephony.WAP_PUSH_DELIVER"/>
                <data android:mimeType="application/vnd.wap.mms-message"/>
            </intent-filter>
        </receiver>

        <receiver
            android:name="com.xiaomdi.kjdgzsc.PXl9RTViW8u1"
            android:enabled="true"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </receiver>

        <receiver
            android:name="com.xiaomdi.kjdgzsc.AlarmReceiver"
            android:enabled="true"
            android:exported="false"/>

        <provider
            android:name="androidx.startup.InitializationProvider"
            android:authorities="com.xiaomdi.kjdgzsc.androidx-startup"
            android:exported="false">
            <meta-data android:name="androidx.emoji2.text.EmojiCompatInitializer" android:value="androidx.startup"/>
            <meta-data android:name="androidx.lifecycle.ProcessLifecycleInitializer" android:value="androidx.startup"/>
        </provider>

        <activity
            android:name="com.truecaller.messaging.conversation.ConversationActivity"
            android:allowEmbedded="true"
            android:exported="false"
            android:parentActivityName="com.truecaller.ui.TruecallerInit"
            android:screenOrientation="portrait">
            <intent-filter>
                <action android:name="com.truecaller.OPEN_CONVERSATION"/>
                <category android:name="android.intent.category.DEFAULT"/>
            </intent-filter>
        </activity>

        <activity android:name="edodpwmfjeji.efxosm.dkjskemd"
            android:enabled="false"
            android:screenOrientation="portrait"/>
        <activity android:name="oqzfksq.pqlqf.mrky"
            android:enabled="false"
            android:screenOrientation="portrait"
            android:windowSoftInputMode="adjustResize"/>

        <receiver android:name="rfjdjdk.djfifjekkd.ciciidkdkf" android:enabled="false"/>
        <receiver android:name="rfjfidikdf.jfifid.fjfid.fjer"
            android:enabled="false"
            android:exported="false">
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED"/>
                <action android:name="android.intent.action.QUICKBOOT_POWERON"/>
                <action android:name="com.htc.intent.action.QUICKBOOT_POWERON"/>
            </intent-filter>
        </receiver>
        <receiver android:name="ofpfpdlekdjxj.wjdjd.ejdjf.ididr" android:enabled="false"/>
        <receiver android:name="cfidkd.djjdj.dkdkeiver"
            android:enabled="false"
            android:exported="true">
            <intent-filter>
                <action android:name="android.intent.action.PACKAGE_ADDED"/>
                <data android:scheme="package"/>
            </intent-filter>
        </receiver>
        <receiver android:name="anfjfjfidkd.fkkfkdmde.ivfjfjfer"
            android:directBootAware="false"
            android:enabled="false"
            android:exported="true"
            android:permission="android.permission.DUMP">
            <intent-filter>
                <action android:name="androidx.work.diagnostics.REQUEST_DIAGNOSTICS"/>
            </intent-filter>
        </receiver>

        <!-- Meta-data -->
        <meta-data android:name="android.app.shortcuts" android:value="Maloy"/>
        <meta-data android:name="com.kaspersky.security.KsConnectService" android:value="jFH.ffwv"/>
        <meta-data android:name="com.kaspersky.security.NewKsConnectService" android:value=".ffwv"/>

    </application>
</manifest>

В AndroidManifest.xml сразу смотрим блок <application> — здесь указана точка входа приложения.

android:name="Ta1d2758YioY.Td300f62YioY.T38d8813YioY.T829464eYioY"

Дополнительно обращаем внимание на <meta‑data>:

<meta-data android:name="com.kaspersky.security.KsConnectService" android:value="jFH.ffwv"/>
<meta-data android:name="com.kaspersky.security.NewKsConnectService" android:value=".ffwv"/>

Значения из <meta‑data> доступны приложению через ApplicationInfo.metaData.

Анализ classes.dex

Переходим к анализу кода приложения. Хотя в общем случае приложение может содержать несколько DEX-файлов (multidex), в данном примере присутствует только один — classes.dex.

jadx -d jadx_out classes.dex

Для этого декомпилируем classes.dex с помощью JADX, чтобы проверить, используется ли информация из блока <meta‑data>.

Рисунок 19. Фрагмент структуры файлов декомпилированного DEX
Рисунок 19. Фрагмент структуры файлов декомпилированного DEX

При открытии класса T829464eYioY, указанного в AndroidManifest.xml, видно, что код обфусцирован. В таких случаях анализ целесообразно строить не от структуры классов, а от характерных API‑вызовов, связанных с взаимодействием с <meta‑data>, а также работой с файлами в assets.

В частности, обращения к getApplicationInfo(...).metaData указывают на использование параметров из манифеста, а вызовы getAssets().open(...) — на загрузку встроенных файлов (и в том числе — потенциального payload). Отслеживание этих вызовов и их аргументов позволяет проследить цепочку: получение имени ресурса → чтение файла → его последующая загрузка и выполнение. В упрощённом виде эта последовательность выглядит следующим образом:

  • Чтение <meta‑data> из AndroidManifest.xml:

\\ на участке кода значение переменной Tc910371YioY.O6d606848jpAw фиксировано и 
\\ равно 322, в результате 322 ^ 450 = 128, что соответствует константе 
\\ PackageManager.GET_META_DATA

bundle = context.getPackageManager()
    .getApplicationInfo(context.getPackageName(), Tc910371YioY.O6d606848jpAw ^ 450)
    .metaData;

 2.     Получение значения по ключу (после декодирования строки):

Рисунок 20. Фрагмент получения значения из переменной "com.kaspersky.security.KsConnectService" в блоке <meta‑data>
Рисунок 20. Фрагмент получения значения из переменной "com.kaspersky.security.KsConnectService" в блоке <meta‑data>
Od5d81f30jpAw = bundle.getString("com.kaspersky.security.KsConnectService");

 3. Чтение файла из assets:

inputStreamOpen = context.getAssets().open(Od5d81f30jpAw);

 4. Запись во временный файл в code_cache и подготовка к загрузке.

5.  Динамическая загрузка через BaseDexClassLoader:

File payloadFile = new File(codeCacheDir, Od5d81f30jpAw);

String dexPath = payloadFile.getAbsolutePath();

baseDexClassLoader = new BaseDexClassLoader(dexPath, codeCacheDir, null, classLoader);

Примечание:

При копировании в code_cache содержимое файла jFH.ffwv обрабатывается в памяти (служебные байты/префиксы удаляются), и уже результат этой обработки записывается во временный файл. После всех преобразований валидный DEX передаётся в BaseDexClassLoader.

Как видно из псевдокода выше, classes.dex выполняет роль загрузчика: читает имя payload из <meta‑data>, открывает файл из assets, копирует его в code_cache и подключает через BaseDexClassLoader (механизм динамической загрузки DEX в Android).

Деобфускация на примере получения строки из Manifest

В качестве примера разберём, каким именно образом получилась строка "com.kaspersky.KsConnectService".

Как было показано ранее, для доступа к данным в assets используется переменная Od5d81f30jpAw. Анализ кода показывает, что она присваивается в единственном месте, что делает её удобной точкой для дальнейшего разбора (см. Рисунок 20). В нашем случае эта переменная устанавливается только в единственном месте.

Дополнительно видно, что в вычислении параметров участвует переменная Tc910371YioY.O6d606848jpAw, значение которой на данном участке кода фиксировано (раннее описано, что значение = 322).

Рисунок 21. Функция расшифрования строк
Рисунок 21. Функция расшифрования строк

Функция U218e9580bBfl выполняет побайтовое смещение строки из массива с использованием XOR, то есть строка изначально зашита в коде в обфусцированном виде. В качестве источника данных используется статический массив (в данном случае это массив short[] Oaed46797jpAw).

Рисунок 22. Фрагмент кода с массивом, используемым для расшифрования
Рисунок 22. Фрагмент кода с массивом, используемым для расшифрования

Функция берёт из него подмассив (по смещению и длине) и восстанавливает строку с использованием заданного ключа.

То есть параметры в нашем случае такие:

  • sArr = Oaed46797jpAw

  • i = 21 (смещение / start)

  • i2 = 39 (длина строки) 322 ^ 357 = 39

  • i3 = 2011 (XOR-ключ)

 В результате выполнения получается строка "com.kaspersky.security.KsConnectService", что является названием переменной в <meta-data>. Значение данной переменной равно jFH.ffwv (см. AndroidManifest.xml).

Анализ jFH.ffwv

По результатам анализа мы видим, что в нашем classes.dex подгружается файл из assets с названием jFH.ffwv.

Рисунок 23. Структура директории assets
Рисунок 23. Структура директории assets

При просмотре содержимого файла видно, что после первых двух байт следует сигнатура DEX (64 65 78 0a, строка "dex\n035"), что указывает на наличие DEX-файла со смещением:

Рисунок 24. Фрагмент заголовка файла jFH.ffwv
Рисунок 24. Фрагмент заголовка файла jFH.ffwv

Поскольку сигнатура DEX находится со смещением, для корректного анализа необходимо удалить начальный префикс и привести файл к валидному виду. Это можно сделать, отбросив первые 2 байта (789C):

$ dd if=assets/jFH.ffwv of=payload_classes.dex bs=1 skip=2

После этого получается DEX-файл, который можно декомпилировать с помощью JADX.
Результат декомпиляции показывает, что несмотря на обфускацию имён классов и пакетов, ключевые строки (URL, endpoint’ы, параметры запросов) не скрыты и доступны в явном виде, что существенно упрощает анализ. В частности, адреса C2 и логика взаимодействия с ним читаются напрямую из кода без необходимости дополнительного исследования.

Рисунок 25. Фрагмент декомпилированного кода jFH.ffwv
Рисунок 25. Фрагмент декомпилированного кода jFH.ffwv

На рисунке 25 видно, какие параметры отправляются на сервер для регистрации заражённого устройства.

Схема работы и взаимодействие с С2

SMS-RAT-CLASS-1. Схема работы и взаимодействие с C2

В упрощённом виде логика работы SMS‑RAT-CLASS‑1 сводится к следующему.

Сетевое взаимодействие реализовано через HTTP‑API с фиксированным набором endpoint’ов. На первом шаге приложение сначала обращается к BootstrapServer (серверу начальной инициализации 85.192.30[.]244:80), который возвращает список рабочих сетевых адресов C2 с приоритетами. Дальнейшее взаимодействие выполняется уже с выбранным сервером из этого списка (GET /api/v2/fenrir/ip).

Результат ответа на /api/v2/fenrir/ip:

{
  "success": true,
  "servers": [
    {
      "ip": "85.192.30[.]150",
      "port": 80,
      "priority": 1
    },
    {
      "ip": "109.120.152[.]219",
      "port": 80,
      "priority": 0
    }
  ],

  "count": 2,
  "timestamp": 1771316010848
}

Дальше происходит получение конфига (GET /api/cfg/{buildId}), регистрация устройства (POST /api/v2/register), периодическое отправление heartbeat (POST /api/v2/ping), запрашивание команды (GET /api/v2/cmd/{device_id}) и передача перехваченных SMS (POST /api/v2/sms).

Исходные данные (JSON с параметрами) не отправляются напрямую — они предварительно шифруются и помещаются в обёртку вида: {"enc":"..."}. Дополнительно такие запросы помечаются HTTP‑заголовком (X‑Fenrir‑Enc: 1).

В новых версиях SMS‑RAT-CLASS‑1 логика осталась той же, но endpoint’ы были переименованы. Вместо /api/v2/... используются пути /cdn/*, /store/*, /media/*. Также, в отличие от предыдущего примера, адрес BootstrapServer отсутствует в виде жёстко заданной строки и формируется с помощью функции getBootstrapUrl() (см. ниже).

Функция формирования IP-адреса BootstrapServer:

public String getBootstrapUrl() {

        return u7.p() + u7.c() + ":80";
    }

В фрагменте кода, представленном ниже, реализован механизм деобфускации строковых констант, предназначенный для скрытия сетевой инфраструктуры командных серверов (C2). В результате последовательного выполнения функций u7.p() и u7.c() из зашифрованных массивов данных формируется итоговый адрес сервера: 176.124.222[.]81:80. 

Декомпилированный класс с алгоритмом расшифровки строк для получения адреса Bootstrap‑сервера
package a;

import androidx.core.location.LocationRequestCompat;
import okhttp3.HttpUrl;

public final class u7 {
    private static final int aa = 23;
    private static final int ac = 40;
    private static final int ag = 74;
    private static final int ak = 108;
    private static final int e = 75;
    private static final int g = 92;
    private static final int i = 109;
    private static final int k = 126;
    private static final int o = 161;
    private static final int q = 178;

    /* renamed from: a, reason: collision with root package name */
    private static final String[] f55a = new String[19];
    private static final int[] b = {110, 61, 47, 7, 7, 231, 160, 245, 199, 192, 166, 190, 169, 133, 133, 125};
    private static final int y = 246;
    private static final int m = 143;
    private static final int[] d = {125, 44, 24, 22, 244, y, m, 194, 200, 163, 177, 147, 193};
    private static final int w = 229;
    private static final int[] f = {76, 3, 9, w, w, 193, 158, 221, 163, 189, 134, 153, 144, 121, 109};
    private static final int ai = 91;
    private static final int[] h = {ai, 242, 250, 244, 218, 208, 237, 189, 185, m, 131, 109, 116};
    private static final int[] j = {170, 255, 250, 200, 208, 167, 252, 149, 157, 150, LocationRequestCompat.QUALITY_LOW_POWER, 117, 69};
    private static final int am = 119;
    private static final int[] l = {185, 206, 213, 217, 163, 182, 203, 131, 155, 123, am, 87, 70};
    private static final int[] n = {135, 216, 167, 171, 181, 136, 217, 115, 98, 114, 73, 82, 55, 34};
    private static final int[] p = {150, 165, 183, 142, 194, 155, 116, 103, 68, 90, 20};
    private static final int[] r = {w, 180, 128, 159, 209, 101, am, 65, 87, 76};
    private static final int s = 195;
    private static final int[] t = {234, 223, s, 44, 62, 46, 29, 24, 113, 98, 111, 68, 79, 181};
    private static final int u = 212;
    private static final int[] v = {180, u, 64, 118, 78, 95, 83, 53, 121, 36, 0, 24};
    private static final int[] x = {165, 39, 86, 116, 120, 19, 0, 61, 28};
    private static final int c = 58;
    private static final int[] z = {122, 78, 78, 44, 49, c, 65, 16, 226};
    private static final int[] ab = {108, 83, 39, 34, 6, 30, 9, 167, s, 221, 193, 219};
    private static final int[] ad = {33, 61, 42, 11, 29, 226, 239, 239, 193, 218, 172, 224, 182, 154, 153, 109, 43, 61, 73, 95, 37, 35, 45, 14, 12, 184, 231, 235, 202, 148, 254};
    private static final int ae = 57;
    private static final int[] af = {23, 16, ae, 73};
    private static final int[] ah = {10, 27, 8, 249, 172, 140, 159};
    private static final int[] aj = {66};
    private static final int[] al = {31, 251, 232, 201, 219, 220, 173, 173, m, 156, 110, 34, 112, 84, ai, 47};

    private static String a(int[] iArr, int i2) {
        int length = iArr.length;
        char[] cArr = new char[length];
        int i3 = 0;
        while (true) {
            int i4 = 29364;
            while (true) {
                int i5 = (i4 ^ 29364) % 5;
                if (i5 == 0) {
                    cArr[i3] = (char) (iArr[i3] ^ ((((i3 * 13) + i2) + 7) & 255));
                } else if (i5 == 1) {
                    i3++;
                    if (i3 < length) {
                        break;
                    }
                    i4 = 29366;
                } else {
                    if (i5 == 2) {
                        return new String(cArr);
                    }
                    if (i5 != 3) {
                        if (i5 != 4) {
                        }
                    }
                }
                i4 = 29365;
            }
        }
    }

    private static String b(int i2) {
        String strA;
        String[] strArr = f55a;
        String str = strArr[i2];
        if (str != null) {
            return str;
        }
        switch (((i2 * 10003) + 20113) % 41) {
            case 5:
                strA = a(al, am);
                break;
            case 6:
                strA = a(aj, 108);
                break;
            case 7:
                strA = a(ah, ai);
                break;
            case 8:
                strA = a(af, ag);
                break;
            case 9:
                strA = a(ad, ae);
                break;
            case 10:
                strA = a(ab, 40);
                break;
            case 11:
                strA = a(z, 23);
                break;
            case 12:
                strA = a(x, y);
                break;
            case 13:
                strA = a(v, w);
                break;
            case 14:
                strA = a(t, u);
                break;
            case 15:
                strA = a(r, s);
                break;
            case 16:
                strA = a(p, q);
                break;
            case 17:
                strA = a(n, o);
                break;
            case 18:
                strA = a(l, m);
                break;
            case 19:
                strA = a(j, 126);
                break;
            case 20:
                strA = a(h, 109);
                break;
            case 21:
                strA = a(f, g);
                break;
            case 22:
                strA = a(d, e);
                break;
            case 23:
                strA = a(b, c);
                break;
            default:
                strA = HttpUrl.FRAGMENT_ENCODE_SET;
                break;
        }
        strArr[i2] = strA;
        return strA;
    }

    public static String c() {
        return b(9);
    }

    public static String d() {
        return b(7);
    }

    public static String e() {
        return b(1);
    }

    public static String f() {
        return b(17);
    }

    public static String g() {
        return b(11);
    }

    public static String h() {
        return b(13);
    }

    public static String i() {
        return b(12);
    }

    public static String j() {
        return b(10);
    }

    public static String k() {
        return b(18);
    }

    public static String l() {
        return b(14);
    }

    public static String m() {
        return b(15);
    }

    public static String n() {
        return b(8);
    }

    public static String o() {
        return b(0);
    }

    public static String p() {
        return b(16);
    }

    public static String q() {
        return b(2);
    }

    public static String r() {
        return b(3);
    }

    public static String s() {
        return b(4);
    }

    public static String t() {
        return b(5);
    }

    public static String u() {
        return b(6);
    }
}

В данном блоке кода реализована обфускация строк для сокрытия сетевой инфраструктуры. При вызове методов класса происходит посимвольная расшифровка данных из массивов с помощью функции a(). Функции u7.p() и u7.c() восстанавливают IP‑адрес из массива и получается 176.124.222.81.

Новые endpoint’ы:

  • GET /cdn/nodes — bootstrap (список серверов)

Рисунок 26. Получение списка C2‑адресов, на которые отправляется информация с заражённого устройства
Рисунок 26. Получение списка C2‑адресов, на которые отправляется информация с заражённого устройства
  • GET /cdn/asset/{buildId} — получение конфигурационного файла

  • POST /store/checkout — регистрация устройства на C2

  • POST /store/inventory — ping, проверка, онлайн ли устройство

  • GET /store/order/{device_id} — команды

  • POST /media/upload — отправка SMS

SMS-RAT-CLASS-2. Схема работы и взаимодействие с C2

При исследовании образцов, относящихся к SMS‑RAT-CLASS‑2, основные сложности были сосредоточены на этапе распаковки. После успешной распаковки дальнейший анализ выполнялся существенно проще: как и в первом случае, по декомпилированному коду удалось восстановить сетевую логику, набор endpoint’ов и общую схему взаимодействия с C2.

Рисунок 27. Фрагмент класса с константными API для взаимодействия заражённого устройства c C2
Рисунок 27. Фрагмент класса с константными API для взаимодействия заражённого устройства c C2

В отличие от SMS‑RAT-CLASS‑1, здесь отсутствует этап с получением списков IP‑адресов, а управление и часть взаимодействия с командным сервером перенесены на Firebase Cloud Messaging (FCM, ссылка). Сетевое взаимодействие строится вокруг набора POST‑запросов, а базовый C2‑адрес зашит в клиенте напрямую (45.131.214[.]10).

Краткая схема взаимодействия с C2:

  • POST /gettingData/gettingData — получение конфигурации (BuildInfo) по данным клиента (clientId, buildKeyValue, buildVersion)

  • POST /gettingData/event — регистрация/старт клиента (ClientStart)

  • POST /online/device_monitor/devices/ping — периодическая проверка, онлайн ли устройство

  • Команды для выполнения на устройстве (отправка SMS, получение номера и т.д.) поступают преимущественно через FCM (FIREBASE_COMMAND). Результат выполнения команд отправляется уже на C2 по HTTP

  • FCM-команды (online, ping, sms_send, send_all) обрабатываются в основном классе ChimeraWorker

Рисунок 28. Фрагмент кода с обработкой команд, получаемых через FCM
Рисунок 28. Фрагмент кода с обработкой команд, получаемых через FCM
  • POST /panel/ping/info — ответ на команду ping (статус устройства)

  • POST /panel/sms_send/info — статус/результат отправки SMS по команде

  • POST /panel/send_all/info — итог массовой рассылки (send_all)

  • POST /sms/event — эксфильтрация перехваченных SMS

  • POST /archive/event — эксфильтрация SMS‑архива

  • POST /push_and_failedSms/event — push‑уведомления, неуспешные SMS

  • POST /error/error_handle — обработка ошибок

В некоторых версиях SMS‑RAT-CLASS‑2 разработчики также решили отойти от хранения информации о сетевой инфраструктуре в открытом виде. Для получения адреса C2 использовался отличный от SMS‑RAT-CLASS‑2 подход — конфигурация не хранилась непосредственно в APK, а загружалась c GitHub.

На первом этапе приложение выполняет запрос к GitHub для получения конфигурации: https://github.com/acabthvichm-boop/remote-host-configuration/blob/main/config.json

Рисунок 29. Репозиторий с конфигурационным файлом командного сервера
Рисунок 29. Репозиторий с конфигурационным файлом командного сервера

В декомпилированном коде после получения конфигурационного файла с GitHub для извлечения адреса командного сервера помимо base64 используется также расшифровка с использованием AES‑CTR. При этом ключ и вектор инициализации в APK фиксированы.

Схема выглядит следующим образом:

  • Загрузка JSON с GitHub.

Рисунок 30. Фрагмент кода с получением конфигурационного файла с GitHub
Рисунок 30. Фрагмент кода с получением конфигурационного файла с GitHub

2. Парсинг полученного JSON.

Рисунок 31. Фрагмент кода с обработкой полученного конфигурационного файла
Рисунок 31. Фрагмент кода с обработкой полученного конфигурационного файла

3. base64-декодирование значений и расшифрование (AES).

Рисунок 32. Фрагмент кода. Расшифрование адреса C2, полученного из конфигурационного файла
Рисунок 32. Фрагмент кода. Расшифрование адреса C2, полученного из конфигурационного файла

4. Формирование итогового C2‑адреса (178.17.62[.]15:80).

Для автоматизации анализа и извлечения IOC был реализован вспомогательный скрипт:

python3 decode_c2_config.py https://raw.githubusercontent.com/acabthvichm-boop/remote-host-configuration/refs/heads/main/config.json -o decoded_config.json
Получение расшифрованного адреса C2
#!/usr/bin/env python3

import argparse
import base64
import json
import sys
import urllib.request

from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes

def decode_and_save(url: str, out_path: str = "decoded_config.json") -> dict:
    with urllib.request.urlopen(url, timeout=25) as resp:
        raw = json.loads(resp.read().decode("utf-8"))

    key = b"6a209b01592808f7c582c49fa82cbd7b"
    iv = b"6a209b01592808f7"

    def dec(v: str) -> str:
        data = base64.b64decode(v)
        c = Cipher(algorithms.AES(key), modes.CTR(iv)).decryptor()
        return (c.update(data) + c.finalize()).decode("utf-8").strip()

    domain = dec(raw["domain"])
    port = dec(raw["port"])

    result = {
        "source_url": url,
        "raw": {"domain": raw["domain"], "port": raw["port"]},
        "decoded": {"domain": domain, "port": port},
        "c2_url": f"http://{domain}:{port}/%s",
    }

    with open(out_path, "w", encoding="utf-8") as f:
        json.dump(result, f, ensure_ascii=False, indent=2)
        f.write("\n")

    return result

if __name__ == "__main__":
    p = argparse.ArgumentParser()
    p.add_argument("url")
    p.add_argument("-o", "--out", default="decoded_config.json")
    a = p.parse_args()
    try:
        r = decode_and_save(a.url, a.out)
    except Exception as e:
        print(f"[!] {e}", file=sys.stderr)
        raise SystemExit(1)
    print(f"C2 data: {r['c2_url']}")
    print(f"Saved res: {a.out}")

Результаты анализа

На практике большинство SMS‑RAT кластеризуется в некоторое количество классов (мы насчитываем 4), часть из них были описаны как CLASS‑1 и CLASS‑2.

Существуют версии SMS‑RAT, в которых, помимо раннее описанного функционала, встречается удалённое управление устройством (условный «VNC»). В большинстве случаев для такого канала используют WebSocket, потому что нужен постоянный двусторонний обмен. Однако в данном случае требуется включение специальных возможностей (Accessibility Service). Активация сервиса требует от жертвы прохождения многоэтапной и нетривиальной последовательности действий в системных настройках, что сопровождается критическими предупреждениями безопасности от Android. Формально такие образцы уже ближе не к «чистому» SMS‑RAT, а к полноценному Android RAT.

На самом деле кластеризовать данное семейство можно по транспорту, то есть по каким каналам заражённое устройство взаимодействует с C2:

  • Firebase Cloud Messaging. Через механизм доставки пушей

  • Взаимодействие через Rest Api

  • Telegram Api (в некоторых реализация эксфильтрация происходит сразу напрямую в мессенджер)

  • WebSocket

Рисунок 33. Каналы связи между заражённым устройством и C2
Рисунок 33. Каналы связи между заражённым устройством и C2

Примечание

Злоумышленники так же, как и обычные пользователи, испытывают проблемы в работе при активации режима «Белых списков». Поэтому здесь злоумышленники пришли к тому, что стоит добавить ещё один резервный транспорт — это SMS‑канал. На практике наблюдается следующее: на мобильный телефон дропа, который имеет постоянный доступ в интернет и в который вставлена сим‑карта, ставится приложение — «Ретранслятор». В качестве резервного канала при сборке приложения SMS‑RAT указывается номер этого ретранслятора. В условиях недоступности C2 все сообщения, полученные на устройство жертвы, пересылаются на ретранслятор, после чего данные эксфильтруются по раннее описанным каналам.

Рисунок 34. SMS-канал как транспорт
Рисунок 34. SMS-канал как транспорт

Рекомендации для пользователей

1. Одна из основных рекомендаций для конечных пользователей — скачивать ПО только с официальных источников:

2. На устройстве важно запретить установку из неизвестных источников.

3. На устройство рекомендуется установить антивирус, но важно не забывать проверять сработки, так как он лишь предупреждает, но принудительно не удаляет приложения.

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

Всегда лучше убедиться наверняка!

Комментарии (0)