SparkCat OCR Crypto Stealer Targeting Android and iOS Users

PT FPT Metrodata Indonesia (FMI) is a joint venture between FPT IS and Metrodata Electronics, focusing on providing Cybersecurity-as-a-Service—including SOC, managed security, professional services, consulting, and threat intelligence—to support Indonesia’s rapidly growing digital economy. FMI is expanding into AI and cloud GPU services to deliver innovative protection and solutions for enterprises. Learn more at https://fmisec.com.
Summary
Cyble Research and Intelligence Labs (CRIL) came across an article in which a security researcher discovered a malware campaign, " SparkCat, " targeting Android and iOS users through official and unofficial app stores in late 2024.
The campaign involved malicious apps, some available on Google Play and the App Store, embedded with an SDK/framework designed to steal cryptocurrency wallet recovery phrases. The infected apps on Google Play had been downloaded over 242,000 times, marking the first known instance of a stealer being identified in Apple’s App Store.
The Android malware module decrypted and launched an OCR plug-in built with Google’s ML Kit library to scan images in the gallery. The plug-in extracted text that matched keywords received from the Command and Control (C&C) server and transmitted it to the attackers.
The iOS-specific module followed a similar design, also leveraging Google’s ML Kit for OCR. Notably, SparkCat used an unidentified protocol implemented in Rust—a language rarely seen in mobile malware—to communicate with its C&C server. Based on timestamps in malware files and the creation dates of configuration files in GitLab repositories, the campaign appears to have been active since March 2024.
Technical Detail
Malware SDK found in Google Play apps
A food delivery app in the UAE and Indonesia, named “ComeCome” (APK name: com.bintiger.mall.android), was identified as containing malicious components. At the time of research, it was available on Google Play and had been downloaded more than 10,000 times.

In version 2.0.0 (f99252b23f42b9b054b7233930532fcd), the onCreate method in the Application subclass—one of the app’s entry points—was overridden to initialize an SDK component named “Spark.”

Written in Java, Spark downloads a JSON configuration file from a GitLab URL embedded in the malware body upon initialization. The JSON file is then base64-decoded and decrypted using AES- 128 in CBC mode.


The “http” and “rust” fields contain SDK-specific C&C addresses, while the tfm flag determines which C&C server to use. If tfm is set to 1, the “rust” address is selected; otherwise, the “http” address is used.
The process of sending data to the “rust” C&C server occurs in three stages:
Data is encrypted using AES-256 in CBC mode, utilizing the same key as the “http” server.
The malware generates a JSON payload, where <PATH> represents the data upload path and <DATA> contains the encrypted data from the previous step

The JSON payload is then transmitted to the server using the native libmodsvmp.so library via an unidentified protocol over TCP sockets. This library, written in Rust, masquerades as a popular Android obfuscator.
Before transmitting data to the server, the library generates a 32-byte key for the AES-GCM-SIV cipher. Using this key, the data—pre-compressed with ZSTD—is encrypted. The algorithm’s nonce value is not dynamically generated but is instead hardcoded as “unique nonce” in the code.

The AES key is encrypted using RSA before being sent to the server. The public RSA key for encryption is provided when calling a native method from the malicious SDK in PEM format. Before encryption, the message is padded with 224 random bytes. When the server receives the request, it decrypts the AES key using its private RSA key, decodes the received data, then compresses the response with ZSTD and encrypts it using the AES-GCM-SIV algorithm. After the server response is decrypted in the native library, it is passed to the SDK, where it undergoes base64 decoding and decryption, following the same process used for communication with the “http” server. An example of the communication between the malware module and the “rust” server is provided below.

Once a configuration is downloaded, Spark decrypts a payload from the app’s assets and executes it in a separate thread. The payload (c84784a5a0ee6fedc2abe1545f933655) serves as a wrapper for the TextRecognizer interface in Google’s ML Kit library. It loads various OCR models based on the system language to detect Latin, Korean, Chinese, or Japanese characters in images. The SDK then uploads device information to /api/e/d/u on the C2 server. In response, the server sends an object that governs further malware actions. This object is a JSON file with the structure shown below. The uploadSwitch flag, when set to 1, allows the malware to continue running.

When the user initiates a chat with the support team, implemented using the legitimate third-party Easemob HelpDesk SDK, the handler requests access to the device’s image gallery. If the pw flag in the previously mentioned object is set to 1, the module will continue requesting access even if initially denied. At first glance, the request for gallery access seems reasonable, as users may want to attach images when contacting support.

If access is granted, the SDK activates its main functionality. This begins by sending a request to /api/e/config/rekognition on the C&C server and receiving parameters for processing OCR results in the response.

These parameters are utilized by processor classes that filter images based on OCR-recognized words. The malware also requests a list of keywords from /api/e/config/keyword for the KeywordsProcessor, which uses the list to select images for uploading to the C&C server.

In addition to the KeywordsProcessor, the malware includes two other processors: DictProcessor and WordNumProcessor. DictProcessor filters images using localized dictionaries stored decrypted in the app’s assets, while WordNumProcessor filters words based on length, with parameters defining acceptable word ranges. The rs field in the response determines which processor to use.
Images matching the search criteria are processed in three steps: a request with the image’s MD5 hash is sent to /api/e/img/uploadedCheck, followed by uploading the image to either Amazon cloud storage or the "rust" server. Finally, a link to the image is sent to /api/e/img/rekognition. Despite being labeled as an analytics SDK (com.spark.stat), it is actually malware designed to selectively steal gallery content.

Malicious frameworks found in App Store apps
A series of apps embedded with a malicious framework were detected in the App Store. It cannot be confirmed with certainty whether the infection resulted from a supply chain attack or was a deliberate action by the developers. Some of the apps, such as food delivery services, appeared legitimate, while others seemed designed to lure victims. For instance, several similar AI-featured “messaging apps” were found by the same developer:

In addition to the malicious framework, some of the infected apps contained a modify_gzip.rb script in the root folder. The developers likely used this script to embed the framework into the app:

The framework is written in Objective-C and obfuscated using HikariLLVM. In the apps detected, it appeared under one of three names:
GZIP
googleappsdk
stat
Similar to the Android version, the iOS malware used the ML Kit interface to access a Google OCR model trained to recognize text, along with a Rust library implementing a custom C&C communication protocol. However, in this case, the Rust library was embedded directly into the malicious executable. Unlike the Android version, the iOS framework retained debugging symbols, revealing several unique details:
Paths on the framework creators' device, including user names, were exposed:
/Users/qiongwu/: The project author's home directory
/Users/quiwengjing/: The Rust library creator’s home directory
The C&C-rust communication module was named im_net_sys. In addition to the client, it included code presumably used by the attackers’ server to communicate with victims.
The original project name was GZIP.

The framework includes several malicious classes, with the following being of particular interest:
MMMaker: Downloads a configuration and collects device information.
ApiMgr: Sends device data.
PhotoMgr: Searches for photos containing specific keywords on the device and uploads them to the server.
MMCore: Stores information about the C&C session.
MMLocationMgr: Gathers the device’s current location. During testing, it did not send data, leaving its exact purpose unclear.
Some classes, like MMMaker, may be missing or renamed in earlier versions of the framework, but this doesn’t impact the malware's core functionality.
Obfuscation significantly complicates the static analysis of samples, as strings are encrypted and the program’s control flow is obscured. To quickly decrypt the relevant strings, dynamic analysis was used. The application was run under Frida, and a dump of the _data section, where these strings were stored, was captured. Notably, the app’s bundleID was among the decrypted data.

The framework also stored other app bundle identifiers, used in the +[MMCore config] selector, indicating that:
The Trojan’s behavior varies depending on the host app.
More apps may be infected than initially suspected
Like its Android counterpart, the Trojan filters OCR output using keywords, word length, and localized dictionaries stored in encrypted form within a "wordlists" folder.
Photo theft is a key function of the framework. Like the Android version, the Trojan requests gallery access only when launching the support chat. At initialization, it replaces the viewDidLoad or viewWillAppear method in the relevant controller, invoking +[PhotoMgr startTask:]. If access is granted, PhotoMgr scans unprocessed photos that match the predefined criteria.

The app communicated with the server over HTTPS instead of using the custom “rust” protocol.

Recommendation
We have listed some essential cybersecurity best practices that create the first line of control against attackers. We recommend that our readers follow the best practices given below:
Avoid saving screenshots containing sensitive information, such as crypto wallet recovery phrases, in the gallery.
Be wary of apps requesting unnecessary access, such as gallery access in a food delivery or messaging app.
Install reputable mobile security apps to detect and block malicious activity.
Unexpected app behavior, excessive battery drain, or data usage spikes could indicate malware.
Use app store security options like Google Play Protect to scan apps for threats.
Use strong passwords and enforce multi-factor authentication wherever possible.
Keep your devices, operating systems, and applications updated.
Conclusion
Despite rigorous screening by official marketplaces and awareness of OCR-based crypto wallet theft scams, the infected apps still managed to appear on Google Play and the App Store. What makes this Trojan particularly dangerous is its stealthy nature—there are no clear signs of a malicious implant within the app. The requested permissions may seem necessary for core functionality or appear harmless at first glance. This case further disproves the notion that iOS is immune to the threats posed by malicious apps targeting Android.





