В марте 2023 года исследователи из ESET обнаружили вредоносные импланты, встроенные в модификации различных мессенджеров. Некоторые из них искали в галерее пользователя картинки с фразами для восстановления доступа к криптокошелькам (recovery phrases). Для поиска использовалась OCR-модель, с помощью которой на устройстве жертвы отбирались картинки для эксфильтрации на С2. В ходе описанной вредоносной кампании, нацеленной на пользователей устройств под управлением Android и Windows, зловреды распространялись через неофициальные источники. В конце 2024 года мы обнаружили новую вредоносную кампанию, которую назвали SparkCat, где злоумышленники использовали похожие тактики, однако атаковали пользователей как Android, так и iOS, в том числе через официальные магазины. Наши выводы в двух словах:
- Мы обнаружили приложения для Android и iOS, в которые был встроен вредоносный SDK/фреймворк для кражи фраз для восстановления доступа к криптокошелькам, некоторые из них были доступны в Google Play и App Store. Из Google Play зараженные приложения скачали более 242 000 раз. Это первый известный случай попадания стилера в App Store.
- Вредоносный модуль для Android расшифровывал и запускал OCR-плагин на основе библиотеки Google ML Kit, с помощью которого распознавал текст на картинках в галерее устройства. По ключевым словам, получаемым с С2, троянец отправлял картинки на командный сервер. Вредоносный модуль для iOS был устроен схожим образом и также использовал библиотеку Google ML Kit для OCR.
- Зловред, который мы назвали SparkCat, использовал для взаимодействия с С2 неопознанный протокол, реализованный на редком для мобильных приложений языке Rust.
- Согласно временным меткам в файлах зловреда и датам создания файлов конфигураций в репозиториях на GitLab, SparkCat был активен с марта 2024 года.
Вредоносный SDK в приложениях в Google Play
Самым первым приложением, которое показалось нам подозрительным, стало приложение для доставки еды в ОАЭ и Индонезии под названием ComeCome (имя пакета — com.bintiger.mall.android), которое на момент анализа было доступно в Google Play и установлено более 10 000 раз.
В версии приложения 2.0.0 (f99252b23f42b9b054b7233930532fcd) в наследнике класса Application, который является одной из точек входа приложения, был переопределен метод onCreate. В этом методе инициализируется SDK-компонент под названием Spark. В оригинале он был обфусцирован, поэтому мы сначала статически деобфусцировали его, после чего приступили к анализу.
Spark написан на Java. При инициализации он загружает с GitLab зашифрованный файл конфигурации в формате JSON по URL, зашитому в коде зловреда. JSON декодируется с помощью base64, а затем расшифровывается с помощью AES-128 в режиме CBC.
Если SDK не смог получить конфигурацию, то будут использоваться настройки по умолчанию.
Нам удалось скачать с GitLab следующую конфигурацию:
1 2 3 4 5 |
{ "http": ["https://api.aliyung.org"], "rust": ["api.aliyung.com:18883"], "tfm": 1 } |
Поля http и rust содержат адреса командных серверов SDK, а флаг tfm регулирует выбор С2. Если tfm равен 1, то в качестве командного сервера будет использован rust, во всех остальных случаях — http.
Для общения с сервером http Spark использует POST-запросы. Данные перед отправкой шифруются с помощью AES-256 в режиме CBC, а ответ от сервера расшифровывается с использованием AES-128 в режиме CBC. В обоих случаях ключи представляют собой константы, прописанные в коде.
Передача данных на сервер rust происходит в три этапа:
- Данные шифруются при помощи AES-256 в режиме CBC тем же ключом, что и для сервера http.
- Формируется JSON следующего вида, где <PATH> — путь для загрузки данных, а <DATA> — зашифрованные данные с предыдущего этапа:
123456{"path": "upload@<PATH>","method": "POST","contentType": "application/json","data": "<DATA>"} - Этот JSON отправляется на сервер с помощью нативной библиотеки libmodsvmp.so по неопознанному протоколу поверх TCP-сокетов. Библиотека написана на языке программирования Rust и маскируется под популярный обфускатор для Android.
Статический анализ кода библиотеки оказался сложной задачей, поскольку Rust использует нестандартное соглашение о вызовах, а имена функций в файле отсутствовали. После динамического анализа с использованием Frida нам удалось восстановить схему взаимодействия. Перед отправкой данных на сервер библиотека генерирует 32-байтовый ключ для шифра AES-GCM-SIV, которым шифрует отправляемые данные, предварительно сжатые с помощью ZSTD. Значение nonce для алгоритма при этом не генерируется — оно зашито в коде и всегда равно «unique nonce» (sic).
AES-ключ шифруется при помощи RSA, публичный ключ для которого передается при вызове нативного метода из вредоносного SDK в формате PEM, а затем также отправляется на сервер. Выравнивание при шифровании AES-ключа происходит путем добавления случайных 224 байт в начало сообщения. Сервер злоумышленников, принимая запрос, расшифровывает AES-ключ приватным ключом RSA, декодирует полученные данные, после чего сжимает ответ с помощью ZSTD и шифрует его по алгоритму AES-GCM-SIV. Ответ от сервера после расшифровки в нативной библиотеке передается в SDK, где декодируется с помощью base64 и расшифровывается по такому же принципу, как и при взаимодействии с сервером http. Пример коммуникации вредоносного модуля с сервером rust приведен ниже.
После загрузки конфигурации Spark в отдельном потоке расшифровывает и запускает полезную нагрузку из ресурсов. В качестве шифра используется XOR с шестнадцатибайтовым ключом.
Полезная нагрузка (c84784a5a0ee6fedc2abe1545f933655) является оберткой для работы с интерфейсом TextRecognizer из библиотеки Google ML Kit. Она подгружает разные модели OCR в зависимости от языка системы, чтобы различать на картинках латинские, корейские, китайские и японские символы. Затем SDK загружает на командный сервер по пути /api/e/d/u информацию об устройстве, а в ответ получает объект, который регулирует дальнейшую работу зловреда. Объект представляет собой файл JSON со структурой, представленной ниже. Флаг uploadSwitch разрешает зловреду дальнейшую работу (значение 1).
1 2 3 4 5 6 7 8 9 |
{ "code": 0, "message": "success", "data": { "uploadSwitch": 1, "pw": 0, "rs": "" } } |
После этого SDK регистрирует обработчик событий жизненного цикла активностей приложения, который при обращении пользователя в чат поддержки, реализованный при помощи легитимного стороннего компонента Easemob HelpDesk SDK, запрашивает разрешения на чтение картинок из галереи. Если флаг pw в объекте выше равен 1, то в случае отказа модуль будет запрашивать разрешения снова. Аргументация, используемая SDK при запросе, на первый взгляд не вызывает вопросов: при обращении в поддержку пользователь действительно может прикреплять изображения.
Если разрешения будут предоставлены, то SDK запустит свою основную функциональность. Она начинается с запроса на С2 по пути /api/e/config/rekognition, в ответ на который будут получены параметры для обработки результатов OCR.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
{ "code": 0, "message": "success", "data": { "letterMax": 34, "letterMin": 2, "enable": 1, "wordlistMatchMin": 9, "interval": 100, "lang": 1, "wordMin": 12, "wordMax": 34 } } |
Эти параметры используют классы-процессоры, фильтрующие изображения по словам, которые OCR-модель на них распознает. Также зловред запрашивает список ключевых слов по пути /api/e/config/keyword для процессора KeywordsProcessor, который по полученным словам выбирает изображения для загрузки на командный сервер.
Помимо KeywordsProcessor в зловреде есть еще два процессора: DictProcessor и WordNumProcessor. Первый фильтрует картинки на основе локализованных словарей, которые расположены в файлe rapp.binary в ресурсах в зашифрованном виде, второй — на основе количества слов определенной длины. Параметры letterMin и letterMax для каждого из процессоров задают диапазон допустимой длины слов. Для DictProcessor параметр wordlistMatchMin регулирует минимальное количество слов из словаря, которое должно быть найдено на изображении; для WordNumProcessor параметры wordMin и wordMax регулируют диапазон, в который должно попасть количество распознанных слов. То, какой именно из процессоров будет использоваться, регулирует поле rs в ответе на запрос о регистрации зараженного устройства.
Изображения, которые удовлетворяют поиску, загружаются с устройства в три этапа. Сначала на С2 отправляется запрос по пути /api/e/img/uploadedCheck, содержащий хэш MD5 изображения. Затем изображение загружается на облачное хранилище Amazon либо на сервер rust по пути file@/api/res/send, после чего ссылка на него загружается на С2 по пути /api/e/img/rekognition. Таким образом, SDK, который, судя по имени пакета com.spark.stat, предназначался для аналитики, на самом деле является вредоносным и избирательно похищает содержимое галереи.
Мы задались вопросом: какие именно картинки интересуют атакующих? Для этого мы самостоятельно запросили список ключевых слов для OCR-поиска с командных серверов. В каждом случае в ответ нам пришли слова на китайском, японском, корейском, английском, чешском, французском, итальянском, польском и португальском языках. Все эти слова указывают на финансовую мотивацию злоумышленников: их интересуют фразы для восстановления доступа к криптокошелькам, известные как мнемоники.
1 2 3 4 5 6 7 8 9 |
{ "code": 0, "message": "success", "data": { "keywords": ["助记词", "助記詞", "ニーモニック", "기억코드", "Mnemonic", "Mnemotecnia", "Mnémonique", "Mnemonico", "Mnemotechnika", "Mnemônico", "클립보드로복사", "복구", "단어", "문구", "계정", "Phrase"] } } |
К сожалению, вредоносный компонент попал не только в приложение ComeCome. Мы также нашли несколько других не связанных между собой приложений на разную тематику, которые на момент написания отчета были установлены суммарно более 242 000 раз (полный список можно найти в разделе «Индикаторы компрометации»), а некоторые из них все еще были доступны в Google Play. Мы предупредили Google о наличии зараженных приложений в магазине.
Кроме того, по данным нашей телеметрии, зараженные приложения распространялись также через неофициальные источники.
В зависимости от приложения функциональность SDK могла незначительно различаться. Например, тогда как в ComeCome зловред запрашивал разрешения только при открытии чата с поддержкой, в некоторых других триггером служил запуск основной функциональности приложения.
Одна маленькая деталь…
При анализе троянизированных Android-приложений мы обратили внимание, что при отправке на С2 сведений о зараженном устройстве вредоносный SDK заполняет поле deviceType значением android, что намекало на существование аналогичного троянца для других платформ.
Мы провели исследование и обнаружили, что в App Store есть iOS-приложения, зараженные вредоносным фреймворком с этим же троянцем. Например, приложение для доставки еды ComeCome для iOS было заражено, как и его Android-версия. Это первый известный случай заражения приложений OCR-шпионом в официальном магазине Apple.
Вредоносные фреймворки в приложениях в App Store
Мы идентифицировали ряд приложений со встроенным вредоносным фреймворком в App Store. У нас нет точных сведений о том, оказались ли они заражены в результате атаки на цепочку поставок, или же разработчики намеренно встраивали в них троянца. Некоторые приложения, такие как сервисы доставки еды, выглядят вполне легитимными, тогда как другие, очевидно, собраны с целью заманивания жертвы — например, мы видели несколько схожих «мессенджеров» с функциями ИИ от одного и того же разработчика:
В некоторых зараженных приложениях помимо самого вредоносного фреймворка в корневой папке присутствовал скрипт modify_gzip.rb, который, по всей видимости, использовался злоумышленниками на этапе разработки для внедрения фреймворка в приложение:
Сам фреймворк написан на Objective-C и обфусцирован при помощи обфускатора HikariLLVM. В обнаруженных нами приложениях он имел одно из трех названий:
- GZIP;
- googleappsdk;
- stat.
Как и в версии для Android, в iOS-зловреде используется интерфейс ML Kit, позволяющий работать с обученной для распознавания текста OCR-моделью Google, а также Rust-библиотека, реализующая такой же самодельный протокол для общения с C2, однако в этом случае она встроена во вредоносный исполняемый файл. В отличие от Android-версии, в iOS-фреймворке остались отладочные символы, благодаря чему мы смогли отметить несколько его уникальных деталей:
- В строках мелькают пути, по которым проект был расположен на устройстве авторов фреймворка, в которых видны имена пользователей:
- /Users/qiongwu/ — домашняя директория автора самого проекта;
- /Users/quiwengjing/ — домашняя директория автора Rust-библиотеки.
- Модуль, отвечающий за сетевое взаимодействие с C2 rust, называется im_net_sys. Помимо клиентской части он также содержит в себе код, который, предположительно, использует сервер злоумышленников для общения с жертвами.
- Оригинальное имя проекта — GZIP.
Фреймворк содержит несколько вредоносных классов, из которых мы можем выделить наиболее интересные:
- MMMaker — реализует загрузку конфигурации и сбор информации об устройстве.
- ApiMgr — предназначен для отправки данных об устройстве.
- PhotoMgr — выполняет поиск на устройстве и загрузку на сервер фотографий, содержащих ключевые слова.
- MMCore — используется для хранения информации о сессии с С2.
- MMLocationMgr — собирает актуальную информацию о местоположении устройства. Во время тестирования он никуда не отправлял эти данные, поэтому конечная цель его существования остается неясной.
Стоит отметить, что в более ранних версиях фреймворка некоторые классы, такие как MMMaker, могут отсутствовать или иметь другое название, однако основное поведение зловреда от этого не меняется.
Обфускация достаточно сильно усложняет процесс статического анализа образцов, так как в них зашифрованы строки и запутан поток управления кодом. Для быстрого дешифрования интересующих нас строк мы пошли путем динамического анализа, запустив приложение под Frida и собрав дамп секции _data, в которой они хранятся. Наше внимание привлекло наличие среди расшифрованных данных идентификатора пакета (bundleID) исследуемого приложения:
Как оказалось, во фреймворке также хранятся идентификаторы пакетов других приложений, и используются они в селекторе +[MMCore config]. Отсюда мы делаем следующие выводы:- Поведение троянца может отличаться в зависимости от того, в каком приложении он запущен.
- Потенциально зараженных приложений больше, чем мы думали изначально.
Полный список собранных нами идентификаторов пакетов из расшифрованных строк из разных образцов фреймворка приведен в разделе IoC. Некоторые из приложений, ассоциированных с этими идентификаторами, на момент исследования уже были удалены из App Store, другие все еще присутствовали в нем и содержали вредоносный фреймворк. Среди списка идентификаторов приложений есть также и те, в которых на момент исследования вредоносного фреймворка не оказалось.
Как и в Android, троянец реализует три режима фильтрации результатов OCR: по ключевым словам, по количеству слов определенной длины, а также по локализованным словарям, которые хранятся в зашифрованном виде непосредственно внутри фреймворка — в файлах в папке wordlists. К сожалению, нам не удалось удостовериться в том, что зловред действительно использует последние — ни один из исследуемых образцов не содержал в своем теле ссылок на эти словари и не обращался к ним в процессе своей работы.
Самый важный этап в работе вредоносного фреймворка — это отправка отобранных им фотографий с ключевыми словами. По аналогии с Android, троянец запрашивает разрешение на доступ к галерее только в момент запуска UI-контроллера (View Controller), ответственного за отображение чата поддержки. На этапе инициализации троянец, в зависимости от того, в каком приложении он запущен, заменяет метод viewDidLoad либо viewWillAppear соответствующего контроллера на свою обертку, вызывающую метод +[PhotoMgr startTask:], который затем проверяет наличие у приложения доступа к галерее и запрашивает его, если необходимо. Далее, если права получены, PhotoMgr попытается отыскать среди доступных и ранее не обработанных фотографий подходящие под условия отправки.
Хоть и не с первого раза, но нам удалось заставить приложение выполнить отправку картинки в облако Amazon, а затем сообщить информацию о загруженном изображении на сервер злоумышленников. В этом случае общение с сервером происходило по протоколу HTTPS, а не самодельному rust:
Отправляемые данные выглядят следующим образом:
1 2 3 4 5 6 |
POST /api/e/img/uploadedCheck { "imgSign": <imgMD5>, "orgId": <implantId>, "deviceId": <deviceUUID> } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
POST api/e/img/rekognition { "imgUrl": "https://dmbucket102.s3.ap-northeast- 1.amazonaws.com/"<app_name>_<device_uuid>"/photo_"<timestamp>".jpg", "deviceName": "ios", "appName": <appName>, "deviceUUID": <deviceUUID>, "imgSign": <imgMD5>, "imgSize": <imgSize>, "orgId":<implantId>, "deviceChannel": <iphoneModel>, "keyword":<keywordsFoundOnPicture>, "reksign":<processor type> } |
В рамках исследования среди всех версий вредоносного фреймворка старшей оказалась собранная 15 марта 2024 года. Существенных отличий между ней и более свежими версиями нет, однако в ней фигурирует большее количество незашифрованных строк, например конечные точки API, адрес С2 один и зашит в коде фреймворка, а ответы от сервера приходят в незашифрованном виде.
Особенности кампании
Во время анализа Android-приложений мы обнаружили, что процессоры слов содержат в коде комментарии на китайском языке. С2-сервер в ответ на некорректные запросы также возвращает описание ошибок на китайском. Это, а также имя домашней папки разработчика фреймворка, полученное в ходе анализа iOS-версии, дает нам основания полагать, что разработчик вредоносного модуля свободно владеет китайским языком. При этом имеющихся данных недостаточно, чтобы приписать кампанию какой-либо известной группе.
В ходе исследования нам удалось установить мотивацию атакующих — злоумышленники крадут фразы для восстановления доступа к криптокошелькам, которых достаточно, чтобы получить полный контроль над кошельком жертвы для дальнейшей кражи средств. Нельзя не отметить, что гибкость зловреда позволяет ему воровать не только секретные фразы, но и другие личные данные из галереи, например содержание сообщений или пароли, которые могли остаться на скриншотах. При этом наличие различных режимов обработки результатов OCR (процессоров) уменьшает влияние ошибок модели, которые в случае обработки только с использованием ключевых слов могли негативно отразиться на распознавании фразы для восстановления доступа на изображении.
При анализе вредоносного кода на Rust в iOS-фреймворках мы обнаружили как код клиента для общения с сервером rust, так и часть кода сервера, отвечающую за шифрование данных. Это дает нам основания полагать, что работа с протоколом на серверах злоумышленников также реализована на Rust.
Мы полагаем, что данная кампания нацелена как минимум на пользователей Android- и iOS-устройств в Европе и Азии, основываясь на следующих фактах:
- Ключевые слова были приведены на разных языках, носителями которых являются жители европейских и азиатских стран.
- Словари в ресурсах имели такую же локализацию, как и у ключевых слов.
- Некоторые приложения явно работают в нескольких странах. Так, отдельные приложения доставки еды позволяют зарегистрироваться пользователям с номерами телефонов из ОАЭ, Казахстана, Китая, Индонезии, Зимбабве и других стран.
При этом мы не исключаем, что жертвами вредоносной кампании могли стать пользователи мобильных устройств и за пределами обозначенных регионов.
Один из первых вредоносных модулей, с которого мы начали исследование, назывался Spark. При этом наше внимание при анализе iOS-зловреда привлек идентификатор пакета самого вредоносного фреймворка — bigCat.GZIPApp. В честь этого мы назвали троянца SparkCat. Мы также можем выделить следующие отличительные особенности этого зловреда:
- кроссплатформенность;
- использование языка Rust, который редко встречается в мобильных приложениях;
- официальные магазины приложений как один из векторов распространения;
- скрытность — С2-домены часто мимикрировали под легитимные сервисы, а вредоносные фреймворки — под служебные пакеты;
- обфускация, которая затрудняет анализ и детектирование.
Заключение
К сожалению, несмотря на строгую модерацию на официальных площадках, а также на известность схемы по краже криптокошельков при помощи OCR, зараженные приложения все равно оказались в Google Play и App Store. Особенно опасен троянец тем, что ничто не выдает вредоносный имплант внутри приложения: запрашиваемые им разрешения могут использоваться в основной функциональности приложения или показаться на первый взгляд неопасными, а работает зловред достаточно скрытно. Этот случай в очередной раз разрушает миф о том, что угрозы, которые несут вредоносные приложения для Android, неактуальны для iOS. Чтобы не стать жертвой зловреда, мы рекомендуем сделать следующее:
- Если у вас установлено одно из зараженных приложений, удалите его с устройства и не пользуйтесь им до выхода исправления, в котором вредоносная функциональность будет устранена.
- Не храните в галерее скриншоты с чувствительной информацией, в том числе фразы для восстановления доступа к криптовалютным кошелькам. Пароли, конфиденциальные документы и другие чувствительные данные можно хранить в специальных приложениях.
- Используйте надежное защитное решение на всех своих устройствах.
Наши защитные решения детектируют вредоносные программы, связанные с этой кампанией, со следующими вердиктами:
- HEUR:Trojan.IphoneOS.SparkCat.*
- HEUR:Trojan.AndroidOS.SparkCat.*
Индикаторы компрометации
Зараженные Android-приложения
0ff6a5a204c60ae5e2c919ac39898d4f
21bf5e05e53c0904b577b9d00588e0e7
a4a6d233c677deb862d284e1453eeafb
66b819e02776cb0b0f668d8f4f9a71fd
f28f4fd4a72f7aab8430f8bc91e8acba
51cb671292eeea2cb2a9cc35f2913aa3
00ed27c35b2c53d853fafe71e63339ed
7ac98ca66ed2f131049a41f4447702cd
6a49749e64eb735be32544eab5a6452d
10c9dcabf0a7ed8b8404cd6b56012ae4
24db4778e905f12f011d13c7fb6cebde
4ee16c54b6c4299a5dfbc8cf91913ea3
a8cd933b1cb4a6cae3f486303b8ab20a
ee714946a8af117338b08550febcd0a9
0b4ae281936676451407959ec1745d93
f99252b23f42b9b054b7233930532fcd
21bf5e05e53c0904b577b9d00588e0e7
eea5800f12dd841b73e92d15e48b2b71
MD5 iOS-фреймворков
35fce37ae2b84a69ceb7bbd51163ca8a
cd6b80de848893722fa11133cbacd052
6a9c0474cc5e0b8a9b1e3baed5a26893
bbcbf5f3119648466c1300c3c51a1c77
fe175909ac6f3c1cce3bc8161808d8b7
31ebf99e55617a6ca5ab8e77dfd75456
02646d3192e3826dd3a71be43d8d2a9e
1e14de6de709e4bf0e954100f8b4796b
54ac7ae8ace37904dcd61f74a7ff0d42
caf92da1d0ff6f8251991d38a840fb4a
Конфигурации троянца на GitLab
hxxps://gitlab[.]com/group6815923/ai/-/raw/main/rel.json
hxxps://gitlab[.]com/group6815923/kz/-/raw/main/rel.json
C2
api.firebaseo[.]com
api.aliyung[.]com
api.aliyung[.]org
uploads.99ai[.]world
socket.99ai[.]world
api.googleapps[.]top
Хранилище для фотографий
hxxps://dmbucket102.s3.ap-northeast-1.amazonaws[.]com
Имена пакетов зараженных Android-приложений из Google Play
com.crownplay.vanity.address
com.atvnewsonline.app
com.bintiger.mall.android
com.websea.exchange
org.safew.messenger
org.safew.messenger.store
com.tonghui.paybank
com.bs.feifubao
com.sapp.chatai
com.sapp.starcoin
BundleID, зашифрованные в теле iOS-фреймворков
im.pop.app.iOS.Messenger
com.hkatv.ios
com.atvnewsonline.app
io.zorixchange
com.yykc.vpnjsq
com.llyy.au
com.star.har91vnlive
com.jhgj.jinhulalaab
com.qingwa.qingwa888lalaaa
com.blockchain.uttool
com.wukongwaimai.client
com.unicornsoft.unicornhttpsforios
staffs.mil.CoinPark
com.lc.btdj
com.baijia.waimai
com.ctc.jirepaidui
com.ai.gbet
app.nicegram
com.blockchain.ogiut
com.blockchain.98ut
com.dream.towncn
com.mjb.Hardwood.Test
com.galaxy666888.ios
njiujiu.vpntest
com.qqt.jykj
com.ai.sport
com.feidu.pay
app.ikun277.test
com.usdtone.usdtoneApp2
com.cgapp2.wallet0
com.bbydqb
com.yz.Byteswap.native
jiujiu.vpntest
com.wetink.chat
com.websea.exchange
com.customize.authenticator
im.token.app
com.mjb.WorldMiner.new
com.kh-super.ios.superapp
com.thedgptai.event
com.yz.Eternal.new
xyz.starohm.chat
com.crownplay.luckyaddress1
Возьмите мои деньги: OCR-воры криптокошельков в Google Play и App Store