一、什么是页面劫持
界面劫持是指在Android系统中,恶意软件通过监控目标软件的运行,当检测到当前运行界面为某个被监控应用的特定界面时(一般为登录或支付界面),弹出伪造的钓鱼页面,从而诱导用户输入信息,最终窃取用户的隐私(恶意盗取用户账号、卡号、密码等信息),或者利用假冒界面进行钓鱼欺诈。
二、页面劫持常用攻击手段
(1)监听系统Logocat日志,一旦监听到发生Activity界面切换行为,即进行攻击,覆盖上假冒Activity界面实施欺骗;
(2)监听系统API,一旦恶意程序监听到相关界面的API组件调用,即可发起攻击;
(3)5.0以下机型枚举获取栈顶Activity,监控到目标Activity出现,即可发起攻击;
(4) 恶意启动Service监听目标应用,在切换到目标Activity时,弹出对话框劫持当前界面迷惑用户。
(5)通过截屏录制屏幕,分析用户行为,对应弹窗。(自己臆想的)
ps:目前来看,只有系统app才有权限获取所有log、顶栈信息,普通app只能获取自己的数据,并没有权限。(可能有其他方式,我没找到)
三、如何防范页面劫持
3.1 用户方面
1、在支付等关键使用环境,看到应用切换后台的相关提示,得注意下
2、在支付等关键使用环境,查看后台,最新记录有不明应用。
3.2 开发者方面
针对Activity类型劫持,在登录窗口或者用户隐私输入等关键Activity的onPause方法中检测最前端Activity应用是不是自身,如果不是,则提示。
针对弹窗对话框类型的劫持。可以对自身弹窗设置标志位,如果acitvity失去焦点,并没有onpause,检查标志位即可判断是否是他人应用覆盖,可以对用户提醒。
针对弹窗,自行实践即可,针对Activity,下面有个工具类
package com.demo.ck.listenerdemo;
import android.app.ActivityManager;
import android.app.KeyguardManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Looper;
import android.widget.Toast;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
/**
* Description: Activity反劫持检测工具
*/
public class ViewInterceptUtil {
public static void check(final Context context) {
new Thread(new Runnable() {
@Override
public void run() {
// 白名单
boolean safe = ViewInterceptUtil.checkActivity(context.getApplicationContext());
// 系统桌面
boolean isHome = ViewInterceptUtil.isHome(context.getApplicationContext());
// 锁屏操作
boolean isReflectScreen = ViewInterceptUtil.isReflectScreen(context.getApplicationContext());
// 判断程序是否当前显示
if (!safe && !isHome && !isReflectScreen) {
Looper.prepare();
Toast.makeText(context.getApplicationContext(), "注意-------",
Toast.LENGTH_LONG).show();
Looper.loop();
}
}
}).start();
}
/**
* 检测当前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);
// }
// }
String runningActivityPackageName = getCurrentPkgName(context);
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 != null ? am.getRunningAppProcesses() : null;
ActivityManager.RunningAppProcessInfo app;
for (int i = 0; i < (appList != null ? appList.size() : 0); i++) {
//ActivityManager.RunningAppProcessInfo app : appList
app = (ActivityManager.RunningAppProcessInfo) appList.get(i);
//表示前台运行进程.
if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
Integer state = null;
try {
state = field != null ? field.getInt(app) : 0;// 反射调用字段值的方法,获取该进程的状态.
} 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);
if (mKeyguardManager != null) {
return mKeyguardManager.isKeyguardLocked();
}
return false;
}
}
四、参考
https://dun.163.com/news/p/47d0c43eb1854bae91872edc656dbd9e
https://www.jianshu.com/p/d4677e837648