Android 安全之 Activity 劫持防禦

文本講解 Android 中 Activity 劫持防禦的具體方法,公司開發的的項目在安全檢查中出現 Activity 被劫持的問題。在網上有不少關於 Activity 劫持防禦方式實踐過都存在問題,本身完善了一些方法但願和你們一塊兒分享。android

什麼是 Activity 劫持

Android 爲了提升用戶的用戶體驗,對於不一樣的應用程序之間的切換,基本上是無縫。舉一個例子,用戶打開安卓手機上的某一應用例如支付寶,進入到登錄頁面,這時惡意軟件檢測到用戶的這一動做,當即彈出一個與支付寶界面相同的 Activity,覆蓋掉了合法的 Activity,用戶幾乎沒法察覺,該用戶接下來輸入用戶名和密碼的操做實際上是在惡意軟件的 Activity上進行的,接下來會發生什麼就可想而知了。具體關於 Activity 劫持原理能夠參考以下這篇文章:blog.csdn.net/nailsoul/ar…git

阿里聚安全 阿里聚安全旗下產品安全組件 SDK 具備安全簽名、安全加密、安全存儲、模擬器檢測、反調試、反注入、反 Activity 劫持等功能。 開發者只須要簡單集成安全組件 SDK 就能夠有效解決上述登陸窗口被木馬病毒劫持的問題,從而幫助用戶和企業減小損失。github

防禦手段

目前,尚未什麼專門針對 Activity 劫持的防禦方法,由於,這種攻擊是用戶層面上的,目前還沒法從代碼層面上根除。可是,咱們能夠適當地在 APP 中給用戶一些警示信息,提示用戶其登錄界面以被覆蓋。在網上查了不少解決方法以下:api

  • 在 Acitivity 的 onStop 方法中 調用封裝的 AntiHijackingUtil 類(檢測系統程序白名單)檢測程序是否被系統程序覆蓋。
  • 在前面創建的正常Activity的登錄界面(也就是 MainActivity)中重寫 onKeyDown 方法和 onPause 方法,判斷程序進入後臺是不是用戶自身形成的(觸摸返回鍵或 HOME 鍵)這樣一來,當其被覆蓋時,就可以彈出警示信息。

AntiHijackingUtil 類代碼以下:安全

/**
 * Description: Activity反劫持檢測工具
 * author: zs
 * Date: 2018/7/8 16:31
 */
public class AntiHijackingUtil {
    public static final String TAG = "AntiHijackingUtil";

    /**
     * 檢測當前Activity是否安全
     */
    public static boolean checkActivity(Context context) {
        PackageManager pm = context.getPackageManager();
        // 查詢全部已經安裝的應用程序
        List<ApplicationInfo> listAppcations =
                pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
        Collections.sort(listAppcations, new ApplicationInfo.DisplayNameComparator(pm));// 排序

        List<String> safePackages = new ArrayList<>();
        for (ApplicationInfo app : listAppcations) {// 這個排序必須有.
            if ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) {
                safePackages.add(app.packageName);
            }
        }
        // 獲得全部的系統程序包名放進白名單裏面.
        ActivityManager activityManager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        String runningActivityPackageName;
        int sdkVersion;
        try {
            sdkVersion = Integer.valueOf(android.os.Build.VERSION.SDK);
        } catch (NumberFormatException e) {
            sdkVersion = 0;
        }
        if (sdkVersion >= 21) {// 獲取系統api版本號,若是是5x系統就用這個方法獲取當前運行的包名
            runningActivityPackageName = getCurrentPkgName(context);
        } else {
            runningActivityPackageName =
                    activityManager.getRunningTasks(1).get(0).topActivity.getPackageName();
        }
        // 若是是4x及如下,用這個方法.
        if (runningActivityPackageName != null) {
            // 有些狀況下在5x的手機中可能獲取不到當前運行的包名,因此要非空判斷。
            if (runningActivityPackageName.equals(context.getPackageName())) {
                return true;
            }
            // 白名單比對
            for (String safePack : safePackages) {
                if (safePack.equals(runningActivityPackageName)) {
                    return true;
                }
            }
        }
        return false;
    }

    private static String getCurrentPkgName(Context context) {
        // 5x系統之後利用反射獲取當前棧頂activity的包名.
        ActivityManager.RunningAppProcessInfo currentInfo = null;
        Field field = null;
        int START_TASK_TO_FRONT = 2;
        String pkgName = null;
        try {
            // 經過反射獲取進程狀態字段.
            field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");
        } catch (Exception e) {
            e.printStackTrace();
        }
        ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List appList = am.getRunningAppProcesses();
        ActivityManager.RunningAppProcessInfo app;
        for (int i = 0; i < appList.size(); i++) {
            //ActivityManager.RunningAppProcessInfo app : appList
            app = (ActivityManager.RunningAppProcessInfo) appList.get(i);
            //表示前臺運行進程.
            if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                Integer state = null;
                try {
                    state = field.getInt(app);// 反射調用字段值的方法,獲取該進程的狀態.
                } catch (Exception e) {
                    e.printStackTrace();
                }
                // 根據這個判斷條件從前臺中獲取當前切換的進程對象
                if (state != null && state == START_TASK_TO_FRONT) {
                    currentInfo = app;
                    break;
                }
            }
        }
        if (currentInfo != null) {
            pkgName = currentInfo.processName;
        }
        return pkgName;
    }

    /**
     * 判斷當前是否在桌面
     *
     * @param context 上下文
     */
    public static boolean isHome(Context context) {
        ActivityManager mActivityManager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
        return getHomes(context).contains(rti.get(0).topActivity.getPackageName());
    }

    /**
     * 得到屬於桌面的應用的應用包名稱
     *
     * @return 返回包含全部包名的字符串列表
     */
    private static List<String> getHomes(Context context) {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = context.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo ri : resolveInfo) {
            names.add(ri.activityInfo.packageName);
        }
        return names;
    }

    /**
     * 判斷當前是否在鎖屏再解鎖狀態
     *
     * @param context 上下文
     */
    public static boolean isReflectScreen(Context context) {
        KeyguardManager mKeyguardManager =
                (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
        return mKeyguardManager.inKeyguardRestrictedInputMode();
    }
}

複製代碼

可是這兩種方法都存在問題,Android onKeyDown 方法目前根本沒法監聽到 HOME 鍵,大家能夠用代碼試一下,我這邊驗證過了。AntiHijackingUtil 類只檢測了系統程序,發如今鎖屏狀態下代碼沒法檢查也提示警告。效果以下在點擊 HOME 鍵和鎖屏在解鎖的狀況下依然提示應用警告。 bash

Activity 劫持防禦失敗

防禦方法改進

Activity 劫持防禦咱們想達到的預期目標以下:app

  • 用戶主動退出 APP ( 返回鍵 、HOME 鍵)這種狀況下咱們不須要給用戶彈出警告提示
  • APP 在鎖屏再解鎖的狀況下咱們不須要給用戶彈出警告提示
  • 其餘應用忽然覆蓋在咱們 APP 上時給出合理的警告提示

APP 返回桌面、鎖屏再解鎖狀況檢測代碼

/**
     * 判斷當前是否在桌面
     *
     * @param context 上下文
     */
    public static boolean isHome(Context context) {
        ActivityManager mActivityManager =
                (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
        List<ActivityManager.RunningTaskInfo> rti = mActivityManager.getRunningTasks(1);
        return getHomes(context).contains(rti.get(0).topActivity.getPackageName());
    }

    /**
     * 得到屬於桌面的應用的應用包名稱
     *
     * @return 返回包含全部包名的字符串列表
     */
    private static List<String> getHomes(Context context) {
        List<String> names = new ArrayList<String>();
        PackageManager packageManager = context.getPackageManager();
        Intent intent = new Intent(Intent.ACTION_MAIN);
        intent.addCategory(Intent.CATEGORY_HOME);
        List<ResolveInfo> resolveInfo = packageManager.queryIntentActivities(intent,
                PackageManager.MATCH_DEFAULT_ONLY);
        for (ResolveInfo ri : resolveInfo) {
            names.add(ri.activityInfo.packageName);
        }
        return names;
    }

    /**
     * 判斷當前是否在鎖屏再解鎖狀態
     *
     * @param context 上下文
     */
    public static boolean isReflectScreen(Context context) {
        KeyguardManager mKeyguardManager =
                (KeyguardManager) context.getSystemService(Context.KEYGUARD_SERVICE);
        return mKeyguardManager.inKeyguardRestrictedInputMode();
    }
複製代碼

而且在 onStop 方法中檢測是否須要彈出警告提醒ide

@Override
    protected void onStop() {
        super.onStop();
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 白名單
                boolean safe = AntiHijackingUtil.checkActivity(getApplicationContext());
                // 系統桌面
                boolean isHome = AntiHijackingUtil.isHome(getApplicationContext());
                // 鎖屏操做
                boolean isReflectScreen = AntiHijackingUtil.isReflectScreen(getApplicationContext());
                // 判斷程序是否當前顯示
                if (!safe && !isHome && !isReflectScreen) {
                    Looper.prepare();
                    Toast.makeText(getApplicationContext(), R.string.activity_safe_warning,
                            Toast.LENGTH_LONG).show();
                    Looper.loop();
                }
            }
        }).start();

    }
複製代碼

感受問題解決了,咱們經過點擊通知欄打開其餘應用來模擬惡意軟件覆蓋我們的 APP來看一下應用運行效果吧 工具

Activity 劫持防禦成功

代碼所有同步到 GitHub:github.com/christian-z…oop

相關文章
相關標籤/搜索