小白也克看懂的插件化DroidPlugin原理(三)– 如何阻止startActivity方法

  前言:当面前片篇稿子中分头介绍了动态代理、反射机制以及Hook机制,如果对这些还不太了解的童鞋建议先失参考一下前少首文章。经过了眼前两篇稿子的搭配,终于可以玩点真刀实弹的了,本篇将会通过
Hook 掉 startActivity 方法的一个稍稍例子来介绍如何寻找有相当的 Hook
切入点。 开始前我们用明白的一些便是,其实以 Android 里面启动一个
Activity 可以通过个别种植方法实现,一种是咱常常因此底调用
Activity.startActivity 方法,一栽是调用 Context.startActivity
方法,两种植方式相比,
第一种启动Activity的主意更为简易,所以先以率先种植乎条例。

  本系列文章的代码已经上传至github,下载地址:https://github.com/lgliuwei/DroidPluginStudy 本篇文章对应的代码在 com.liuwei.proxy_hook.hook.activityhook
包内,下载下来对照代码看文章效果会再次好!

一、Hook 掉 Activity 的 startActivity
的方法

  在 Hook Activity 的 startActivity
方法之前,我们首先明确一下咱的靶子,我们事先经追踪源码找有
startActivity
调用的审由作用的主意,然后想方法把对象措施阻碍截掉,并出口我们的一样漫长 Log
信息。

  我们先来一步步剖析 startActivity 的源码,随手写一个 startActivity
的言传身教,按停 command 键( windows 下按停 control )用鼠标点击
startActivity的不二法门即可跳反至点子中。

  startActivity(Intent intent) 源码如下:

1 public void startActivity(Intent intent) {
2         this.startActivity(intent, null);
3 }

  就看 this.startActivity(intent, null) 方法源码:

1 public void startActivity(Intent intent, @Nullable Bundle options) {
2         if (options != null) {
3             startActivityForResult(intent, -1, options);
4         } else {
5             // Note we want to go through this call for compatibility with
6             // applications that may have overridden the method.
7             startActivityForResult(intent, -1);
8         }
9 }

  从上等同步传的参数 options 为 null
我们虽可以知晓这无异于步调用了 startActivityForResult(intent, -1) 的代码。

  startActivityForResult(Intent intent, int requestCode) 源码如下:

1 public void startActivityForResult(@RequiresPermission Intent intent, int requestCode) {
2         startActivityForResult(intent, requestCode, null);
3 }

  startActivityForResult(Intent intent, int requestCode, Bundle
options) 源码如下:

 1 public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
 2             @Nullable Bundle options) {
 3         if (mParent == null) {
 4             options = transferSpringboardActivityOptions(options);
 5             Instrumentation.ActivityResult ar =
 6                 mInstrumentation.execStartActivity(
 7                     this, mMainThread.getApplicationThread(), mToken, this,
 8                     intent, requestCode, options);
 9             if (ar != null) {
10                 mMainThread.sendActivityResult(
11                     mToken, mEmbeddedID, requestCode, ar.getResultCode(),
12                     ar.getResultData());
13             }
14             if (requestCode >= 0) {
15                 // If this start is requesting a result, we can avoid making
16                 // the activity visible until the result is received.  Setting
17                 // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
18                 // activity hidden during this time, to avoid flickering.
19                 // This can only be done when a result is requested because
20                 // that guarantees we will get information back when the
21                 // activity is finished, no matter what happens to it.
22                 mStartedActivity = true;
23             }
24 
25             cancelInputsAndStartExitTransition(options);
26             // TODO Consider clearing/flushing other event sources and events for child windows.
27         } else {
28             if (options != null) {
29                 mParent.startActivityFromChild(this, intent, requestCode, options);
30             } else {
31                 // Note we want to go through this method for compatibility with
32                 // existing applications that may have overridden it.
33                 mParent.startActivityFromChild(this, intent, requestCode);
34             }
35         }
36 }

   到即同步我们已看到了要点,注意点代码块被革命的代码,其实
startActivity 真正调用的凡 mInstrumentation.execStartActivity(…)
方法,mInstrumentation 是
Activity 的一个私家变量。接下来的任务将移得非常简单,回忆一下直达等同篇博文《小白也能够看明白插件化DroidPlugin原理(二)–
反射机制以及Hook入门》未遭的方案一,在轮换汽车引擎时我们后续原来的汽车引擎类创建了一个新类,然后于初引擎类中梗阻了极其要命快之点子,这里的思绪是一样的,我们一直新建一个累
Instrumentation 的新类,然后又写 execStartActivity()
。对是产生未晓的童鞋建议再拘留一样任何上同一篇博文《小白也能看明白插件化DroidPlugin原理(二)–
反射机制和Hook入门》。代码如下:

 1 public class EvilInstrumentation extends Instrumentation {
 2     private Instrumentation instrumentation;
 3     public EvilInstrumentation(Instrumentation instrumentation) {
 4         this.instrumentation = instrumentation;
 5     }
 6     public ActivityResult execStartActivity(
 7             Context who, IBinder contextThread, IBinder token, Activity target,
 8             Intent intent, int requestCode, Bundle options) {
 9         Logger.i(EvilInstrumentation.class, "请注意! startActivity已经被hook了!");
10         try {
11             Method execStartActivity = Instrumentation.class.getDeclaredMethod("execStartActivity", Context.class,
12                     IBinder.class, IBinder.class, Activity.class,
13                     Intent.class, int.class, Bundle.class);
14             return (ActivityResult)execStartActivity.invoke(instrumentation, who, contextThread, token, target,
15                     intent, requestCode, options);
16         } catch (Exception e) {
17             e.printStackTrace();
18         }
19 
20         return null;
21     }
22 }

   重写工作已做得了了,接着我们由此反射机制用新建的
EvilInstrumentation 替换掉 Activity 的 mInstrumentation
变量,具体代码如下:

 1 public static void doActivityStartHook(Activity activity){
 2         try {
 3             Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation");
 4             mInstrumentationField.setAccessible(true);
 5             Instrumentation originalInstrumentation = (Instrumentation)mInstrumentationField.get(activity);
 6             mInstrumentationField.set(activity, new EvilInstrumentation(originalInstrumentation));
 7         } catch (Exception e) {
 8             e.printStackTrace();
 9         }
10 }

   这对我们的话已经老是如数家珍了,很快就写了了,然后我们当 Activity
的 onCreate() 方法中要调用一下 doActivityStartHook 即可形成对
Activity.startActivity 的 hook。MainActivity 的代码如下:

 1 public class MainActivity extends Activity {
 2     private Button btn_start_by_activity;
 3     @Override
 4     protected void onCreate(Bundle savedInstanceState) {
 5         super.onCreate(savedInstanceState);
 6         setContentView(R.layout.activity_main);
 7         // hook Activity.startActivity()的方法时不知道这行代码为什么放在attachBaseContext里面不行?
 8         // 调试发现,被hook的Instrumentation后来又会被替换掉原来的。
10         ActivityThreadHookHelper.doActivityStartHook(this);
11         btn_start_by_activity = (Button) findViewById(R.id.btn_start_by_activity);
12         btn_start_by_activity.setOnClickListener(new View.OnClickListener() {
13             @Override
14             public void onClick(View v) {
15                 Intent intent = new Intent(MainActivity.this, OtherActivity.class);
16                 startActivity(intent);
17             }
18         });
19     }
20 }

   程序运行之后,点击启动 Activity 的按钮将出口以下 Log:

   [EvilInstrumentation] :
请注意! startActivity已经被hook了!

   到之结束我们早已 hook 了 Activity 的 startActivity
方法,非常简单,代码量也特别少,但我们吧格外自由之发现这种措施要在列一个
Activity 的 onCreate 方法中调用一差 doActivityStartHook
方法,显然这不是一个吓的方案,所以我们于搜索 hook
点时得要是注意尽量找有当经过遭到维系无变换或未便于为转移的变量,就比如单例和静态变量。

  问题1:在此出某些值得一提,我们以
doActivityStartHook(…) 方法的调用如果放置  MainActivity
的 attachBaseContext(…) 方法被替换工作拿无会见生效,为什么?

  调试发现,我们以
attachBaseContext(..) 里面执行完毕 doActivityStartHook(…) 方法后当真以
Activity 的 mInstrumentation 变量换成了咱们和好的
EvilInstrumentation,但程序执行到 onCreate() 方法后即见面发觉这候
mInstrumentation 变成了系协调之 Instrumentation
对象了。这时候我们得确信的凡 mInstrumentation 变量一定是以
attachBaseContext() 之后吃初始化或者赋值的。带在这个目标我们特别自在就于
Activity 源码的 attach() 方法被找到如下代码:

  Activity.attach() 的源码如下(注意第8尽及第26行):

 1   final void attach(Context context, ActivityThread aThread,
 2             Instrumentation instr, IBinder token, int ident,
 3             Application application, Intent intent, ActivityInfo info,
 4             CharSequence title, Activity parent, String id,
 5             NonConfigurationInstances lastNonConfigurationInstances,
 6             Configuration config, String referrer, IVoiceInteractor voiceInteractor,
 7             Window window) {
 8         attachBaseContext(context);
 9 
10         mFragments.attachHost(null /*parent*/);
11 
12         mWindow = new PhoneWindow(this, window);
13         mWindow.setWindowControllerCallback(this);
14         mWindow.setCallback(this);
15         mWindow.setOnWindowDismissedCallback(this);
16         mWindow.getLayoutInflater().setPrivateFactory(this);
17         if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
18             mWindow.setSoftInputMode(info.softInputMode);
19         }
20         if (info.uiOptions != 0) {
21             mWindow.setUiOptions(info.uiOptions);
22         }
23         mUiThread = Thread.currentThread();
24 
25         mMainThread = aThread;
26         mInstrumentation = instr;
27         mToken = token;
28         mIdent = ident;
29         mApplication = application;
30         mIntent = intent;
31         mReferrer = referrer;
32         mComponent = intent.getComponent();
33         mActivityInfo = info;
34         mTitle = title;
35         mParent = parent;
36         mEmbeddedID = id;
37         mLastNonConfigurationInstances = lastNonConfigurationInstances;
38         if (voiceInteractor != null) {
39             if (lastNonConfigurationInstances != null) {
40                 mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
41             } else {
42                 mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
43                         Looper.myLooper());
44             }
45         }
46 
47         mWindow.setWindowManager(
48                 (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
49                 mToken, mComponent.flattenToString(),
50                 (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
51         if (mParent != null) {
52             mWindow.setContainer(mParent.getWindow());
53         }
54         mWindowManager = mWindow.getWindowManager();
55         mCurrentConfig = config;
56     }

  至此,问题1算凡找到了答案。

二、Hook 掉 Context 的 startActivity
的方法

  文章开始我们就算说 Android 中生个简单栽启动 Activity 的方式,一种植是
Activity.startActivity 另一样种植是
Context.startActivity,但得注意的经常,我们于使用 Context.startActivity
启动一个 Activity 的上以 flags 指定为 FLAG_ACTIVITY_NEW_TASK。

  于连下的分析面临需要查阅 Android 源码,先引进两只翻 Android
源码的网站:

  http://androidxref.com

  http://grepcode.com/project/repository.grepcode.com/java/ext/com.google.android/android/

  我们试试着 hook 掉 Context.startActivity 方法,我们还随手写一个
Context 方式启动 Activity 的以身作则,如下:

1 Intent intent = new Intent(MainActivity.this, OtherActivity.class);
2 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
3 getApplicationContext().startActivity(intent);

   照着(一)中之姿势点入 startActivity() 方法中,由于 Context
是一个抽象类,所以我们得找到其的落实类似才能够见到实际的代码,通过翻
Android 源码我们好于 ActivityTread 中不过知 Context 的贯彻类似是
ContextImpl。(在此大家先了解这同沾就是实行,具体的调用细节将会见于产一样首博文被详细介绍)

  源码地址:

  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/app/ActivityThread.java#2338

 1 private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
 2         ...
 3             if (activity != null) {
 4                 Context appContext = createBaseContextForActivity(r, activity);
 5                 CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
 6                 Configuration config = new Configuration(mCompatConfiguration);
 7         ...
 8 }
 9         ...
10 private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
11          ContextImpl appContext = ContextImpl.createActivityContext(this, r.packageInfo, r.token);
12          appContext.setOuterContext(activity);
13          Context baseContext = appContext;
14          ...
15 }

    现在我们来查看 ContextImpl.startActivity() 的源码。 

  源码地址:

  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/app/ContextImpl.java#ContextImpl.startActivity%28android.content.Intent%29

1 @Override
2 public void startActivity(Intent intent) {
3         warnIfCallingFromSystemProcess();
4         startActivity(intent, null);
5 }

  再进来 startActivity(intent, null) 查看源码如下:

 1 @Override
 2 public void startActivity(Intent intent, Bundle options) {
 3         warnIfCallingFromSystemProcess();
 4         if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) {
 5             throw new AndroidRuntimeException(
 6                     "Calling startActivity() from outside of an Activity "
 7                     + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
 8                     + " Is this really what you want?");
 9         }
10         mMainThread.getInstrumentation().execStartActivity(
11             getOuterContext(), mMainThread.getApplicationThread(), null,
12             (Activity)null, intent, -1, options);
13 }

   由点第四实施代码可以看出在代码中判断了 intent 的 flag
类型,如果非 FLAG_ACTIVITY_NEW_TASK
类型就会见废弃来非常。接着看红部分的严重性代码,可以视先由 ActivityTread
中得到到了 Instrumentation 最后要调用了 Instrumentation 的
execStartActivity(…) 方法,我们现欲举行的哪怕是分析 ActivityTread
类,并想艺术用我们团结写的 EvilInstrumentation 类将 ActivityTread 的
mInstrumentation 替换掉。

  源码地址:

  http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/5.1.1_r1/android/app/ActivityThread.java#ActivityThread.0sCurrentActivityThread

  ActivityTread 部分代码如下:

206     private static ActivityThread sCurrentActivityThread;
207     Instrumentation mInstrumentation;
...
1597    public static ActivityThread currentActivityThread() {
1598        return sCurrentActivityThread;
1599    }
...
1797    public Instrumentation getInstrumentation()
1798    {
1799        return mInstrumentation;
1800    }

  这里用报大家是,ActivityTread
即代表行使之主线程,而一个施用被只有发一个主线程,并且由源码可知,ActivityTreadd
的对象又是为静态变量的花样是的,太好了,这多亏我们要找的 Hook
点。废话不多说了,现在我们一味需要采用反射通过 currentActivityThread()
方法将到 ActivityThread 的对象,然后在以 mInstrumentation 替换成
EvilInstrumentation 即可,代码如下:

 1   public static void doContextStartHook(){
 2         try {
 3             Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
 4             Method currentActivityThreadMethod = activityThreadClass.getDeclaredMethod("currentActivityThread");
 5             Object activityThread = currentActivityThreadMethod.invoke(null);
 6 
 7             Field mInstrumentationField = activityThreadClass.getDeclaredField("mInstrumentation");
 8             mInstrumentationField.setAccessible(true);
 9             Instrumentation originalInstrumentation = (Instrumentation)mInstrumentationField.get(activityThread);
10             mInstrumentationField.set(activityThread, new EvilInstrumentation(originalInstrumentation));
11         } catch (Exception e) {
12             e.printStackTrace();
13         }
14     }

    其实代码也不难理解,跟 Hook Activity 的 startActivity()
方法是一个思路,只是 Hook 的触及不同而已。下面我们以 MainActivity
的 attachBaseContext() 方法被调用 doContextStartHook()
方法,并累加相关测试代码,具体代码如下:

 1 public class MainActivity extends Activity {
 2     private Button btn_start_by_context;
 3     @Override
 4     protected void onCreate(Bundle savedInstanceState) {
 5         super.onCreate(savedInstanceState);
 6         setContentView(R.layout.activity_main);
 7         btn_start_by_context = (Button) findViewById(R.id.btn_start_by_context);
 8         btn_start_by_context.setOnClickListener(new View.OnClickListener() {
 9             @Override
10             public void onClick(View v) {
11                 Intent intent = new Intent(MainActivity.this, OtherActivity.class);
12                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
13                 getApplicationContext().startActivity(intent);
14             }
15         });
16     }
17     @Override
18     protected void attachBaseContext(Context newBase) {
19         super.attachBaseContext(newBase);
20         ActivityThreadHookHelper.doContextStartHook();
21     }
22 }

   点击按钮后翻 Log 输出如下:

   [EvilInstrumentation]
: 请注意! startActivity已经被hook了!

   看这样的 Log,说明我们既成之 Hook 了
Context.startActivity()。而且 doContextStartHook()
方法就当程序开始之时节调用一次于即可,后面在先后外的 Activity 中调用
Context.startActivity() 时此拦截工作全只是生效,这是盖
Context.startActivity() 在履行启动 Activity 的操作时调是通过
ActivityTread 获取到 Instrumentation,然后再调用
Instrumentation.execStartActivity() 方法,而 ActivityTread
在次中凡坐单例的款式有的,这就是由。所以说调用
doContextStartHook() 方法极其好的机会该是身处 Application 中。

  注意!前方惊现彩蛋一朵!!

  将 doContextStartHook() 方法放入到了 MyApplication
的 attachBaseContext() 里面后,代码如下:

1 public class MyApplication extends Application {
2     @Override
3     protected void attachBaseContext(Context base) {
4         super.attachBaseContext(base);
5         ActivityThreadHookHelper.doContextStartHook();
6     }
7 } 

  MainActivity 的代码如下:

 1 public class MainActivity extends Activity {
 2     private final static String TAG = MainActivity.class.getSimpleName();
 3     private Button btn_start_by_activity;
 4     private Button btn_start_by_context;
 5     @Override
 6     protected void onCreate(Bundle savedInstanceState) {
 7         super.onCreate(savedInstanceState);
 8         setContentView(R.layout.activity_main);
 9         btn_start_by_activity = (Button) findViewById(R.id.btn_start_by_activity);
10         btn_start_by_context = (Button) findViewById(R.id.btn_start_by_context);
11         ActivityThreadHookHelper.doActivityStartHook(this);
12         btn_start_by_activity.setOnClickListener(new View.OnClickListener() {
13             @Override
14             public void onClick(View v) {
15                 Log.i(TAG, "onClick: Activity.startActivity()");
16                 Intent intent = new Intent(MainActivity.this, OtherActivity.class);
17                 startActivity(intent);
18             }
19         });
20 
21         btn_start_by_context.setOnClickListener(new View.OnClickListener() {
22             @Override
23             public void onClick(View v) {
24                 Log.i(TAG, "onClick: Context.startActivity()");
25                 Intent intent = new Intent(MainActivity.this, OtherActivity.class);
26                 intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
27                 getApplicationContext().startActivity(intent);
28             }
29         });
30     }
31 }

   代码如达到,布局文件充分简单就未贴出了,就是鲜单按钮,一个测试
Activity.startActivity() 方法,一个测试 Context.startActivity()
方法,然后在 MainActivity 的 onCreate() 中调用了 doActivityStartHook()
在 MyApplication 里面调用了 doContextStartHook(),
目前看来代码很正常,符合我们地方的思绪,但楼主在点击按钮发现 Log
输出如下:

图片 1

  是的,Activity.startActivity 被 hook 的音讯输出了少数不行!为什么?

  我们不妨先猜想一下,一定是 Activity 的 mInstrumentation
对象在我们轮换之前便既改成了 EvilInstrumentation, 然后我们以在
Activity.onCreate 方法调用了一如既往次于 doActivityStartHook(), 相当给我们同时因此
EvilInstrumentation 又还写了 EvilInstrumentation 的 startActivity()
方法,所以造成 log 信息输出了少次。

  那问题又来了,为什么 Activity 的 mInstrumentation
对象在咱们轮换之前就是已经成为了 EvilInstrumentation? 

  纵观代码,只出一个地方来疑点,那便是咱放开
MyApplication.attachBaseContext() 方法中的 doContextStartHook()
起的作用!

  还是事先直接省略说一下实际的面目吧,结合上文所说,一个下内仅在一个
ActivityTread 对象,也惟有设有一个 Instrumentation
对象,这个 Instrumentation 是 ActivityTread 的成员变量,并当
ActivityTread 内成功初始化,在起步一个 Activity 的流水线中盖在最后之岗位
ActivityTread 会回调 Activity 的 attach() 方法,并拿团结的
Instrumentation 对象传于 Activity。启动 Activity
的事无巨细流程以及调用细节将见面在生同样篇博文介绍,敬请期待!

三、小结

  本篇文章通过拦截 Context.startActivity() 和 Activity.startActivity()
两独点子,将直达亦然篇稿子被介绍的 Hook 技术实施 Activity
的起步流程中,同时经过这片独稍例子初步摸底了 Android
源码以及哪些去选定一个恰当的 Hook 点。想如果打听插件化的基本原理,熟悉
Activity 的起步流程是少不了的,下一致首文章以会晤详细介绍 Activity
的启航流程,感兴趣的同窗可以关心一下!

参考文章

  《Android插件化原理分析——Hook机制的动态代理》

  《Android应用程序的Activity启动过程简单介绍和学计划》

本文链接:http://www.cnblogs.com/codingblock/p/6666239.html