Android開發——程式鎖的實現(可用於開發釣魚登入介面)
1. 程式鎖原理
1.1 實現效果:
在使用者開啟一個應用時,若此應用是我們業務內的邏輯攔截目標,那就在開啟應用之後,彈出一個輸入密碼的介面,輸入密碼正確則進入目標應用。若不輸入直接按返回鍵,則直接返回桌面。
1.2 實現原理:
實時檢測棧頂Activity的包名,如果和我們預置的包名相符(可用SQLite資料庫對要進行匹配的包名進行資訊儲存),則新開一個Activity任務棧
2. 程式鎖實現
1. 程式碼實現比較簡單,獲取到topActivity的包名即可進行程式鎖的邏輯判斷。
注意,當判斷某個應用需要保護時,因為服務是沒有任務棧資訊的,所以在服務裡開啟Activity,需要指定這個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處理
(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畫素保活方案遺棄。