Recently at EFF’s Threat Lab, we’ve been focusing a lot on the Android malware ecosystem and providing tools for its analysis. We’ve noticed lot of samples of Android malware in the tor-hydra family have surfaced, masquerading as banking apps to lure unsuspecting customers into installing them. In this post, we will take an example of one such sample and analyze it using open-source tools available to anyone.

At a Glance

The sample we’ll be looking at was first seen on March 1st, 2022. This particular malware presents itself as the banking app for BAWAG, a prominent financial institution in Austria. Upon first run, the app prompts the user to give “accessibility services” permission to the app. The accessibility services permission grants an app broad access to read the screen and mimic user interaction. Upon granting the permission, the app backgrounds itself. Any attempt by the user to uninstall the app is prevented by the app interrupting and closing the uninstall dialogues. Attempting to open the app again also fails—nothing happens.

App UX

Analyzing the Android Package APK

AndroidManifest.xml

The Android app manifest file contains a list of permissions, activities, and services that an app provides. If an activity is not listed in the app manifest, the app can’t launch that activity. Using an Android static analysis tool like jadx or apktool we can take a look at the manifest XML. The malware app’s manifest asks for a wide range of permissions, including the ability to read and send SMS messages (a common way for malware to propagate), request installation and deletion of packages, read contacts, initiate calls, and request the aforementioned accessibility service. In addition, a number of classes are referenced which are not defined anywhere in our jadx-reversed code:

  • com.ombththz.ufqsuqx.bot.components.commands.NLService
  • com.ombththz.ufqsuqx.bot.components.injects.system.InjAccessibilityService
  • com.ombththz.ufqsuqx.bot.components.locker.LockerActivity
  • com.ombththz.ufqsuqx.bot.components.locker.LockerActivity$DummyActivity
  • com.ombththz.ufqsuqx.bot.components.screencast.ScreencastService
  • com.ombththz.ufqsuqx.bot.components.screencast.ScreencastStartActivity
  • com.ombththz.ufqsuqx.bot.components.screencast.UnlockActivity
  • com.ombththz.ufqsuqx.bot.components.socks5.Socks5ProxyService
  • com.ombththz.ufqsuqx.bot.HelperAdmin$MyHomeReceiver
  • com.ombththz.ufqsuqx.bot.PermissionsActivity
  • com.ombththz.ufqsuqx.bot.receivers.MainReceiver
  • com.ombththz.ufqsuqx.bot.sms.ComposeSmsActivity
  • com.ombththz.ufqsuqx.bot.sms.HeadlessSmsSendService
  • com.ombththz.ufqsuqx.bot.sms.MmsReceiver
  • com.ombththz.ufqsuqx.bot.sms.SmsReceiver
  • com.ombththz.ufqsuqx.core.injects_core.Screen
  • com.ombththz.ufqsuqx.core.injects_core.Worker
  • com.ombththz.ufqsuqx.core.PeriodicJobReceiver
  • com.ombththz.ufqsuqx.core.PeriodicJobService
  • com.ombththz.ufqsuqx.MainActivity
  • info.pluggabletransports.dispatch.service.DispatchReceiver
  • info.pluggabletransports.dispatch.service.DispatchService
  • info.pluggabletransports.dispatch.service.DispatchVPN
  • org.torproject.android.service.OrbotService

The fact that the manifest references activities, services and receivers it wants to be run without defining them is the first indication that we are dealing with an “Android dropper.”

Unpacking Android Droppers

An Android dropper is malware which obfuscates its behavior by hiding its payload and only decoding and loading the code it needs at runtime. As Ahmet Bilal Can explains, this makes it harder for AV and security researchers to detect the malware by including “reflection, obfuscation, code-flow flattening and trash codes to make [the] unpacking process stealthy.” While stealthy, the steps the malware takes to hide itself can still be detected and subverted with a little help from the dynamic instrumentation toolkit Frida. Frida is able to inject itself into the control-flow of a running app, introducing its own code. This can be helpful to detect typical methods malware uses to disguise itself and load the underlying payload. In this case, we can use a short script to detect that Java classes are being loaded dynamically:

var classLoader = Java.use('java.lang.ClassLoader');
var loadClass = classLoader.loadClass.overload('java.lang.String', 'boolean');

loadClass.implementation = function(str, bool){
  console.log("== Detected ClassLoader usage ==");
  console.log("Args: ", str, bool);
  return this.loadClass(str, bool)
}

Running this code, we get

$ frida -U -f com.ombththz.ufqsuqx -l class-loader-usage.js --no-pause
     ____
    / _  |   Frida 15.1.16 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
Spawned `com.ombththz.ufqsuqx`. Resuming main thread!                   
[Android Emulator 5554::com.ombththz.ufqsuqx]-> == Detected ClassLoader usage ==
Args:  com.honey.miletes.k false
== Detected ClassLoader usage ==
Args:  android.support.v4.content.FileProvider false
== Detected ClassLoader usage ==
Args:  com.ombththz.ufqsuqx.App false
== Detected ClassLoader usage ==
Args:  com.ombththz.ufqsuqx.MainActivity false
== Detected ClassLoader usage ==
Args:  com.ombththz.ufqsuqx.core.injects_core.Worker false
== Detected ClassLoader usage ==
Args:  com.ombththz.ufqsuqx.bot.PermissionsActivity false
== Detected ClassLoader usage ==
Args:  org.torproject.android.service.OrbotService false

Our missing classes are indeed being loaded dynamically!

Previous iterations of tor-hydra malware dynamically loaded a dex file (an Android Dalvik executable file), which could be seen with adb logcat, and used the syscall unlink to delete that file, which would be seen in an strace call. For this app, we can use the command

monkey -p com.ombththz.ufqsuqx -c android.intent.category.LAUNCHER 1 && set `ps -A | grep com.ombththz.ufqsuqx` && strace -p $2

to see the syscalls in real time. We did not observe unlink being used in this sample, so this iteration was doing something different. Java provides a method in java.io.File called delete, which will not trigger the unlink syscall. Using this script, we can detect when that method is used, alert us of the file it attempted to delete, and make it a non-operation:

var file = Java.use("java.io.File")

file.delete.implementation = function(a){ 
  console.log("=> Detected and bypassed Java file deletion: ", this.getAbsolutePath());
  return true;
}  

The first few files deleted are of interest:

=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/tyfkjfUjju/HjIgfhjyqutIhjf/tmp-base.apk.gjGyTF88583765359401054429.88g
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/tyfkjfUjju/HjIgfhjyqutIhjf/dfGgIgyj.HTgj
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/tyfkjfUjju/HjIgfhjyqutIhjf/base.apk.gjGyTF81.88g
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/tyfkjfUjju/HjIgfhjyqutIhjf
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/shared_prefs/multidex.version.xml.bak
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/shared_prefs/pref_name_setting.xml.bak
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/files
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/shared_prefs/prefs30.xml.bak
=> Detected and bypassed Java file deletion:  /data/user/0/com.ombththz.ufqsuqx/files/all_tor.zip

Once we issue an adb pull to download the base.apk.gjGyTF81.88g file from the device, we can use jadx again to determine that this includes the missing class definitions referenced in the manifest.

Investigating the Unpacked Payload

Looking into these files, there is a string obfuscation method that appears thousands of times throughout the code, unaltered from instance to instance:

private static String $(int i, int i2, int i3) {
    char[] cArr = new char[i2 - i];
    for (int i4 = 0; i4 < i2 - i; i4++) {
        cArr[i4] = (char) ($[i + i4] ^ i3);
    }
    return new String(cArr);
}

Wherever we see a call which looks like $(166, 217, 28670) in the code, it refers to this function and uses the $ variable in the same scope to return a string. We can use a Java sandbox like this one to define the locally-scoped $ variable, the $ method, and print out the decoded string.

In sources/com/ombththz/ufqsuqx/bot/network/TorConnectionHelper.java we see a method which looks like a promising lead called loadAdminUrl. Decoding the $(556, 664, 4277) call, we get a base64-encoded onion address:

http://loa5ta2rso7xahp7lubajje6txt366hr3ovjgthzmdy7gav23xdqwnid.onion/api/mirrors

This address is available over the Tor network, and contains a base64-encoded URL which references the command and control (C&C) server, the server from which the malware operator issues commands. The author of this post reached out to the Tor Project on March 7th informing them of this C&C server. On app bootstrap, the Tor network is connected to by code lifted from Orbot in order to discover the C&C server, and then the Tor connection is promptly dropped. When first doing this investigation, the domain referenced yuuzzlllaa.xyz, but this has since changed to zhgggga.in. We can see a login page for the C&C server administrator when accessed:

C&C Login Page

One of the main features of the Tor network is censorship-resistance. If you can access the Tor network, you can access information and websites that cannot easily be taken down because of the way the network is architected. This is a good thing for dissidents in censorship regimes or whistleblowers trying to get privileged information to reporters: the services they rely on will be available even if their adversaries don’t want them to be. This is a double-sided coin, though—in this case malware is also able to direct victims’ devices to C&C servers in a way that can’t be taken down. There is no way to have one without the other and keep the integrity of the network intact. In this case, the clearnet domain yuuzzlllaa.xyz was presumably taken down after being reported and then the malware operator spun up another domain at zhgggga.in without much interruption of the malware command and control. In these cases, reporting malicious C&C domains seems like a game of whack-a-mole: as soon as you take one down, the next pops up.

In the file com/ombththz/ufqsuqx/bot/DexTools.java we see an interesting method, run(), which loads a stage-2 payload from the admin C&C url path /payload. This is a dex file which can be decoded by jadx to an app ID of com.fbdevs.payload. Unfortunately for the sake of our analysis, this file contains mostly uninteresting and non-malicious code.

Looking at the om/ombththz/ufqsuqx/bot/components/ path, many of the components seem to be inherited directly from the Android BianLian malware, an excellent analysis of which can be found here. One of the components not included in this previous iteration is under the socks5 path, which opens a proxy server to a specified host in order to receive commands and launch attacks. All the components are activated and controlled by the C&C server through a Firebase Cloud Messaging (FCM) connection, allowing messages targeting specific devices.

Fighting Back Against Malware

Despite relatively state-of-the-art techniques employed to thwart analysis, a few powerful publicly accessible open-source tools were used to interrupt the control flow and reverse engineer this sample. More complex malware will detect hardware profiles and be able to determine that it is being run in an emulator, and change its behavior to further hide its core functionality. Still others will deploy malicious code in deeper stage payloads in an attempt to further bury its true behavior. However, this sample shows how a few simple steps can be taken to peel those layers back to eventually discover the control flow of a new class of malware. Moving forward, other samples in this class can be analyzed in much the same way to track changes in the ecosystem and how malware developers are responding to attempts to mitigate their effectiveness.

Analyzing malware and tracing its evolution is important for fighting back against it. Not only does it result in better signatures for anti-virus software to use and protect users, it helps us understand what protections are necessary on the operating-system level and guides platform security recommendations. Sometimes, it can lead to C&C servers being shut down and the targets of the botnets gaining some much-needed reprieve. And lastly, it gives users insight into what software is running on their devices so they can take control back.

Related Issues