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

  前言:在上一篇博文《小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理》中详细介绍了 DroidPlugin 原理中涉及到的动态代理模式,看完上篇博文后你就会发现原来动态代理真的非常简单,只不过就是实现一个 InvocationHandler 接口重写一下 invoke 方法而已。不错,其实很多看似 high level 的技术都并没有想象中的那么晦涩难懂,只要你肯下定决心去了解它,去认识它,去学习它你就会发现,原来都是可以学得懂的。本篇博文将介绍 DroidPlugin 框架中常用到的另外两个知识点--反射机制和Hook技术。

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

一、反射机制

  1、反射是什么?

  JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

  2、反射机制的作用:

  (1)反射可以在运行时判断任意一个对象所属的类;

  (2)反射可以在运行时构造任意一个类的对象;

  (3)反射可以在运行时判断任意一个类拥有的任意成员变量和方法;

  (4)反射可以在运行时调用任意一个类的任意方法;

  (5)可以通过反射机制生成动态代理。

  3、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] : [email protected]

  (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 field1
  [ReflectTest] : field 改变后的值:我是 field1 被改变后的值

  (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 的理解,通俗来将就是通过某种手段对一件事物进行偷梁换柱,从而劫持目标来以达到控制目标的行为的目的。从技术角度来说,就是替换原有的对象,拦截目标函数/方法,从而改变其原有的行为。

  在3月份初刚开始学习 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;
    }
}

  一个简单的小汽车搞定了,试跑一下:

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 进行代理,然后用反射替换。

  第一种方案:

  首先创建一个 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

  第二种方案:

  首先创建一个动态代理类,并拦截 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 有了一个基本的认识,但值得一提的是,在 Test 类中的第10行代码中我们首先取出了 Car 中的 carEngine 对象,然后将此对象传入了它的替身中,为什么要这样做的,在替身中不传入 carEngine 或者重新 new 一个新的 CarEngine 不行吗?这是一个关键点,我们需要明白的是,这里我们只是想修改一下引擎的最大速度,而并不希望引擎的其他属性受到影响,我们把从 Car 中取出原有的 carEngine 对象传入替身中,这样替身就可以只选择我们关心的方法进行修改,对于我们不想修改的方法直接调用传经来的 carEngine 对方法即可。因为这里的例子为了简单起见没有添加其他的方法和属性,所以这一点需要着重说明一下。

三、小结

  其实 Hook 技术简单来说可以用替换、拦截来形容,并没有用到新技术。Hook 本身并不难,它的难点在于你在对一段代码 Hook 之前需要找出一个合适的 Hook 点,也就是说分析出从哪下手很关键,这就要求你对将要 Hook 的目标代码的执行流程非常熟悉。本篇博文只是初步认识一下 Hook 技术,下一篇博文将会介绍如何通过 Hook 技术拦截 Android 中 startActivity 方法,并在分析的过程中介绍哪些才是合适的 Hook 点。感兴趣的朋友可以关注一下,敬请期待!

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

时间: 2024-08-24 11:37:14

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

小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理

前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述.前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以决心花些时间研究了一下 DroidPlugin 插件框架的原理,以便再出现问题时也能从容应对.打开源码后发现尽是大把大把的 hook.binder.classloader 等等,很难摸清头绪,幸运的是,有很多热心的大神已经对 DroidPlugin 的原理进行了透彻的剖析,文末会有本人对参考文章的致

Android插件化探索(二)资源加载

前情提要 在探索资源加载方式之前,我们先来看看上一篇中没细讲的东西.还没看过的建议先看上一篇Android插件化探索(一)类加载器DexClassLoader. PathClassLoader和DexClassLoader的区别 DexClassLoader的源码如下: public class DexClassLoader extends BaseDexClassLoader { //支持从任何地方的apk/jar/dex中读取 public DexClassLoader(String dex

小白都能看懂的block

首先说明一下,我自己也是一个小白:这是我对block的一点认识或总结,有不对的地方,希望大家指出来 block就是一个代码块,用来执行一小段程序的, 通常我们定义一个block 可以用它的基本模型,返回值类型(^变量的名字)参数类型 例如: int (^myBlock)(int ); 这就是定义了一个block 这个变量的名字为myBlock 就像是 int i:(类比有利于理解) 当我们定义完block之后,我们应该实体化它,.就像是我们定义完int i: 我们要给int i 赋值, (其实实

小白也能看懂的 Laravel 核心概念讲解

自动依赖注入 什么是依赖注入,用大白话将通过类型提示的方式向函数传递参数. 实例 1 首先,定义一个类: /routes/web.php class Bar {} 假如我们在其他地方要使用到 Bar 提供的功能(服务),怎么办,直接传入参数即可: /routes/web.php Route::get('bar', function(Bar $bar) { dd($bar); }); 访问 /bar,显示 $bar 的实例: Bar {#272} 也就是说,我们不需要先对其进行实例!如果学过 PH

Unity发布Android新手教学 (小白都能看懂的教学 )

原文地址:http://blog.csdn.net/aries_h/article/details/51673338 最近在Unity的有些交流群里,发现好多Unity开发的爱好者们都遇到了这个问题. 而且都说在网上看到好多教程弄了好几天都弄不出来,每个人都解释一遍有觉得比较繁琐. 索性我就写一个博客永久保存.希望大家会喜欢. 本文纯属个人经验之谈,如有不足,欢迎指出. 下面进入正题 如果想要让Unity可以打包Apk,你需要先下载一个JDK7以上(包括7)的版本. 并且必须是64位. 安装时请

微信开发学习日记(八):7步看懂weiphp插件机制,核心目标是响应微信请求

又经过了几个小时的梳理.回顾,截至目前,终于对weiphp这个框架的机制搞明白了些.想要完全明白,自然还需要大把的时间.第1步:   配置微信公众号,http://weiphp.jiutianniao.com/ ... .html  从上面这个配置可以看出,微信请求呗weiphp的入口文件index.php接收了,可能会被/home/weixin/index/这个action响应.第2步:   index.php入口文件,校验了是否是微信请求.   /** * 微信接入验证 * 在入口进行验证而

Unity 打包发布Android新手教学 (小白都能看懂的教学 ) [转]

版权声明:本文为Aries原创文章,转载请标明出处.如有不足之处欢迎提出意见或建议,联系QQ531193915 扫码关注微信公众号,获取最新资源 最近在Unity的有些交流群里,发现好多Unity开发的爱好者们都遇到了这个问题. 而且都说在网上看到好多教程弄了好几天都弄不出来,每个人都解释一遍有觉得比较繁琐. 索性我就写一个博客永久保存.希望大家会喜欢. 本文纯属个人经验之谈,如有不足,欢迎指出. 下面进入正题 如果想要让Unity可以打包Apk,你需要先下载一个JDK7以上(包括7)的版本.

gitbook 入门教程之小白都能看懂的 Gitbook 插件开发全流程

什么是插件 Gitbook 插件是扩展 GitBook 功能(电子书和网站)的最佳方式. 只要是 Gitbook 默认没有提供的功能,基于插件机制都可以自行扩展,是插件让 Gitbook 变得更加强大. 本文将全面介绍插件的相关知识并重点介绍插件开发的全流程,只有熟悉插件开发流程才能做到有的放矢,心中有数,进而开发出自己的插件. 关于插件请参考 Gitbook 入门教程高级进阶系列文章,本文重点讲解开发 Gitbook 的基本流程. gitbook 入门教程之插件介绍 gitbook 入门教程之

小白都能看懂的Linux系统下安装配置Zabbix

实验环境: 操作系统:Centos 7.6 服务器ip:192.168.10.100 运行用户:root 网络环境:Internet Zabbix是一个基于web界面的提供分布式系统监控及网络功能的企业级的开源监控工具,做为一个企业运维人员来说,zabbix可以给企业和运维人员带来很大的帮助,zabbix是一个功能非常强大.跨平台.开源的企业级运维管理系统,由2部分构成:zabbix服务端和客户端(agentd),也可以通过系统自带的SNMP来采集数据. Zabbix可以部署在Windows.L