The zLabs team has uncovered RatHat, a novel Android malware strain linked to threat actors that appear to be operating in China. RatHat incorporates novel techniques for persistence and leverage generative AI for operational control.
Distributed primarily via targeted smishing (SMS/text phishing) and malvertising campaigns leading to deceptive third-party download portals, RatHat uses an automated multi-stage infection pipeline. Once deployed, it pairs Accessibility abuse with autonomous local ADB (Android Debug Bridge) self-pairing to break out of the standard Android application sandbox, staging independent native daemons that execute with shell-level privileges.
RatHat highlights a distinct paradigm shift in the mobile threat landscape: moving away from static, easily disrupted automation toward adaptive, AI-assisted execution chains that operate outside the constraints of traditional mobile app sandboxes.
Threat actors are constantly refining their malware to gain undetected, unauthorized access to sensitive financial information. To succeed, malware developers must overcome two primary obstacles: neutralizing modern OS security controls and deceiving increasingly cautious users.
Initial compromise relies on hybrid delivery channels that combine social engineering with technical obfuscation. During this research we have discovered how the malware has been adapting itself and improving in order to maximize infection rates.
RatHat is primarily distributed through deceptive phishing sites promoted via malvertising, smishing campaigns, and third-party forums, luring victims into manually downloading malicious APKs that appear to be legitimate apps (Figure 1).
Fig.1: Phishing site distributing RatHat malware
In response to continuous improvements in Android's security architecture, threat actors have refined their deployment methods to bypass real-time detection and ensure persistent access to compromised endpoints.
To remain silent, the installation is done by a dropper and the malware contains four anti-analysis layers and one anti-debug layer. All these stages are described below.
The dropper carries its payload in two encrypted assets. Both use the same trivial transform: skip a 24-byte header, read a big-endian length, then per byte ((b ^ 0x4A) - 0xD2) ^ 0xF1, then gunzip. The stage 2 is an in-memory DEX loaded by reflection that contains the code inside to unpack and install the malware payload.
Droppers exploit native SessionInstaller APIs to bypass Android’s restricted settings and Accessibility Service protections, enabling the installation of the malicious payload and subsequent access to protected APIs.
Droppers and payloads incorporate multiple sophisticated anti-analysis mechanisms to complicate static and dynamic inspection. These defenses span four distinct technical layers: container tampering, manifest manipulation, DEX bytecode poisoning, and dual string-encryption schemes.
The container tampering layer consists of declaring some files as directories in the zip, or setting the ZIP general-purpose encryption bit on some files. These modifications are ignored by Android libziparchive but not by unzip, apktool and other tools. A similar technique is used by other malware families we blogged about in the past.
The AndroidManifest.xml file is of 61MB and the 99% of it is composed by 2 chunks of undocumented type 0x9999 placed between string pool and the real element stream. This exploits a parse differential: Android’s native runtime simply skips the unknown 0x9999 chunk headers, whereas decompilers and static analyzers fail to parse the non-standard structure or exhaust their memory limits, causing automated analysis pipelines to crash or time out.
A substantial portion of the Dex code incorporates pseudo-instructions configured with an invalid element_width attribute. Although the Android Runtime (ART) never executes these payloads, disassemblers attempt to calculate the payload length. Consequently, while the application operates normally, the disassembly process fails.
General strings use StringFog (a string encryption library for Android) that is based on a base64 ciphertext XORed with a per-string base64 key. In order to prevent the analysis, this malware uses a scheme called StringCrypto: base64. On top of this, the malware swaps adjacent byte pairs and XOR the result with a 16-byte key.
The malware contains six runtime checks for avoiding the debugging:
The architecture of this Android threat can be broken down into three primary operational parts: the Malicious Android Application, the Go Agent (liblocal-service.so), and the FRP Client (libmedia_codec.so).
Fig.2: Architecture and Operational Flow
Its main role is to act as the primary interface with the user, acquire critical system permissions, and bootstrap the rest of the infection chain:
Fig.4: AI Prompts used by RatHat indicating potential of China-based threat actors
The malware includes a sophisticated agent written in Go, which is dropped and executed by the APK once a shell is acquired via the local ADB daemon. It is shipped disguised as a native library (liblocal-service.so) and staged to /data/local/tmp.
The agent is the command-and-control brain executing commands from an ADB shell context, bypassing security limitations that the user-facing app faces.
It is responsible for applying critical system-level persistence and power management exemptions. These are crucial for steady-state operation but are absent from the APK's DEX file, existing only within the Go binary. These commands are visible in the Go agent's strings:
The third main part is the reverse-proxy client, frpc (derived from fatedier/frp). This is also a Go binary, shipped disguised as libmedia_codec.so. Once staged to /data/local/tmp/frpc, its only function is to establish a secure, persistent reverse tunnel back to the C2 server. It connects back to the attacker using configuration data (IP, Port, and Token) fetched from the C2 server by the Go Agent (main.fetchFrpcConfigFromServer).
This connection is used by attackers to have access to the ADB daemon: it's a general-purpose road into the device that carries whatever the operator wants, independent of the malware's own feature set.
The infection and operational flow illustrates the precise sequence through which these components interact.
Initially, the Android application secures Accessibility service access and employs synthetic tap gestures to enable Developer Options and access the Wireless Debugging pairing interface. Subsequently, the application leverages its Accessibility service to extract the local pairing PIN and dynamic port from the user interface. Utilizing the embedded libadb-android library, the application authenticates against the local device's ADB daemon, thereby securing shell-level access to the /data/local/tmp directory.
Via this established ADB connection, the application executes commands to stage and launch both the Go Agent and the FRP Client.
The application interacts with the active Go Agent over a local loopback HTTP interface. Operating within its elevated shell context, the Go Agent executes key system commands—including pm grant and deviceidle—to grant the application persistent operation and WRITE_SECURE_SETTINGS permissions.
Finally, the Go Agent retrieves the FRP tunnel configuration from the C2 server, enabling the FRP Client to establish a persistent, active reverse tunnel to the operator.
The App intercepts the uninstall confirmation dialog and renders a fake failure overlay styled as Google Play, cancelling the uninstallation. But even if the user manages to uninstall it, there is another mechanism in place: the local-service is running outside the package lifecycle, so it is not removed when the app is uninstalled.
After the uninstallation, the attacker still has a shell on the device. The service checks if the app is still installed and if the presence is not confirmed, then the app is reinstalled.
The service launches the following commands that allow the reinstallation of the apk, granting every runtime permission automatically and re-granting the accessibility without interacting with the UI:
|
> pm install -r -g -i com.android.packageinstaller /data/local/tmp/app.apk |
On the other side, until the app is present and running, it runs a heartbeat that redeploys and relaunches local-service if it stops responding, rescanning the ADB port ladder and re-enabling debugging settings if needed.
The malware establishes persistent, real-time command-and-control channels to execute remote commands and exfiltrate telemetry, though they rely on fundamentally different networking protocols.
The Android App registers over HTTP, then holds a WebSocket open with heartbeats. Requests are authenticated with HMAC-SHA256 over a device key derived from deviceKeySalt.
Using the embedded Go Agent (liblocal-service.so), the malware spins up an HTTP server bound to 127.0.0.1:7910.
Additionally, as said before, it deploys a masqueraded native library (libmedia_codec.so), which is actually a Fast Reverse Proxy Client (frpc), to expose the internal loopback server and local device interfaces to the internet, bypassing NAT and firewall restrictions.
| Endpoint | Caller | Data Carried / Description |
|---|---|---|
| /api/node/register | App | Device enrolment and fingerprint |
| /api/data/credentials | App | Overlay-harvested credentials |
| /api/sync/cipher | App | Lock-screen credential sync |
| /api/data/messages | App | SMS bodies |
| /api/data/app-list | App | Installed-application inventory |
| /api/fs/upload-from-device /api/fs/download-for-device |
App | File transfer in both directions |
| /api/adbk/upload | App | The device's ADB cert.pem and private.key (multipart, with deviceId = android_id) |
| /api/adbk/port | App | {"deviceId","ip":"127.0.0.1","port":N} the live wireless-debugging port |
| /api/data/cipher | Both | Lock-screen PIN, pattern, and password material. Managed by agent via main.syncCipherToServer so exfiltration survives app removal |
| /api/data/form | Both | Overlay form submissions |
| /api/node/logs | Both | Log upload, including anti-analysis findings |
| /api/v2/dev/inj/global-configs | Both | Overlay template distribution |
| /api/v2/dev/tasks?deviceId=%s /api/v2/dev/task/response |
Agent | Work queue and results, a command channel independent of the app's WebSocket |
| /api/v2/dev/cacheTasks?deviceId=%s /api/v2/dev/cacheTaskResult |
Agent | Offline queue (main.startupSyncServerCacheTasks) that drains buffered tasks, executing downtime commands on reconnect |
| /api/v2/dev/message | Agent | Free-form messaging (main.postMessageToServer), including initial start check-in |
| /api/v2/dev/localServiceHeartbeat | Agent | Agent liveness check, separate from the app's heartbeat |
| /api/v2/dev/data/screen-monitor | Agent | Shell-level screen monitoring, independent of MediaProjection and its consent dialog |
| /api/v2/dev/upload/video /api/vid/upload?deviceId=%s |
Agent | Chunked video upload pipeline (handleBeginVideoUpload → handleAppendVideoChunk → handleFinishVideoUpload) |
| /api/tun/config | Agent | FRP tunnel configuration, main.fetchFrpcConfigFromServer retrieves FrpsAddr, FrpsPort, and FrpsToken |
| /api/tun/deployed | Agent | Reports active tunnel status via main.notifyTunnelDeployed |
| /api/sapp | Agent | Evasion list via main.fetchSensitiveAppsFromServer, suspends Accessibility processing on sensitive packages without updating the build |
| /api/bapp | Agent | Block list (main.fetchBlackAppsFromServer) |
| /api/bin/%s/minicap.so?sdk=%s /api/bin/%s/minicap?brand=%s /api/bin/noarch/minicap.apk |
Agent | Screen-capture tooling selected by device ABI, SDK level, and brand |
| /api/v2/dev/agentVersion?deviceId=%s¤tVersion=%s | Agent | Self-update check staged to /data/local/tmp/local-service.update |
| /api/v2/dev/install-log | Agent | Install telemetry |
Keylogging remains a staple technique across Remote Access Trojans (RATs). While malware authors employ various strategies to harvest keystrokes, exploiting Android's Accessibility service to track screen activity and exfiltrate data to command-and-control (C&C) servers is among the most prevalent.
To achieve this, RatHat implements the 3 specialized mechanisms described below.
UniversalInputMonitor consumes accessibility text-change events and reconstructs what was typed, tracking beforeText, eventText, nodeText, hintText, fieldId and pkg per event. It classifies credential fields using the localised PASSWORD_KEYWORDS, PIN_KEYWORDS and ENTER_PASSWORD_KEYWORDS categories, and specifically handles masked_input, reconstructing content from a field showing only dots, with password_field, new_field and per-character char_ records.
KeystrokeCapture reads address bars by resource ID: com.android.chrome:id/url_bar, com.brave.browser:id/url_bar, com.opera.browser:id/url_field, com.opera.mini.native:id/url_field, com.microsoft.emmx:id/url_bar, com.duckduckgo.mobile.android:id/omnibarTextInput, com.sec.android.app.sbrowser:id/location_bar_edit_text, plus AOSP and ColorOS browsers.
This is the most capable of the three, and it's a genuine hardware-level keylogger. The class driving it is named AdbTouchCapture.
This logging is executed by the Go Agent and it is able to record where the finger touched the screen.
Fig.5: Go Agent keylogging
The malware is leveraging the fact that it is executed in a shell environment and is using the tool getevent. The command getevent is a standard Android debugging tool that reads (Figure 5) the raw device file the kernel writes to. Its output looks like this:
|
[ 12345.678901] /dev/input/event2: EV_ABS ABS_MT_POSITION_X 0000021c |
That's it: a X, a Y, and a timestamp. No text, no app name, no idea what was on screen. Just "someone touched pixel (540, 1624) at this moment."
Normal apps cannot read /dev/input/*; the permissions don't allow it. Shell UID 2000 can. That's a large part of why the malware goes through the whole Wireless Debugging self-pairing dance in the first place, this capability is one of the prizes.
A raw coordinate is useless on its own. What makes it a keylogger is the second half: the malware ships locateValues.json, which contains the keypad layouts for every major phone brand, where key "0" sits, where "5" sits, the geometry of the pattern-lock grid.
So touch at (540, 1624) + "this is a Samsung PIN pad" = "5"
The same trick is used for pattern unlock: the sequence of touch points is matched against the 3×3 dot grid to recover the pattern. That's what the Point and DotAlign helper classes in the cipher package do, and it's why the malware tags recovered credentials with a type called PASSWORD_QUALITY_TOUCH_POINTS: it knows this credential came from geometry rather than from text.
This approach seems to be quite complex and very powerful since it is able to bypass the protections that Android put in place for the other techniques:
RatHat is able to steal the credentials of some banking/crypto applications: the malware monitors the foreground application and, upon detecting a targeted package, triggers the corresponding HTML injection mechanism.
The malicious HTML content is supplied by the attacker and is associated with the targeted application, allowing the malware to present a deceptive interface over the legitimate application to capture sensitive banking credentials as shown in Figure 6.
Fig.6: Malware targeting banking apps
RatHat malware focuses also on payment applications like WeChat and Alipay, deploying deceptive screen overlays (Figure 7) that are directly embedded in its code to steal user PINs.
Fig.7: RatHat targeting payment apps
RatHat’s multi-tiered architecture, reliance on out-of-lifecycle daemons, and use of real-time GenAI decision loops illustrate why traditional, signature-based mobile security controls are insufficient. Both Zimperium’s Mobile Threat Defense (MTD) and Mobile Runtime Protection (zDefend) provide comprehensive zero-day coverage against RatHat across every stage of the attack chain.
For enterprise mobile devices, Zimperium MTD detects and neutralizes RatHat through on-device, dynamic machine learning models:
For financial institutions, e-wallets, and payment providers targeted by RatHat’s credential-harvesting overlays and input scraping:
RatHat IOCs can be found in this repository.