什么是 Activity 劫持
简单的说就是 APP 正常的 Activity 界面被恶意攻击者替换上仿冒的恶意 Activity 界面进行攻击和非法用途. 界面劫持攻击通常难被识别出来, 其造成的后果不仅会给用户带来严重损失, 更是移动应用开发者们的恶梦. 举个例子来说, 当用户打开安卓手机上的某一应用, 进入到登陆页面, 这时, 恶意软件侦测到用户的这一动作, 立即弹出一个与该应用界面相同的 Activity, 覆盖掉了合法的 Activity, 用户几乎无法察觉, 该用户接下来输入用户名和密码的操作其实是在恶意软件的 Activity 上进行的, 最终会发生什么就可想而知了.
Activity 界面被劫持的原因
很多网友发现, 如果在启动一个 Activity 时, 给它加入一个标志位 FLAG_ACTIVITY_NEW_TASK, 就能使它置于栈顶并立马呈现给用户. 针对这一操作, 假使这个 Activity 是用于盗号的伪装 Activity 呢? 在 Android 系统当中, 程序可以枚举当前运行的进程而不需要声明其他权限, 这样子我们就可以写一个程序, 启动一个后台的服务, 这个服务不断地扫描当前运行的进程, 当发现目标进程启动时, 就启动一个伪装的 Activity. 如果这个 Activity 是登录界面, 那么就可以从中获取用户的账号密码.
常见的攻击手段
监听系统 Logocat 日志, 一旦监听到发生 Activity 界面切换行为, 即进行攻击, 覆盖上假冒 Activity 界面实施欺骗. 开发者通常都知道, 系统的 Logcat 日志会由 ActivityManagerService 打印出包含了界面信息的日志文件, 恶意程序就是通过 Logocat 获取这些信息, 从而监控客户端的启动, Activity 界面的切换.
监听系统 API, 一旦恶意程序监听到相关界面的 API 组件调用, 即可发起攻击.
逆向 APK, 恶意攻击者通过反编译和逆向分析 APK, 了解应用的业务逻辑之后针对性的进行 Activity 界面劫持攻击
Activity 组件已知产生的安全问题
恶意盗取用户账号, 卡号, 密码等信息
利用假冒界面进行钓鱼欺诈
乌云网漏洞报告实例
android 利用悬浮窗口实现界面劫持钓鱼盗号 建设银行 android 客户端设计逻辑缺陷导致用户被钓鱼
研发人员该如何预防
针对用户
Android 手机均有一个 HOME 键 (即小房子的那个图标), 长按可以查看到近期任务. 用户在要输入密码进行登录时, 可以通过长按 HOME 键查看近期任务, 比如说登录微信时长按发现近期任务出现了微信, 那么我现在的这个登录界面就极有可能是一个恶意伪装的 Activity, 切换到另一个程序, 再查看近期任务, 就可以知道这个登录界面是来源于哪个程序了.
针对开发人员
研发人员通常的做法是, 在登录窗口或者用户隐私输入等关键 Activity 的 onPause 方法中检测最前端 Activity 应用是不是自身或者是系统应用, 如果发现恶意风险, 则给用户一些警示信息, 提示用户其登陆界面以被覆盖, 并给出覆盖正常 Activity 的类名.
下面参考网友分享, 给出一个研发人员常用的 activity 界面劫持防范措施代码:
首先, 在前正常的登录 Activity 界面中重写 onKeyDown 方法和 onPause 方法, 这样一来, 当其被覆盖时, 就能够弹出警示信息, 代码如下:
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// 判断程序进入后台是否是用户自身造成的 (触摸返回键或 HOME 键), 是则无需弹出警示.
if((keyCode==KeyEvent.KEYCODE_BACK || keyCode==KeyEvent.KEYCODE_HOME) && event.getRepeatCount()==0){
needAlarm = false;
}
return super.onKeyDown(keyCode, event);
}
@Override
protected void onPause() {
// 若程序进入后台不是用户自身造成的, 则需要弹出警示
if(needAlarm) {
// 弹出警示信息
Toast.makeText(getApplicationContext(), "您的登陆界面被覆盖, 请确认登陆环境是否安全", Toast.LENGTH_SHORT).show();
// 启动我们的 AlarmService, 用于给出覆盖了正常 Activity 的类名
Intent intent = new Intent(this, AlarmService.class);
startService(intent);
}
super.onPause();
}
然后实现 AlarmService.java, 并在在 AndroidManifest.xml 中注册
import android.app.ActivityManager;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.widget.Toast;
public class AlarmService extends Service{
boolean isStart = false;
Handler handler = new Handler();
Runnable alarmRunnable = new Runnable() {
@Override
public void run() {
// 得到 ActivityManager
ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
//getRunningTasks 会返回一个 List,List 的大小等于传入的参数.
//get(0) 可获得 List 中的第一个元素, 即栈顶的 task
ActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);
// 得到当前栈顶的类名, 按照需求, 也可以得到完整的类名和包名
String shortClassName = info.topActivity.getShortClassName(); // 类名
// 完整类名
//String className = info.topActivity.getClassName();
// 包名
//String packageName = info.topActivity.getPackageName();
Toast.makeText(getApplicationContext(), "当前运行的程序为"+shortClassName, Toast.LENGTH_LONG).show();
}
};
@Override
public int onStartCommand(Intent intent, int flag, int startId) {
super.onStartCommand(intent, flag, startId);
if(!isStart) {
isStart = true;
// 启动 alarmRunnable
handler.postDelayed(alarmRunnable, 1000);
stopSelf();
}
return START_STICKY;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
来源: https://www.cnblogs.com/ganchuanpu/p/8411257.html