1. 程式人生 > >Android開發——程式鎖的實現(可用於開發釣魚登入介面)

Android開發——程式鎖的實現(可用於開發釣魚登入介面)

1. 程式鎖原理

1.1 實現效果:

在使用者開啟一個應用時,若此應用是我們業務內的邏輯攔截目標,那就在開啟應用之後,彈出一個輸入密碼的介面,輸入密碼正確則進入目標應用。若不輸入直接按返回鍵,則直接返回桌面

1.2 實現原理:

實時檢測棧頂Activity的包名,如果和我們預置的包名相符(可用SQLite資料庫對要進行匹配的包名進行資訊儲存),則新開一個Activity任務棧

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畫素保活方案遺棄。