本篇博文将介绍,第①种运行Activity的方法进一步简易

  前言:在上一篇博文《小白也能看懂的插件化DroidPlugin原理(一)–
动态代理》
中详尽介绍了
DroidPlugin
原理中涉嫌到的动态代理方式,看完上篇博文后您就会发现原本动态代理真的卓殊容易,只不过正是贯彻1个InvocationHandler 接口重写一下 invoke 方法而已。不错,其实过多类似 high
level
的技巧都并不曾设想中的那么晦涩难懂,只要您肯下定狠心去领悟它,去认识它,去学习它你就会发现,原来都是足以学得懂的。本篇博文将介绍
DroidPlugin 框架中常用到的其它三个知识点–反射机制和Hook技术。

  前言:在前两篇小说中分别介绍了动态代理、反射机制和Hook机制,若是对这个还不太理解的童鞋建议先去参考一下前两篇小说。经过了前方两篇文章的铺垫,终于能够玩点真刀实弹的了,本篇将会经过
Hook 掉 startActivity 方法的3个小例子来介绍怎么样找出杰出的 Hook
切入点。 开头在此以前大家须求了然的少数正是,其实在 Android 里面运行二个Activity 能够经过二种办法贯彻,一种是我们常用的调用
Activity.startActivity 方法,一种是调用 Context.startActivity
方法,二种格局相比较,
第3种运维Activity的形式尤其简单,所以先以第1种为例。

  本体系小说的代码已经上传至github,下载地址:https://github.com/lgliuwei/DroidPluginStudy 本篇小说对应的代码在
com.liuwei.proxy_hook.reflect
和 com.liuwei.proxy_hook.hook.simplehook 包内。

  本连串作品的代码已经上传至github,下载地址:https://github.com/lgliuwei/DroidPluginStudy 本篇小说对应的代码在 com.liuwei.proxy_hook.hook.activityhook
包内,下载下来对照代码看小说效果会更好!

一 、反射机制

一、Hook 掉 Activity 的 startActivity
的方法

  一 、反射是怎样?

  在 Hook Activity 的 startActivity
方法在此之前,大家率先肯定一下大家的靶子,大家先通过追踪源码找出
startActivity
调用的的确起效果的主意,然后想办法把对象措施拦截掉,并出口大家的一条 Log
消息。

  JAVA反射机制是在运营境况中,对于自由一个类,都能够知道那几个类的拥有属性和方法;对于随意1个目的,都能够调用它的任性方法和质量;那种动态获取新闻以及动态调用对象方法的效应称为java语言的反光机制。

  我们先来一步步剖析 startActivity 的源码,随手写二个 startActivity
的示范,按住 command 键( windows 下按住 control )用鼠标点击
startActivity的方法即可跳转到方法里面。

  ② 、反射机制的职能:

  startActivity(Intent intent) 源码如下:

  (1)反射能够在运作时判断任意三个目标所属的类;

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

  (2)反射能够在运作时组织任意七个类的对象;

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

  (3)反射能够在运作时判断任意1个类具有的即兴成员变量和措施;

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 }

  (4)反射能够在运转时调用任意2个类的随机方法;

  从上一步传入的参数 options 为 null
我们就能够精晓这一步调用了 startActivityForResult(intent, -1) 的代码。

  (5)能够透过反射机制转变动态代理。

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

  三 、Talk is cheap,show me the code. 来一组反射机制小示例

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

  首先创制2个类供反射调用测试使用,临时将类名
BeReflected,并在类中添加八个成员变量、多个一般方法、三个静态变量,二个静态方法,具体代码如下:

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

 1 /**
 2  * 被反射测试的类
 3  * Created by liuwei on 17/4/2.
 4  */
 5 public class BeReflected {
 6     private String field1 = "I am field1";
 7     private String field2 = "I am field2";
 8     private static String staticField = "I am staticField";
 9     private void method1(){
10         Logger.i(BeReflected.class, "I am method1");
11     }
12     private void method1(String param) {
13         Logger.i(BeReflected.class, "I am method1--param = " + param);
14     }
15     private void method2(){
16         Logger.i(BeReflected.class, "I am method2");
17     }
18     public static void staticMethod(){
19         Logger.i(BeReflected.class, "I am staticMethod");
20     }
21 }
 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 }

  (1)通过反射获取 BeReflected 的Class类型,并将其初步化。(在那之中Logger 是楼主封装的一个日记打字与印刷类,无需在意这么些细节)

   到这一步我们早已观望了关键点,注意上边代码块中中灰的代码,其实
startActivity 真正调用的是 mInstrumentation.execStartActivity(…)
方法,mInstrumentation 是
Activity 的两个私有变量。接下来的职分将变得非凡不难,回想一下上一篇博文《小白也能看懂插件化DroidPlugin原理(二)–
反射机制和Hook入门》
中的方案一,在轮换小车外燃机时大家继续原来的汽车引擎类创制了八个新类,然后在新引擎类中截留了最大速度的点子,那里的思路是同等的,大家一贯新建二个后续
Instrumentation 的新类,然后重写 execStartActivity()
。对此有不明白的童鞋建议再看贰遍上一篇博文《小白也能看懂插件化DroidPlugin原理(二)–
反射机制和Hook入门》
。代码如下:

1 // 1、通过反射获取BeReflected所属的类
2 Class<?> beReflectedClass = Class.forName("com.liuwei.proxy_hook.reflect.BeReflected");
3 Logger.i(ReflectTest.class, beReflectedClass);
4 
5 // 2、通过反射创建实例化一个类
6 Object beReflected = beReflectedClass.newInstance();
7 Logger.i(ReflectTest.class, beReflected);
 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
变量,具体代码如下:

  [ReflectTest] : class
com.liuwei.proxy_hook.reflect.BeReflected
  [ReflectTest] :
com.liuwei.proxy_hook.reflect.BeReflected@7d4991ad

 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 }

  (2)通过反射访问私有方法和个人成员变量,并改变私有变量的值。我们都清楚,对于一个私有类型的变量,在尚未提供公开的
set 之类方法的状态下,想改变它的值是不容许的,不过利用反射就足以做到。

   那对于大家的话已经相当熟稔了,相当慢就写完了,然后大家在 Activity
的 onCreate() 方法中需求调用一下 doActivityStartHook 即可到位对
Activity.startActivity 的 hook。MainActivity 的代码如下:

 1 // 3、通过反射调用一个私有方法和成员变量
 2 Method method = beReflectedClass.getDeclaredMethod("method1");
 3 method.setAccessible(true);// 将此值设为true即可访问私有的方法和成员变量
 4 method.invoke(beReflected);// 访问普通成员变量和方法是需要在调用invoke方法是传入该类的对象
 5 
 6 Field field1 = beReflectedClass.getDeclaredField("field1");
 7 field1.setAccessible(true);
 8 Logger.i(ReflectTest.class, "field 改变前的值:" + field1.get(beReflected));
 9 field1.set(beReflected, "我是 field1 被改变后的值");
10 Logger.i(ReflectTest.class, "field 改变后的值:" + field1.get(beReflected));
 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:

  [BeReflected] : I am
method1
  [ReflectTest] : 田野先生改变前的值:I am 田野1
  [ReflectTest] : 田野同志改变后的值:小编是 田野同志1 被转移后的值

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

   (3)通过反射访问静态方法和静态变量。访问静态方法和变量时不要求传入所属类的对象,传入
null 即可访问。代码如下:

   到此结束我们已经 hook 了 Activity 的 startActivity
方法,分外不难,代码量也很少,但大家也很自由的意识那种方法必要在每二个Activity 的 onCreate 方法里面调用三遍 doActivityStartHook
方法,明显那不是二个好的方案,所以我们在摸索 hook
点时必定要留心尽量找一些在经过中保持不变或不易于被更改的变量,就像是单例和静态变量。

1 // 4、通过反射调用一个静态的方法和变量
2 Method staticMethod = beReflectedClass.getDeclaredMethod("staticMethod");
3 staticMethod.invoke(null);
4 
5 Field staticField = beReflectedClass.getDeclaredField("staticField");
6 staticField.setAccessible(true);
7 Logger.i(ReflectTest.class, staticField.get(null));

  难点1:在那里有少数值得一提,大家将
doActivityStartHook(…) 方法的调用要是放置  MainActivity
的 attachBaseContext(…) 方法中替换工作将不会卓有成效,为何?

  输出如下:

  调节和测试发现,大家在
attachBaseContext(..) 里面执行完成 doActivityStartHook(…) 方法后确实将
Activity 的 mInstrumentation 变量换到了我们自个儿的
EvilInstrumentation,但程序执行到 onCreate() 方法后就会发现此时候
mInstrumentation 变成了系统和谐的 Instrumentation
对象了。那时候大家得以确信的是 mInstrumentation 变量一定是在
attachBaseContext() 之后被开首化可能赋值的。带着那个目的大家很自在就在
Activity 源码的 attach() 方法中找到如下代码:

  [BeReflected] : I am
staticMethod
  [ReflectTest] : I am
staticField

  Activity.attach() 的源码如下(注意第⑩行和第一6行):

  (4)通过反射访问三个带参数的措施。访问带参数的措施是,须要在
getDeclareMethod 前面传来一组参数的类型。

 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 // 5、通过反射访问一个带参数的方法
2 Method method1 = beReflectedClass.getDeclaredMethod("method1", String.class);
3 method1.setAccessible(true);
4 method1.invoke(beReflected, "我是被传入的参数");

  至此,难点1算是找到了答案。

  输出如下:

二、Hook 掉 Context 的 startActivity
的方法

  [BeReflected] : I am
method1–param = 笔者是被盛传的参数

  文章开始大家就说 Android 中有个三种运行 Activity 的艺术,一种是
Activity.startActivity 另一种是
Context.startActivity,但须要专注的时,大家在使用 Context.startActivity
运转二个 Activity 的时候将 flags 内定为 FLAG_ACTIVITY_NEW_TASK。

   (5)通过反射获取类中有所的积极分子变量和章程。

  在接下去的解析中须求查阅 Android 源码,先引进多少个查看 Android
源码的网站:

1 // 6、遍历类中所有的方法和成员变量
2 for (Method tempMethod : beReflectedClass.getDeclaredMethods()) {
3     Logger.i(ReflectTest.class, tempMethod.getName());
4 }
5 for (Field tempField : beReflectedClass.getDeclaredFields()) {
6     Logger.i(ReflectTest.class, tempField.getName());
7 }

  http://androidxref.com

  输出如下:

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

  [ReflectTest] :
method2
  [ReflectTest] :
method1
  [ReflectTest] :
method1
  [ReflectTest] :
staticMethod
  [ReflectTest] :
field1
  [ReflectTest] :
field2
  [ReflectTest] :
staticField

  我们试着 hook 掉 Context.startActivity 方法,我们照旧随手写一个Context 格局运维 Activity 的演示,如下:

  看完上面多少个例子之后,你是否觉得反射还真是神奇,能够形成很多用健康情势做不到的操作。当然下面只是示例了反光机制中最宗旨的部分调用而已,感兴趣的爱人能够自行查阅官方文书档案。废话不多说了,大家神速开头介绍
Hook 技术。

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

二、Hook入门

   照着(一)中的姿势点入 startActivity() 方法里面,由于 Context
是二个抽象类,所以大家要求找到它的贯彻类才能来看实际的代码,通过查阅
Android 源码大家能够在 ActivityTread 中可见 Context 的完成类是
ContextImpl。(在此地大家先知道那点就行,具体的调用细节将会在下一篇博文中详尽介绍)

  Hook 中文释意是“钩子”,那二日楼主也一向在雕刻,Hook
到底指的是怎么?怎么着才能用一种简单易懂,生动形象的诠释来提现 Hook
技术?以楼主近日对 Hook
的明亮,通俗来将就是经过某种手段对一件东西举办偷梁换柱,从而威迫目的来以高达控制目的的行事的目的。从技术角度来说,就是替换原有的指标,拦截目的函数/方法,从而改变其原有的一坐一起。

  源码地址:

  在11月份初刚开端读书 Hook
技术时写了三个关于替换小车发动机的小例子,前几天就把这几个例子贴出来吧。先说一下大体流程,首先我们会有贰个不难易行的汽车类,小车类里面有个引擎的靶子,当然,小车内燃机都以有标准的(这里即为接口),为不难起见,我们那边的小车汽油发动机标准一时唯有3个最大速度的指标,后续大家会经过反射机制来替换掉汽车斯特林发动机以达到提升最大速度的目标。例子分外简单,通过那几个例子大家很不难就能起初的接头
Hook 技术。

  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 }
 1 /**
 2  * Created by liuwei on 17/3/1.
 3  */
 4 public class Car {
 5     private CarEngineInterface carEngine;
 6     public Car() {
 7         this.carEngine = new CarEngine();
 8     }
 9     public void showMaxSpeed(){
10         Logger.i(Car.class, "我卯足劲,玩命跑的最大速度可以达到:" + carEngine.maxSpeed());
11     }
12 }

    未来咱们来查看 ContextImpl.startActivity() 的源码。 

  能够见见,汽车类里面有多少个 carEngine
(小车引擎)的属性,小车内燃机接口代码如下:

  源码地址:

1 /**
2  * 车引擎接口
3  * Created by liuwei on 17/3/1.
4  */
5 public interface CarEngineInterface {
6     int maxSpeed();
7 }

  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 }
/**
 * 车引擎
 * Created by liuwei on 17/3/1.
 */
public class CarEngine implements CarEngineInterface {
    @Override
    public int maxSpeed() {
        return 60;
    }
}

  再进入 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 }
1 public class Test {
2     public static void main(String[] args) {
3         Car car = new Car();
4         car.showMaxSpeed();
5     }
6 }

   由地方第6行代码能够见见在代码中判断了 intent 的 flag
类型,要是非 FLAG_ACTIVITY_NEW_TASK
类型就会抛出特别。接着看深藕红部分的第①代码,可以见见先从 ActivityTread
中拿走到了 Instrumentation 最终还是调用了 Instrumentation 的
execStartActivity(…) 方法,我们前天内需做的正是分析 ActivityTread
类,并想方法用大家和好写的 EvilInstrumentation 类将 ActivityTread 的
mInstrumentation 替换掉。

  输出结果:[Car] :
作者卯足劲,玩命跑的最大速度能够达到规定的标准:60

  源码地址:

  额…好呢,卯足劲才能跑到60,那内燃机速度有点….,作为四个飙车党,肯定不能够忍,必须改装!

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

  在改装此前,大家供给先考察从哪儿出手合适,能够看到,在 Car
类里面有个 CarEngine 的靶子,大家须求做的就是将以此 CarEngine
的对象替换来我们温馨创办的引擎类,这一个引擎类需求有那和 CarEngine
一样的性状,也便是说需求完成 CarEngineInterface 接口可能间接接轨
CarEngine ,然后拦截到 maxSpeed
方法并修改重返值。那么那里大家实际有二种方案,一种方案,能够重复创制三个引擎类,让其后续
CarEngine 或然完毕 CarEngineInterface 都行,然后通过反射来替换 Car
对象中的 carEngine 属性;另一种方案,写2个动态代理,让其对 CarEngine
进行代理,然后用反射替换。

  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    }

  首先创立1个 EvilCarEngine 类, 详细代码如下:

  那里需求告诉大家是,ActivityTread
即代表选用的主线程,而1个利用中唯有3个主线程,并且由源码可见,ActivityTreadd
的对象又是以静态变量的款型存在的,太好了,那正是大家要找的 Hook
点。废话不多说了,以往大家只需利用反射通过 currentActivityThread()
方法获得 ActivityThread 的指标,然后在将 mInstrumentation 替换到EvilInstrumentation 即可,代码如下:

 1 /**
 2  * Created by liuwei on 17/3/1.
 3  */
 4 public class EvilCarEngine extends CarEngine {
 5     private CarEngineInterface base;
 6     public EvilCarEngine(CarEngineInterface base) {
 7         this.base = base;
 8     }
 9     public int maxSpeed() {
10         return 3 * base.maxSpeed();
11     }
12 }
 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 Test {
 2     public static void main(String[] args) {
 3         Car car = new Car();
 4         Logger.i(Test.class, "------------------替换前----------------");
 5         car.showMaxSpeed();
 6         // 怎样在不手动修改CarEngine类和Car类的情况下将大速度提高?
 7         try {
 8             Field carEngineField = Car.class.getDeclaredField("carEngine");
 9             carEngineField.setAccessible(true);
10             CarEngine carEngine = (CarEngine)carEngineField.get(car);
11             // 方法1
12             carEngineField.set(car, new EvilCarEngine(carEngine));
13         } catch (Exception e) {
14             e.printStackTrace();
15         }
16         Logger.i(Test.class, "------------------替换后----------------");
17         car.showMaxSpeed();
18     }
19 }
 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 输出如下:

  [Test] :
——————替换前—————-
  [Car] :
作者卯足劲,玩命跑的最大速度可以高达:60
  [Test] :
——————替换后—————-
  [Car] :
笔者卯足劲,玩命跑的最大速度能够完毕:180

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

  第2种方案:

   看到如此的 Log,表明大家早就打响的 Hook 了
Context.startActivity()。而且 doContextStartHook()
方法只在程序起初的时候调用1遍即可,前面在先后其余的 Activity 中调用
Context.startActivity() 时此拦截工作均可生效,这是因为
Context.startActivity() 在实践运转 Activity 的操作时调是因而ActivityTread 获取到 Instrumentation,然后再调用
Instrumentation.execStartActivity() 方法,而 ActivityTread
在先后中是以单例的情势存在的,那正是原因。所以说调用
doContextStartHook() 方法最佳的空子应该是放在 Application 中。

  首先创设一个动态代理类,并拦截 maxSpeed 方法,修改再次回到值。

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

 1 /**
 2  * Created by liuwei on 17/3/1.
 3  */
 4 public class CarEngineProxyHandler implements InvocationHandler {
 5     private Object object;
 6     public CarEngineProxyHandler(Object object) {
 7         this.object = object;
 8     }
 9     @Override
10     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
11         if ("maxSpeed".equals(method.getName())) {
12             Logger.i(CarEngineProxyHandler.class, "我是动态代理,我已拦截到 maxSpeed 方法,并偷偷返回了另一个值!");
13             return 180;
14         }
15         return method.invoke(object, args);
16     }
17 }

  将 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 } 
 1 public class Test {
 2     public static void main(String[] args) {
 3         Car car = new Car();
 4         Logger.i(Test.class, "------------------替换前----------------");
 5         car.showMaxSpeed();
 6         // 怎样在不手动修改CarEngine类和Car类的情况下将大速度提高?
 7         try {
 8             Field carEngineField = Car.class.getDeclaredField("carEngine");
 9             carEngineField.setAccessible(true);
10             CarEngine carEngine = (CarEngine)carEngineField.get(car);
11             // 方法2
12             CarEngineInterface carEngineProxy = (CarEngineInterface) Proxy.newProxyInstance(
13                     CarEngine.class.getClassLoader(), 
14                     new Class[]{CarEngineInterface.class}, 
15                     new CarEngineProxyHandler(carEngine));
16             carEngineField.set(car, carEngineProxy);
17 
18         } catch (Exception e) {
19             e.printStackTrace();
20         }
21 
22         Logger.i(Test.class, "------------------替换后----------------");
23         car.showMaxSpeed();
24     }
25 }

  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 }

  写到这里,Hook 的宗旨用法也曾经写完了,看完例子之后,或者你已经对
Hook 有了二个骨干的认识,但值得提的是,在 Test
类中的第⑦行代码中大家第贰取出了 Car 中的 carEngine
对象,然后将此指标传入了它的垫脚石中,为何要如此做的,在替身中不传播
carEngine 也许再一次 new 一个新的 CarEngine
不行呢?那是三个关键点,大家要求领会的是,这里我们只是想修改一下引擎的最大速度,而并不愿意引擎的另外属性受到震慑,大家把从
Car 中取出原有的 carEngine
对象传入替身中,那样替身就能够只采纳大家关怀的方法开展改动,对于大家不想修改的法子间接调用传经来的
carEngine
对章程即可。因为那边的事例为了简单起见没有拉长别的的点子和特性,所以这或多或少内需珍视说美赞臣(Meadjohnson)下。

   代码如上,布局文件相当的粗略就不贴出来了,就是三个按钮,3个测试
Activity.startActivity() 方法,一个测试 Context.startActivity()
方法,然后在 MainActivity 的 onCreate() 中调用了 doActivityStartHook()
在 MyApplication 里面调用了 doContextStartHook(),
近年来总的来说代码很平日,符合大家地点的思绪,但楼主在点击按钮发现 Log
输出如下:

三、小结

图片 1

  其实 Hook 技术不难的话能够用替换、拦截来描写,并从未采用新技巧。Hook
自个儿并简单,它的难处在于你在对一段代码 Hook 从前需求找出3个适当的 Hook
点,也正是说分析出从哪出手很关键,那就供给您对将要 Hook
的对象代码的实行流程万分熟习。本篇博文只是从头认识一下 Hook
技术,下一篇博文将会介绍怎么样通过 Hook 技术阻碍 Android 中 startActivity
方法,并在条分缕析的长河中牵线怎么样才是适合的 Hook
点。感兴趣的情人能够关心一下,敬请期待!

  是的,Activity.startActivity 被 hook 的新闻输出了一遍!为啥?

本文地址:http://www.cnblogs.com/codingblock/p/6642476.html

  我们不要紧先估计一下,一定是 Activity 的 mInstrumentation
对象在大家轮换从前就已经济体改成了 EvilInstrumentation, 然后大家又在
Activity.onCreate 方法调用了2回 doActivityStartHook(), 也便是大家又用
EvilInstrumentation 又重写了 EvilInstrumentation 的 startActivity()
方法,所以造成 log 音讯输出了一回。

 

  那难点又来了,为何 Activity 的 mInstrumentation
对象在大家轮换在此之前就已经成为了 EvilInstrumentation? 

  纵观代码,只有3个地点有问号,那就是大家放手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

相关文章