文本講解 Android 中 Activity 劫持防護的具體方法,公司開發的的項目在安全檢查中出現 Activity 被劫持的問題。在網上有很多關於 Activity 劫持防護方式實踐過都存在問題,自己完善了一些方法希望和大家一起分享。
什麼是 Activity 劫持
Android 爲了提高用戶的用戶體驗,對於不同的應用程序之間的切換,基本上是無縫。舉一個例子,用戶打開安卓手機上的某一應用例如支付寶,進入到登陸頁面,這時惡意軟件檢測到用戶的這一動作,立即彈出一個與支付寶界面相同的 Activity,覆蓋掉了合法的 Activity,用戶幾乎無法察覺,該用戶接下來輸入用戶名和密碼的操作其實是在惡意軟件的 Activity上進行的,接下來會發生什麼就可想而知了。具體關於 Activity 劫持原理可以參考如下這篇文章:https://blog.csdn.net/nailsoul/article/details/11767243
阿里聚安全阿里聚安全旗下產品安全組件 SDK 具有安全簽名、安全加密、安全存儲、模擬器檢測、反調試、反注入、反 Activity 劫持等功能。開發者只需要簡單集成安全組件 SDK 就可以有效解決上述登錄窗口被木馬病毒劫持的問題,從而幫助用戶和企業減少損失。
防護手段
目前,還沒有什麼專門針對 Activity 劫持的防護方法,因爲,這種攻擊是用戶層面上的,目前還無法從代碼層面上根除。但是,我們可以適當地在 APP 中給用戶一些警示信息,提示用戶其登陸界面以被覆蓋。在網上查了很多解決方法如下:
在 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 鍵和鎖屏在解鎖的情況下依然提示應用警告。
防護方法改進
Activity 劫持防護我們想達到的預期目標如下:
用戶主動退出 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 方法中檢測是否需要彈出警告提醒
@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();
}
代碼全部同步到 GitHub:https://github.com/christian-zs/AntihijackUtil
作者:christian_zs
鏈接:https://juejin.im/post/5ef958a76fb9a07e876707ce
關注我獲取更多知識或者投稿