Last activity time inspection

      Technique summary
    Technique Last activity time inspection
    Against UI injections: Activity injections
    Limitations Android API ≥ 29 (Android 10+)
      Does not detect view injections
    Side effects Causes a false positive when a user manually switches apps
    Recommendations Recommended for use combined with other techniques and with reaction logic that accommodates for false positives

    It is possible to detect activity injections by examining the last active time of the running tasks corresponding to our application and the current time since boot. If the difference between the two of them is beyond a certain threshold, consider it as a detection.

    As shown in the diagram below, there are several cases in which an activity transits from running to on-paused state. The first case (a), is filtered out when the difference between the current time and the last active time is lower than the threshold. The (d) case can also be distinguished, since it implies passing by the on-stop state. However, it is not possible to discriminate between (b) and (c). Thus, there will be a false positive whenever the user changes to another app. The positive point is that no particular permission is required. Hence, LATI can be used as a fallback for RAI when the PACKAGE_USAGE_STATS permission has not been granted.

    lastactivitytimeinspection

    Transitions from RUNNING to PAUSED in the Activity life-cycle (source: Android Develop Guide)

    An example code that implements the check:

    ActivityManager activityManager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); if (activityManager != null) { ActivityManager.RunningTaskInfo foregroundTaskInfo = activityManager.getRunningTasks(1).get(0); String foregroundPackageName = foregroundTaskInfo.topActivity.getPackageName(); if (foregroundPackageName.equals(activity.getPackageName())) { // This will always be the case, as we cannot get information from other apps Object lastActiveTimeObj = null; if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { lastActiveTimeObj = ReflectionHelper.getFieldFromObjectAndClassName( foregroundTaskInfo, "android.app.TaskInfo", "lastActiveTime"); } else { // LATI does not work here return true; // to be treated at DetectionDispatcher // and show a warning indicating that // lati does not work for API level < 29 } long lastActiveTime = ((Long)lastActiveTimeObj).longValue(); long elapsedTimeSinceBoot = SystemClock.elapsedRealtime(); long timeDiff = elapsedTimeSinceBoot-lastActiveTime; if(timeDiff > LAST_ACTIVE_TIME_INSPECTION_DIFF_THRESHOLD) { result = true; } } }

    Note that this check relies on the android.app.TaskInfo class, which was introduced in the Android API level 29. Thus, this countermeasure only works from Android 10 (API level 29).

     

    Note

    False positives may occur if the user switches to a different app while the protected activity is on screen, because the launcher will appear as the most recent app. If the launcher and the settings apps are allow-listed, false positives can still happen if the phone has configured a different launcher by default.

    As the action on detection is a soft block (such as a warning message or bringing the obscured activity back on top), having a false positive does not entail severe consequences. We advise that the data related to positive checks is sent to the backend.

    Guardsquare

    Table of contents