小白也能看懂的插件化DroidPlugin原理(二)– 反射机制和Hook入门

  前言:在上一篇博文《小白也能看懂的插件化DroidPlugin原理(一)–
动态代理》
中详尽介绍了
DroidPlugin
原理中提到到的动态代理格局,看完上篇博文后你就会发现原本动态代理真的万分简单,只然而正是兑现3个InvocationHandler 接口重写一下 invoke 方法而已。不错,其实过多看似 high
level
的技术都并不曾设想中的那么晦涩难懂,只要您肯下定狠心去询问它,去认识它,去上学它你就会发现,原来都以足以学得懂的。本篇博文将介绍
DroidPlugin 框架中常用到的别的多少个知识点–反射机制和Hook技术。

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

一 、反射机制

  一 、反射是哪些?

  JAVA反射机制是在运作意况中,对于自由2个类,都能够精通那一个类的富有属性和格局;对于自由多少个指标,都可以调用它的私下方法和性质;那种动态获取新闻以及动态调用对象方法的功用称为java语言的反射机制。

  ② 、反射机制的职能:

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

  (2)反射能够在运营时协会任意二个类的目的;

  (3)反射可以在运维时判断任意3个类具有的任意成员变量和格局;

  (4)反射可以在运作时调用任意贰个类的轻易方法;

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

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

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

 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)通过反射获取 BeReflected 的Class类型,并将其开始化。(其中Logger 是楼主封装的三个日志打字与印刷类,无需在意这个细节)

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);

  输出如下:

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

  (2)通过反射访问私有方法和民用成员变量,并转移私有变量的值。大家都晓得,对于一个私人住房类型的变量,在没有提供公开的
set 之类方法的景况下,想更改它的值是一点都不大概的,可是使用反射就能够完结。

 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));

  输出如下:  

  [BeReflected] : I am
method1
  [ReflectTest] : field改变前的值:I am 田野同志1
  [ReflectTest] : 田野先生改变后的值:作者是 田野先生1 被改动后的值

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

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));

  输出如下:

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

  (4)通过反射访问三个带参数的点子。访问带参数的主意是,需求在
getDeclareMethod 前边传来一组参数的连串。

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

  输出如下:

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

   (5)通过反射获取类中享有的成员变量和方法。

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 }

  输出如下:

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

  看完上边多少个例证之后,你是否认为反射还真是神奇,能够做到很多用常规方法做不到的操作。当然上边只是示例了反光机制中最基本的一部分调用而已,感兴趣的情人能够自动查阅官方文书档案。废话不多说了,大家尽快起先介绍
Hook 技术。

二、Hook入门

  Hook 普通话释意是“钩子”,那二日楼主也直接在雕琢,Hook
到底指的是怎么样?怎么样才能用一种简易易懂,生动形象的诠释来提现 Hook
技术?以楼主近日对 Hook
的精晓,通俗来将正是通过某种手段对一件事物进行偷梁换柱,从而威胁目的来以达成控制目标的一坐一起的指标。从技术角度来说,正是替换原有的对象,拦截目的函数/方法,从而改变其本来面指标作为。

  在二月份初刚初始学习 Hook
技术时写了三个有关替换小车引擎的小例子,今日就把这一个事例贴出来吧。先说一下大约流程,首先我们会有贰个简易的小车类,小车类里面有个引擎的靶子,当然,小车外燃机都以有行业内部的(那里即为接口),为简易起见,我们这里的汽车引擎标准一时半刻只有二个最大速度的指标,后续大家会通过反射机制来替换掉汽车引擎以达成提升最大速度的目标。例子万分不难,通过那一个例子大家很容易就能初阶的精通Hook 技术。

  小车类代码如下:

 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 }

  可以观察,小车类里面有三个 carEngine
(小车发动机)的习性,小车引擎接口代码如下:

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

  汽车引擎类代码如下:

/**
 * 车引擎
 * Created by liuwei on 17/3/1.
 */
public class CarEngine implements CarEngineInterface {
    @Override
    public int maxSpeed() {
        return 60;
    }
}

  2个回顾的小汽车消除了,试跑一下:

1 public class Test {
2     public static void main(String[] args) {
3         Car car = new Car();
4         car.showMaxSpeed();
5     }
6 }

  输出结果:[Car] :
笔者卯足劲,玩命跑的最大速度能够实现:60

  额…好吧,卯足劲才能跑到60,那发动机速度有点….,作为二个飙车党,肯定无法忍,必须改装!

  在改装从前,大家必要先考察从哪个地方入手合适,能够看看,在 Car
类里面有个 CarEngine 的靶子,大家要求做的正是将以此 CarEngine
的指标替换来大家友好成立的引擎类,那一个引擎类必要有那和 CarEngine
一样的特性,也便是说须要达成 CarEngineInterface 接口只怕直接接轨
CarEngine ,然后拦截到 maxSpeed
方法并修改重回值。那么那里我们实在有二种方案,一种方案,能够重复成立叁个引擎类,让其后续
CarEngine 大概完成 CarEngineInterface 都行,然后经过反射来替换 Car
对象中的 carEngine 属性;另一种方案,写一个动态代理,让其对 CarEngine
进行代理,然后用反射替换。

  率先种方案:

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

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

   输出结果:

  [Test] :
——————替换前—————-
  [Car] :
笔者卯足劲,玩命跑的最大速度能够达到规定的标准:60
  [Test] :
——————替换后—————-
  [Car] :
笔者卯足劲,玩命跑的最大速度可以达到:180

  第①种方案:

  首先成立1个动态代理类,并截留 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 }

   同理,利用反射替换掉原来的小车内燃机

 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 }

  输出结果与方案一一致。

  写到那里,Hook 的主干用法也已经写完了,看完例子之后,或者你曾经对
Hook 有了2个中坚的认识,但值得一提的是,在 Test
类中的第八行代码中大家先是取出了 Car 中的 carEngine
对象,然后将此目的传入了它的替罪羊中,为何要这么做的,在替身中不传播
carEngine 也许另行 new 3个新的 CarEngine
不行啊?那是3个关键点,大家必要精通的是,那里大家只是想修改一下引擎的最大速度,而并不希望引擎的其余品质受到震慑,大家把从
Car 中取出原有的 carEngine
对象传入替身中,这样替身就足以只采用大家关心的主意开始展览修改,对于大家不想修改的章程直接调用传经来的
carEngine
对艺术即可。因为那里的事例为了容易起见没有添加任何的艺术和属性,所以这点亟需重点说圣元(Synutra)下。

三、小结

  其实 Hook 技术简单的话能够用替换、拦截来形容,并不曾使用新技巧。Hook
本人并简单,它的难关在于你在对一段代码 Hook 从前须求找出多少个适度的 Hook
点,相当于说分析出从哪入手很要紧,那就供给您对将要 Hook
的指标代码的实践流程非凡熟谙。本篇博文只是从头认识一下 Hook
技术,下一篇博文将会介绍怎么样通过 Hook 技术阻碍 Android 中 startActivity
方法,并在解析的经过中牵线如何才是适当的 Hook
点。感兴趣的朋友能够关注一下,敬请期待!

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