Android开发——程序锁的实现(可用于开发钓鱼登录界面)

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/SEU_Calvin/article/details/51933690

1. 程序锁原理

1.1 实现效果:

在用户打开一个应用时,若此应用是我们业务内的逻辑拦截目标,那就在开启应用之后,弹出一个输入密码的界面,输入密码正确则进入目标应用。若不输入直接按返回键,则直接返回桌面

1.2 实现原理:

实时检测栈顶Activity的包名,如果和我们预置的包名相符(可用SQLite数据库对要进行匹配的包名进行信息存储),则新开一个Activity任务栈,将拦截画面置于用户面前。只有在用户输入密码,并且验证成功后,才“放行”。本文原创,转载请注明出处:http://blog.csdn.net/seu_calvin/article/details/51933690


2. 程序锁实现

1. 代码实现比较简单,获取到topActivity的包名即可进行程序锁的逻辑判断。

注意,当判断某个应用需要保护时,因为服务是没有任务栈信息的,所以在服务里开启Activity,需要指定这个Activity运行的任务栈。这里指定Flag为Intent.FLAG_ACTIVITY_NEW_TASK。这时候就会有一个Activity挡在被打开的应用前面,完成程序锁的功能。

当然,监听到用户开启QQ时,跳出一个我们“自定义”的登录界面,这就是所谓的钓鱼了,可以拿到用户的用户名和密码。实现起来还是比较简单的。不过需要申请这个权限<uses-permission android:name="android.permission.GET_TASKS" />

                    while (flag){
                    List<ActivityManager.RunningTaskInfo> runningTasks = am.getRunningTasks(1);
                    //最近操作的任务栈runningTasks.get(0)
                    //topActivity栈顶 baseActivity栈底
                    String packageName = runningTasks.get(0).topActivity.getPackageName();
                    if(protectPacknames.contains(packageName)){
                        //判断应用是否需要临时停止保护
                        if(packageName.equals(tempStopEnterPwPackageName)){
                        }else{ Intent intent = new Intent(getApplicationContext(),EnterAppLockPwActivity.class);
                            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
                            intent.putExtra("packageName",packageName);//用户在拦截界面展示被保护的应用信息
                            startActivity(intent);
                        }
                    }
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }


2. tempStopEnterPwPackageName的概念。

为了防止用户验证成功,进入被锁应用后,继续弹出登录验证界面,(因为我们做了死循环),那么就该在合适的时机跳出循环,在本例中肯定是当用户验证成功时,我们采用发送自定义广播的方式来解决,在服务里代码动态注册此广播的Broadcast Receiver,从而使该应用成为tempStopEnterPwPackageName,暂时处于不被锁的状态即可。自定义广播发送时,将该应用的包名放入Extra中进行传递即可。

                    Intent intent = new Intent();
                    intent.setAction("com.example.user.mobilesafe.tempstop");
                    intent.putExtra("packageName",packageName);
                    sendBroadcast(intent);
                    finish();


3.最后需要处理的是,在EnterAppLockPwActivity界面进行Back键的屏蔽操作,原因很简单,就不用多说了。这里实现Back键回桌面,因为不会执行OnDestroy方法,但是会执行onStop方法,因此我们把finish()放在onStop方法中实现。

    @Override
    public void onBackPressed() {
        Intent intent = new Intent();
        intent.setAction("android.intent.action.MAIN");
        intent.addCategory("android.intent.category.HOME");
        intent.addCategory("android.intent.category.DEFAULT");
        intent.addCategory("android.intent.category.MONKEY");
        startActivity(intent);
    }

    @Override
    protected void onStop() {
        super.onStop();
        finish();
    }

3. BUG处理

(1)由于任务栈的原因,有可能出现的BUG为,打开程序锁所在应用的其他界面A,按Home键,返回桌面,再打开被保护的应用,验证通过后,会进入A界面,这是因为我们没有给 EnterAppLockPwActivity指定一个新的任务栈可以通过指定Activity启动模式来解决该问题。

(2)再一个BUG是用户打开程序锁所在应用的其他界面A,按Home键返回桌面,再打开被保护的应用,在不输入密码直接按Back键返回桌面,长按Home键,弹出用户最近打开过的Activity列表。用户认为从该列表点开进入的就应该是应用,那么用户想再次打开程序锁应用时,很显然,会弹出我们的EnterAppLockPwActivity界面(因为这个界面本身就属于我们的程序,从Activity列表里看起来就像是程序锁本身这个应用),这就给用户带来了困扰针对上述两个BUG,可以通过屏蔽应用在Activity列表里显示来解决

<activity android:name=".EnterAppLockPwActivity"
android:launchMode="singleInstance"
android:excludeFromRecents="true"/>

(3)我们有时需要方便用户开启和关闭服务,而设置一个开关,为了防止开关显示开启但服务实际上没有Alive,无论什么时候需要展示这个开关设置界面时,我们都进行此服务“是死是活”的判断。再修改界面控件显示即可。

public class IsServiceAliveUtils {
	public static boolean isServiceRunning(Context context,String serviceName){
		ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
		List<RunningServiceInfo> infos =  am.getRunningServices(100);
		for(RunningServiceInfo info : infos){
			String name = info.service.getClassName();
			if(serviceName.equals(name)){
				return true;
			}
		}
		return false;
	}
}


4.  防护措施

如果自己的应用的登录界面被一个恶意应用的拦截Activity给劫持了,就会存在用户帐号密码被盗的风险。目前,还没有什么专门针对Activity劫持的防护方法,因为这种攻击是用户层面上的,目前还无法从代码层面上根除。但是我们可以合适的时机给用户一些警示信息,提示用户其登陆界面已经被覆盖,并且给出到底是被什么应用给覆盖了,在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();
    }
public class AlarmService extends Service{

    boolean isStart = false;
    Handler handler = new Handler();

    Runnable alarmRunnable = new Runnable() {
        @Override
        public void run() {
            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;
    }
}

5.  关于程序锁和1像素包活

由于部分应用采用1像素保护机制,在锁屏时开启1像素的Activity,使应用在锁屏期间保持较高的进程优先级,但是如果该应用被程序锁监控,则会出现解锁手机后出现输入密码的提示界面,给用户带来很大困扰,在我们现在的实际项目中就遇到了这个问题,因为有的手机,比如小米手机,系统自带了程序锁功能。最终将1像素保活方案遗弃。



猜你喜欢

转载自blog.csdn.net/SEU_Calvin/article/details/51933690