日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 运维知识 > Android >内容正文

Android

Android Activity防劫持方案

發布時間:2024/8/1 Android 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Android Activity防劫持方案 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

最近,安全合規部門又對金融類、銀行類app進行了大規模的多方面安全檢查,其中有一項安全問題:Activity劫持。其實Android界面防劫持我們app這邊也是做了的,但是為啥還會有這些問題呢?自我感覺就是絕不會有此類問題,于是我們向檢測部門要了劫持工具,但是事實往往是打臉的。。。。。

那么什么是Activity劫持呢?簡單的說就是我們APP正常的Activity界面被惡意攻替換上仿冒的惡意Activity界面進行攻擊和非法用途。界面劫持攻擊通常難被識別出來,其造成的后果必然會給用戶帶來嚴重損失。

舉個例子來說,當用戶打開安卓手機上的某一應用,進入到登陸頁面,這時,惡意軟件偵測到用戶的這一動作,立即彈出一個與該應用界面相同的Activity,覆蓋掉了合法的Activity,用戶幾乎無法察覺,該用戶接下來輸入用戶名和密碼的操作其實是在惡意軟件的Activity上進行的,最終會發生什么就可想而知了。

那么應該怎么防護呢?目前是還沒有什么專門針對 Activity 劫持的防護方法,因為,這種攻擊是用戶層面上的,現在還無法從代碼層面上根除。但是,我們可以適當地在 APP 中給用戶一些警示信息,如toast提示.“某某app正在后臺運行”。

前面說到公司app被檢測到Activity界面被劫持問題,其實我們項目也有代碼邏輯處理劫持問題,但是現在看來還是不夠完善的,項目中用的是監聽app生命周期方法去做的,在app處于后臺時,toast提醒用戶,用到了兩個庫: //Gooogle官方獲取App生命周期的監聽器implementation "android.arch.lifecycle:extensions:1.1.1"annotationProcessor "android.arch.lifecycle:compiler:1.1.1" 接著需要實現LifecycleObserver public class AppLifeCycleImpl implements LifecycleObserver {private final static String sDES = "MDroidS正在后臺運行,請注意了!";public AppLifeCycleImpl() {}@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)public void create() {}//App處于前臺可見狀態。@OnLifecycleEvent(Lifecycle.Event.ON_START)public void start() {UILog.e("AppLifeCycleImpl start");}//App重新進入前臺@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)public void resume() {UILog.e("AppLifeCycleImpl resume");}//此后App進入不可見狀態/后臺@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)public void pause() {UILog.e("AppLifeCycleImpl pause");}//App進入后臺或者熄滅屏幕@OnLifecycleEvent(Lifecycle.Event.ON_STOP)public void stop() {UILog.e("AppLifeCycleImpl stop");UIToast.showShort(sDES);}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)public void onDestroy() {UILog.e("AppLifecycleObserver onDestroy");} } 然后在自己的application中注冊監這個Observer ProcessLifecycleOwner.get().getLifecycle().addObserver(new AppLifeCycleImpl()); 在這里會有個問題,當我們申請權限時或在個別手機上這個玩意也會吐司,我真是吐了,所以在這個基礎上又做了一些操作,完善后的AppLifeCycleImpl: public class AppLifeCycleImpl implements LifecycleObserver {private final static String sDES = "MDroidS正在后臺運行,請注意了!";private boolean isBackground = false;public AppLifeCycleImpl() {}@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)public void create() {}//App處于前臺可見狀態。@OnLifecycleEvent(Lifecycle.Event.ON_START)public void start() {UILog.e("AppLifeCycleImpl start");}//App重新進入前臺@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)public void resume() {UILog.e("AppLifeCycleImpl resume");isBackground = false;}//此后App進入不可見狀態/后臺@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)public void pause() {// 放在pause中,加快顯示防劫持的提示信息UILog.e("AppLifeCycleImpl pause");// 增加appToBackground判斷,兼容部分手機誤判if (AppUtils.isAppForeground()) {return;}isBackground = true;UIToast.showShort(sDES);}//App進入后臺或者熄滅屏幕@OnLifecycleEvent(Lifecycle.Event.ON_STOP)public void stop() {UILog.e("AppLifeCycleImpl stop");if (!isBackground) {isBackground = true;UIToast.showShort(sDES);}}@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)public void onDestroy() {UILog.e("AppLifecycleObserver onDestroy");} } 其實就是加了一層判斷和一個isBackground 標識,其中isAppForeground()方法在uitlcodex庫里,引入即可: implementation'com.blankj:utilcodex:1.30.6' 然后就是這個玩意也被檢測出頁面劫持問題,其實大多數開發者想到的就是用這個去監聽app生命周期,畢竟比較簡單,但是現在這個不中用了,于是上網一搜尋找解決辦法,網上的辦法都是大同小異,多多少少都會有一些問題,最后吸取了一些精華整理出來的方案。

網上一些方案的需求又不符合我們項目邏輯,如:
package com.littlejerk.sample.util;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 java.lang.reflect.Field; import java.util.ArrayList; import java.util.Collections; import java.util.List;/*** @author : HHotHeart* @date : 2021/8/23 22:07* @desc : 描述*/ public class AntiHijackingUtil {public static final String TAG = "AntiHijackingUtil";/*** 檢測當前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);}}// 得到所有的系統程序包名放進白名單里面.ActivityManager activityManager =(ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);String runningActivityPackageName;int sdkVersion;try {sdkVersion = Integer.valueOf(android.os.Build.VERSION.SDK);} catch (NumberFormatException e) {sdkVersion = 0;}if (sdkVersion >= 21) {// 獲取系統api版本號,如果是5x系統就用這個方法獲取當前運行的包名runningActivityPackageName = getCurrentPkgName(context);} else {runningActivityPackageName =activityManager.getRunningTasks(1).get(0).topActivity.getPackageName();}// 如果是4x及以下,用這個方法.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.getRunningAppProcesses();ActivityManager.RunningAppProcessInfo app;for (int i = 0; i < appList.size(); i++) {//ActivityManager.RunningAppProcessInfo app : appListapp = (ActivityManager.RunningAppProcessInfo) appList.get(i);//表示前臺運行進程.if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {Integer state = null;try {state = field.getInt(app);// 反射調用字段值的方法,獲取該進程的狀態.} 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);return mKeyguardManager.inKeyguardRestrictedInputMode();} } 然后在Actiivty的onStop()方法調用: @Overrideprotected void onStop() {super.onStop();new Thread(new Runnable() {@Overridepublic void run() {// 白名單boolean safe = AntiHijackingUtil.checkActivity(getApplicationContext());// 系統桌面boolean isHome = AntiHijackingUtil.isHome(getApplicationContext());// 鎖屏操作boolean isReflectScreen = AntiHijackingUtil.isReflectScreen(getApplicationContext());// 判斷程序是否當前顯示if (!safe && !isHome && !isReflectScreen) {Looper.prepare();UIToast.showShort(AppLifeCycleImpl.sDES);Looper.loop();}}}).start();} 這個方案實現的情況:
  • 用戶主動退出 APP ( 返回鍵 、HOME 鍵)這種情況下我們不需要給用戶彈出警告提示
  • APP 在鎖屏再解鎖的情況下我們不需要給用戶彈出警告提示
  • 其他應用突然覆蓋在我們 APP 上時給出合理的警告提示
實際檢測中發現這個方案也檢測不到發生頁面劫持的情況,所以這個方法也是很雞肋,再加上其實現的需求和我們項目也不一樣。

其實相信大多數app對于進入后臺的行為都會toast一下,如果有其它應用的頁面覆蓋到我們的app,這時自己的app能夠及時感知到者行為并且及時通知用戶,這樣才是比較好地防范劫持問題。

接下來我們以Activity的生命周期作文章,我們都知道Activity的跳轉必然會涉及到生命周期的回調,如 A跳轉到B生命周期方法回調:
  • A頁面回調onPause();
  • B頁面回調onCreate()、onResume(),然后回調A的onStop(),如果B頁面是透明Activity,則不會回調A的onStop();
  • A頁面如果跳轉到其它app,則app內部肯定不會新建activity,即不會回調onCreate();
  • app內部activity之間的切換應該是流暢的(耗時會ANR),產生ANR情況大都是500ms后的了;
基于上述activity的回調和需求分析,我們可以設計這樣的方案:在 Activity 生命周期走到 onPause 時,延時發送一個事件,該事件會觸發一個 oast 提醒用戶已離開本應用。然后在 onCreate、onResume 中移除延時事件。

上面分析得也差不多了,總得來說有兩部分,一是發送延時通知和取消通知toast的工具類,二是監聽activity生命周期,然后在合適的周期回調方法中去發送和取消通知。

通知發送和取消工具類如下:
package com.littlejerk.sample.util;import android.app.Activity; import android.os.Handler; import android.os.Looper; import android.text.TextUtils;import com.littlejerk.library.manager.toast.UIToast;/*** @author : HHotHeart* @date : 2021/8/24 20:43* @desc : 界面防劫持工具類,通過延時通知的發送和取消實現*/ public class HijackingPrevent {public final static String sDES = "MDroidS正在后臺運行,請注意了!";/*** 退出APP的標識*/private boolean isExit = false;/*** 延時事件*/private Runnable runnable;/*** 延時事件發送和取消*/private Handler handler;/*** 創建單例*/private HijackingPrevent() {handler = new Handler(Looper.getMainLooper());runnable = new Runnable() {@Overridepublic void run() {if (isExit()) {isExit = false;UIToast.showShort(sDES);}}};}/*** 獲取單例** @return*/public static HijackingPrevent getInstance() {return Holder.S_HIJACKING_PROVENT;}/*** Holder初始化單例*/private static class Holder {private static final HijackingPrevent S_HIJACKING_PROVENT = new HijackingPrevent();}/*** 退出activity時,延時通知*/public synchronized void delayNotify(Activity activity) {// 不需要通知,則返回if (!isNeedNotify(activity)) {return;}setExit(true);// 先移除已有的handler.removeCallbacks(runnable);handler.postDelayed(runnable, 500);}/*** 進入當前app activity時,移除通知*/public synchronized void removeNotify() {if (isExit()) {setExit(false);handler.removeCallbacks(runnable);}}/*** 判斷是否需要通知Toast*/public synchronized boolean isNeedNotify(Activity activity) {if (activity == null) {return false;}String actName = activity.getClass().getName();if (TextUtils.isEmpty(actName)) {return false;}//除了申請權限的activity,其它都需要延時通知return !actName.contains("UtilsTransActivity");}/*** 是否退出app** @return*/public boolean isExit() {return isExit;}/*** 設置app退出與否標識** @param isExit*/public void setExit(boolean isExit) {this.isExit = isExit;} } 對于監聽activity生命周期方法,我們可以實現Application.ActivityLifecycleCallbacks接口,然后注冊這個回調,至于何時發送這個通知,什么時候取消通知,前面也說的比較清楚。 public class ActivityLifeCycleImpl implements Application.ActivityLifecycleCallbacks {private static final String TAG = "ActivityLifeCycleImpl";@Overridepublic void onActivityCreated(@NonNull Activity activity, @Nullable Bundle savedInstanceState) {UILog.e(TAG,"onActivityCreated" + activity.getClass().getName());// 移除通知HijackingPrevent.getInstance().removeNotify();}@Overridepublic void onActivityStarted(@NonNull Activity activity) {}@Overridepublic void onActivityResumed(@NonNull Activity activity) {UILog.e(TAG,"onActivityResumed");// 移除通知HijackingPrevent.getInstance().removeNotify();}@Overridepublic void onActivityPaused(@NonNull Activity activity) {UILog.e(TAG,"onActivityPaused");// 延時通知HijackingPrevent.getInstance().delayNotify(activity);}@Overridepublic void onActivityStopped(@NonNull Activity activity) {UILog.e(TAG,"onActivityStopped");}@Overridepublic void onActivitySaveInstanceState(@NonNull Activity activity, @NonNull Bundle outState) {}@Overridepublic void onActivityDestroyed(@NonNull Activity activity) {} } 在你的application的onCreate()方法中注冊: registerActivityLifecycleCallbacks(new ActivityLifeCycleImpl()); 其實到這里,方案實現的也差不多了,在這里我們可以觀察到HijackingPrevent中對UtilsTransActivity做了攔截處理,UtilsTransActivity是PermissionUtils權限申請的頁面,然后,在申請權限request()后調用取消通知的方法,如: PermissionUtils.permission(Manifest.permission.CAMERA).callback(new PermissionUtils.SingleCallback() {@Overridepublic void callback(boolean isAllGranted,@NonNull List<String> granted,@NonNull List<String> deniedForever,@NonNull List<String> denied) {}}).request();HijackingPrevent.getInstance().removeNotify(); 這樣做的目的就是為了在申請權限的時候不用toast提醒用戶的需求,具體看業務人員的要求。有時候一籌莫展時,出去沖浪一下,肯定會有意想不到的收獲,站在巨人肩膀上才能看得更遠。

最后的方案比較好地解決了合規檢查界面防劫持問題,自己也嘗試用這個劫持工具去測試其它銀行app,發現大多數地都沒有toast提示用戶app進入后臺。新時代農民工應學會思考,融會貫通。

APP合規檢查系列文章:
Android 組件導出風險及防范
Android 申請權限前簡單封裝彈框闡述申請理由工具類,應付app合規檢查

總結

以上是生活随笔為你收集整理的Android Activity防劫持方案的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。