第二种运营Activity的格局进一步简易,1个APP进程只对应二个ActivityThread对象

  前言:在前两篇小说中分别介绍了动态代理、反射机制和Hook机制,如若对这一个还不太领悟的童鞋提出先去参考一下前两篇小说。经过了前面两篇小说的衬映,终于可以玩点真刀实弹的了,本篇将会由此Hook 掉 startActivity 方法的八个小例子来介绍如何找出极度的 Hook
切入点。 开头在此以前大家须求驾驭的1些便是,其实在 Android 里面运营三个Activity 能够由此三种格局贯彻,一种是我们常用的调用
Activity.startActivity 方法,一种是调用 Context.startActivity
方法,两种办法相比较,
第3种运转Activity的方法进一步简单,所以先以第贰种为例。

Hook Context.startActivity和Activity.startActivity

  • Context的startActivity其实具体是由ContextImpl去贯彻的,首先来看下framework的相关源码ContextImpl:

@Override
    public void startActivity(Intent intent) {
        warnIfCallingFromSystemProcess();
        startActivity(intent, null);
    }

@Override
    public void startActivity(Intent intent, Bundle options) {
        warnIfCallingFromSystemProcess();

        // Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
        // generally not allowed, except if the caller specifies the task id the activity should
        // be launched in.
        if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
                && options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
            throw new AndroidRuntimeException(
                    "Calling startActivity() from outside of an Activity "
                    + " context requires the FLAG_ACTIVITY_NEW_TASK flag."
                    + " Is this really what you want?");
        }
        mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);
    }

由上能够,最后是透过ActivityThread里面包车型客车mInstrumentation对象来执行execStartActivity,而ActivityThread是在Android
APP运维实施Java main方法的时候创立的ActivityThread:

public static void main(String[] args) {
        Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
        SamplingProfilerIntegration.start();

        // CloseGuard defaults to true and can be quite spammy.  We
        // disable it here, but selectively enable it later (via
        // StrictMode) on debug builds, but using DropBox, not logs.
        CloseGuard.setEnabled(false);

        Environment.initForCurrentUser();

        // Set the reporter for event logging in libcore
        EventLogger.setReporter(new EventLoggingReporter());

        // Make sure TrustedCertificateStore looks in the right place for CA certificates
        final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
        TrustedCertificateStore.setDefaultUserDirectory(configDir);

        Process.setArgV0("<pre-initialized>");

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }
}

上边的ActivityThread thread = new
ActivityThread();thread.attach(false);那两句正是开创了三个ActivityThread对象,再看attach方法,这里代码太多,只贴出有用的1些:

private void attach(boolean system) {
        sCurrentActivityThread = this;
        // 余下省略...
}

sCurrentActivityThread就是ActivityThread对象,三个应用软件进度只对应三个ActivityThread对象,同时这些目的可由Activity
本人提供的二个静态公有方法来获得到,那几个格局在应用层是不可能调用到的,可是能够动过反射method来获取到这几个法子并invoke,因为那是二个静态的不二法门:

private static volatile ActivityThread sCurrentActivityThread;

public static ActivityThread currentActivityThread() {
        return sCurrentActivityThread;
    }

自然,你也足以经过反射Field来得到到sCurrentActivityThread对象:

private static Object getActivityThread() {
        Object target = null;
        try {
            Class<?> activityThreadCls = Class.forName("android.app.ActivityThread");
            Method method = activityThreadCls.getDeclaredMethod("currentActivityThread");
            method.setAccessible(true);
//            Field sCurrentActivityThreadField = activityThreadCls.getDeclaredField("sCurrentActivityThread");
//            sCurrentActivityThreadField.setAccessible(true);
//            target = sCurrentActivityThreadField.get(null);
            target = method.invoke(null);
            return target;
        } catch (Exception e) {
            throw new RuntimeException(e.toString());
        }
    }

以往我们来合计一下,想要在startActivity在此之前做一些作业,然后在startActivity之后再做一些业务实在能够hook
startActivity
里面的execStartActivity这几个点,因为一个APP进度只对应三个ActivityThread,而且那么些目的依然static类型的,相比好通过反射直接获得到具体对象sCurrentActivityThreadField.get(null),那里传null即可,借使不是静态类型的目的,那反射的话那里还要填入具体的靶子,由于execStartActivity那么些主意是Instrumentation类里面包车型客车,而以此类的对象是ActivityThread里面包车型地铁1个全局变量:

mMainThread.getInstrumentation().execStartActivity(
                getOuterContext(), mMainThread.getApplicationThread(), null,
                (Activity) null, intent, -1, options);

您可因此反射方法来赢得那一个目的,也可通过反射Field来获取到这几个指标,然后把那么些指标设置成大家同心合力的代办对象,代理对象实际就是代理Instrumentation,那里用的是静态代理:

public class BaseInstrumentationProxy extends Instrumentation {

    private static final String TAG = "BaseInstrumentationProxy";

    private Instrumentation mBase;

    public final void setInstrumentation(Instrumentation mBase) {
        this.mBase = mBase;
    }

    protected void before() {

    }

    protected void after() {

    }

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        if (mBase == null) {
            throw new RuntimeException("mBase is null");
        }

        before();
        try {
            Method execStartActivity = Instrumentation.class.getDeclaredMethod(
                    "execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            execStartActivity.setAccessible(true);
            ActivityResult activityResult = (ActivityResult) execStartActivity.invoke(mBase, who, contextThread, token, target, intent, requestCode, options);
            after();
            return activityResult;
        } catch (Exception e) {
            throw new RuntimeException("no support hook:" + e.toString());
        }
    }
}

本条代理对象实际即是在execStartActivity此前做一些自定义的操作,然后在execStartActivity之后做一些自定义的操作,而它实际是透过反射来实行真正的Instrumentation对象的execStartActivity方法:

public static void hookStartActivityFormContext(BaseInstrumentationProxy baseInstrumentationProxy) {
        Object activityThread = InstrumentationHooker.getActivityThread();
        try {
            Field mInstrumentationField = activityThread.getClass().getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            baseInstrumentationProxy.setInstrumentation((Instrumentation) mInstrumentationField.get(activityThread));
            mInstrumentationField.set(activityThread, baseInstrumentationProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

此间注意一点,ContextImpl是调用ActivityThread对象里面包车型客车Instrumentation来执行execStartActivity,而Activity是调用小编的Instrumentation来执行execStartActivity,实际上Activity里面包车型地铁Instrumentation是在Activity创制的时候由ActivityThread对象里面包车型客车Instrumentation传进来的,Activity里面包车型地铁Instrumentation对象引用也是指向ActivityThread里面的Instrumentation对象,可是大家明日是把ActivityThread对象引用替换来我们自定义的代办对象,而Activity里面包车型大巴Instrumentation如故依旧原来的,并未有被轮换,所以一旦要Hook
Activity的startActivity是要把Activity里面包车型大巴Instrumentation对象设置成大家自定义的代办对象:

public static void hookStartActivityFormContext(BaseInstrumentationProxy baseInstrumentationProxy) {
        Object activityThread = InstrumentationHooker.getActivityThread();
        try {
            Field mInstrumentationField = activityThread.getClass().getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            baseInstrumentationProxy.setInstrumentation((Instrumentation) mInstrumentationField.get(activityThread));
            mInstrumentationField.set(activityThread, baseInstrumentationProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static void hookStartActivityFromActivity(Activity activity, BaseInstrumentationProxy baseInstrumentationProxy) {
        Field mInstrumentationField = null;
        try {
            mInstrumentationField =Activity.class.getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            baseInstrumentationProxy.setInstrumentation((Instrumentation) mInstrumentationField.get(activity));
            mInstrumentationField.set(activity, baseInstrumentationProxy);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

那样就hook成功了,然后在startActivity以前调用一下之上多少个主意就能够了:

public class MainActivity extends Activity {

    private static final String TAG = "MainActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        InstrumentationHooker.hookStartActivityFromActivity(this, new BaseInstrumentationProxy(){
            @Override
            protected void before() {
                Log.d(TAG, "hookStartActivityFromActivity before");
            }

            @Override
            protected void after() {
                Log.d(TAG, "hookStartActivityFromActivity after");
            }
        });

        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent);
    }
}

public class SecondActivity extends Activity {

    private static final String TAG = "SecondActivity";

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

        InstrumentationHooker.hookStartActivityFormContext(new BaseInstrumentationProxy(){
            @Override
            protected void before() {
                Log.d(TAG, "hookStartActivityFromActivity before");
            }

            @Override
            protected void after() {
                Log.d(TAG, "hookStartActivityFromActivity after");
            }
        });

        Intent intent = new Intent(this, ThirdActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getApplicationContext().startActivity(intent);
    }
}

谈起底运行的结果如下:

09-28 23:15:52.843 22148-22148/com.lhd.hooktest D/MainActivity: hookStartActivityFromActivity before
09-28 23:15:52.849 22148-22148/com.lhd.hooktest D/MainActivity: hookStartActivityFromActivity after

09-28 23:15:52.935 22148-22148/com.lhd.hooktest D/SecondActivity: hookStartActivityFormContext before
09-28 23:15:52.943 22148-22148/com.lhd.hooktest D/SecondActivity: hookStartActivityFormContext after

  本类别作品的代码已经上传至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, -壹) 的代码。

  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 的四个个体变量。接下来的职分将变得分外不难,回想一下上1篇博文《小白也能看懂插件化DroidPlugin原理(二)–
反射机制和Hook入门》
中的方案壹,在轮换汽车斯特林发动机时大家一连原来的汽车引擎类创制了1个新类,然后在新引擎类中截留了最大速度的格局,那里的思路是壹样的,大家间接新建贰个无冕Instrumentation 的新类,然后重写 execStartActivity()
。对此有不晓得的童鞋提出再看1次上一篇博文《小白也能看懂插件化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 }

   重写工作1度做完了,接着我们通过反射机制用新建的
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
方法,显明那不是1个好的方案,所以大家在寻找 hook
点时一定要留意尽量找壹些在经过中维系不变或不不难被转移的变量,就像是单例和静态变量。

  难题壹:在此间有某个值得1提,大家将
doActivityStartHook(…) 方法的调用借使放置  MainActivity
的 attachBaseContext(…) 方法中替换工作将不会生效,为啥?

  调节和测试发现,我们在
attachBaseContext(..) 里面执行达成 doActivityStartHook(…) 方法后当真将
Activity 的 mInstrumentation 变量换成了大家和衷共济的
EvilInstrumentation,但程序执行到 onCreate() 方法后就会发现此时候
mInstrumentation 变成了系统协调的 Instrumentation
对象了。那时候大家得以确信的是 mInstrumentation 变量一定是在
attachBaseContext() 之后被开头化或许赋值的。带着这么些指标我们很轻松就在
Activity 源码的 attach() 方法中找到如下代码:

  Activity.attach() 的源码如下(注意第7行和第3陆行):

 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 的主意,1种是
Activity.startActivity 另1种是
Context.startActivity,但必要专注的时,我们在运用 Context.startActivity
运维3个 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);

   照着(1)中的姿势点入 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
即代表选取的主线程,而1个行使中只有四个主线程,并且由源码可见,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() 方法,2个测试 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? 

  纵观代码,唯有3个地方有疑难,那便是大家松手MyApplication.attachBaseContext() 方法里面包车型客车 doContextStartHook()
起的职能!

  依旧先直接省略说一下真相的精神呢,结合上文所说,3个施用内只存在1个ActivityTread 对象,也只设有二个 Instrumentation
对象,那些 Instrumentation 是 ActivityTread 的成员变量,并在
ActivityTread 内做到起首化,在开发银行二个 Activity 的流程中山大学约在终极的职位
ActivityTread 会回调 Activity 的 attach() 方法,并将协调的
Instrumentation 对象传给 Activity。运行 Activity
的详尽流程及调用细节将会在下1篇博文介绍,敬请期待!

三、小结

  本篇小说通过拦截 Context.startActivity() 和 Activity.startActivity()
四个方法,将上1篇小说中牵线的 Hook 技术实施 Activity
的开发银行流程之中,同时通过这五个小例子先河询问了 Android
源码以及怎样去选定三个伏贴的 Hook 点。想要掌握插件化的基本原理,纯熟Activity 的启航流程是必备的,下1篇小说将会详细介绍 Activity
的开发银行流程,感兴趣的同窗能够关怀一下!

参照小说

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

  《Android应用程序的Activity运维进程大约介绍和读书安排》

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

相关文章