小白也能看懂的插件化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,并在类中添加两个成员变量、三个普通方法、一个静态变量,一个静态方法,具体代码如下:
- /**
- * 被反射测试的类
- * Created by liuwei on 17/4/2.
- */
- public class BeReflected {
- private String field1 = "I am field1";
- private String field2 = "I am field2";
- private static String staticField = "I am staticField";
- private void method1(){
- Logger.i(BeReflected.class, "I am method1");
- }
- private void method1(String param) {
- Logger.i(BeReflected.class, "I am method1--param = " + param);
- }
- private void method2(){
- Logger.i(BeReflected.class, "I am method2");
- }
- public static void staticMethod(){
- Logger.i(BeReflected.class, "I am staticMethod");
- }
- }
(1)通过反射获取 BeReflected 的Class类型,并将其初始化。(其中 Logger 是楼主封装的一个日志打印类,无需在意这些细节)
- // 1、通过反射获取BeReflected所属的类
- Class<?> beReflectedClass = Class.forName("com.liuwei.proxy_hook.reflect.BeReflected");
- Logger.i(ReflectTest.class, beReflectedClass);
- // 2、通过反射创建实例化一个类
- Object beReflected = beReflectedClass.newInstance();
- Logger.i(ReflectTest.class, beReflected);
输出如下:
[ReflectTest] : class com.liuwei.proxy_hook.reflect.BeReflected
[ReflectTest] : com.liuwei.proxy_hook.reflect.BeReflected@7d4991ad
(2)通过反射访问私有方法和私有成员变量,并改变私有变量的值。我们都知道,对于一个私有类型的变量,在没有提供公开的 set 之类方法的情况下,想更改它的值是不可能的,但是利用反射就可以做到。
- // 3、通过反射调用一个私有方法和成员变量
- Method method = beReflectedClass.getDeclaredMethod("method1");
- method.setAccessible(true);// 将此值设为true即可访问私有的方法和成员变量
- method.invoke(beReflected);// 访问普通成员变量和方法是需要在调用invoke方法是传入该类的对象
- Field field1 = beReflectedClass.getDeclaredField("field1");
- field1.setAccessible(true);
- Logger.i(ReflectTest.class, "field 改变前的值:" + field1.get(beReflected));
- field1.set(beReflected, "我是 field1 被改变后的值");
- Logger.i(ReflectTest.class, "field 改变后的值:" + field1.get(beReflected));
输出如下:
[BeReflected] : I am method1
[ReflectTest] : field 改变前的值:I am field1
[ReflectTest] : field 改变后的值:我是 field1 被改变后的值
(3)通过反射访问静态方法和静态变量。访问静态方法和变量时不需要传入所属类的对象,传入 null 即可访问。代码如下:
- // 4、通过反射调用一个静态的方法和变量
- Method staticMethod = beReflectedClass.getDeclaredMethod("staticMethod");
- staticMethod.invoke(null);
- Field staticField = beReflectedClass.getDeclaredField("staticField");
- staticField.setAccessible(true);
- Logger.i(ReflectTest.class, staticField.get(null));
输出如下:
[BeReflected] : I am staticMethod
[ReflectTest] : I am staticField
(4)通过反射访问一个带参数的方法。访问带参数的方法是,需要在 getDeclareMethod 后面传入一组参数的类型。
- // 5、通过反射访问一个带参数的方法
- Method method1 = beReflectedClass.getDeclaredMethod("method1", String.class);
- method1.setAccessible(true);
- method1.invoke(beReflected, "我是被传入的参数");
输出如下:
[BeReflected] : I am method1--param = 我是被传入的参数
(5)通过反射获取类中所有的成员变量和方法。
- // 6、遍历类中所有的方法和成员变量
- for (Method tempMethod : beReflectedClass.getDeclaredMethods()) {
- Logger.i(ReflectTest.class, tempMethod.getName());
- }
- for (Field tempField : beReflectedClass.getDeclaredFields()) {
- Logger.i(ReflectTest.class, tempField.getName());
- }
输出如下:
[ReflectTest] : method2
[ReflectTest] : method1
[ReflectTest] : method1
[ReflectTest] : staticMethod
[ReflectTest] : field1
[ReflectTest] : field2
[ReflectTest] : staticField
看完上面几个例子之后,你是不是觉得反射还真是神奇,可以做到很多用常规方法做不到的操作。当然上面只是示例了反射机制中最基本的一些调用而已,感兴趣的朋友可以自行查阅官方文档。废话不多说了,我们尽快开始介绍 Hook 技术。
二、Hook入门
Hook 中文释意是“钩子”,这两天楼主也一直在琢磨,Hook 到底指的是什么?如何才能用一种简单易懂,生动形象的解释来提现 Hook 技术?以楼主目前对 Hook 的理解,通俗来将就是通过某种手段对一件事物进行偷梁换柱,从而劫持目标来以达到控制目标的行为的目的。从技术角度来说,就是替换原有的对象,拦截目标函数/方法,从而改变其原有的行为。
在3月份初刚开始学习 Hook 技术时写了一个关于替换汽车引擎的小例子,今天就把这个例子贴出来吧。先说一下大体流程,首先我们会有一个简单的汽车类,汽车类里面有个引擎的对象,当然,汽车引擎都是有标准的(这里即为接口),为简单起见,我们这里的汽车引擎标准暂且只有一个最大速度的指标,后续我们会通过反射机制来替换掉汽车引擎以达到提高最大速度的目的。例子非常简单,通过这个例子我们很容易就能初步的理解 Hook 技术。
汽车类代码如下:
- /**
- * Created by liuwei on 17/3/1.
- */
- public class Car {
- private CarEngineInterface carEngine;
- public Car() {
- this.carEngine = new CarEngine();
- }
- public void showMaxSpeed(){
- Logger.i(Car.class, "我卯足劲,玩命跑的最大速度可以达到:" + carEngine.maxSpeed());
- }
- }
可以看到,汽车类里面有一个 carEngine (汽车引擎)的属性,汽车引擎接口代码如下:
- /**
- * 车引擎接口
- * Created by liuwei on 17/3/1.
- */
- public interface CarEngineInterface {
- int maxSpeed();
- }
汽车引擎类代码如下:
- /**
- * 车引擎
- * Created by liuwei on 17/3/1.
- */
- public class CarEngine implements CarEngineInterface {
- @Override
- public int maxSpeed() {
- return 60;
- }
- }
一个简单的小汽车搞定了,试跑一下:
- public class Test {
- public static void main(String[] args) {
- Car car = new Car();
- car.showMaxSpeed();
- }
- }
输出结果:[Car] : 我卯足劲,玩命跑的最大速度可以达到:60
额...好吧,卯足劲才能跑到60,这发动机速度有点....,作为一个飙车党,肯定不能忍,必须改装!
在改装之前,我们需要先观察从哪里下手合适,可以看到,在 Car 类里面有个 CarEngine 的对象,我们需要做的就是将这个 CarEngine 的对象替换成我们自己创建的引擎类,这个引擎类需要有这和 CarEngine 一样的特征,也就是说需要实现 CarEngineInterface 接口或者直接继承 CarEngine ,然后拦截到 maxSpeed 方法并修改返回值。那么这里我们其实有两种方案,一种方案,可以重新创建一个引擎类,让其继承 CarEngine 或者实现 CarEngineInterface 都行,然后通过反射来替换 Car 对象中的 carEngine 属性;另一种方案,写一个动态代理,让其对 CarEngine 进行代理,然后用反射替换。
第一种方案:
首先创建一个 EvilCarEngine 类, 详细代码如下:
- /**
- * Created by liuwei on 17/3/1.
- */
- public class EvilCarEngine extends CarEngine {
- private CarEngineInterface base;
- public EvilCarEngine(CarEngineInterface base) {
- this.base = base;
- }
- public int maxSpeed() {
- return 3 * base.maxSpeed();
- }
- }
然后用反射机制替换掉原来的汽车引擎。
- public class Test {
- public static void main(String[] args) {
- Car car = new Car();
- Logger.i(Test.class, "------------------替换前----------------");
- car.showMaxSpeed();
- // 怎样在不手动修改CarEngine类和Car类的情况下将大速度提高?
- try {
- Field carEngineField = Car.class.getDeclaredField("carEngine");
- carEngineField.setAccessible(true);
- CarEngine carEngine = (CarEngine)carEngineField.get(car);
- // 方法1
- carEngineField.set(car, new EvilCarEngine(carEngine));
- } catch (Exception e) {
- e.printStackTrace();
- }
- Logger.i(Test.class, "------------------替换后----------------");
- car.showMaxSpeed();
- }
- }
输出结果:
[Test] : ------------------替换前----------------
[Car] : 我卯足劲,玩命跑的最大速度可以达到:60
[Test] : ------------------替换后----------------
[Car] : 我卯足劲,玩命跑的最大速度可以达到:180
第二种方案:
首先创建一个动态代理类,并拦截 maxSpeed 方法,修改返回值。
- /**
- * Created by liuwei on 17/3/1.
- */
- public class CarEngineProxyHandler implements InvocationHandler {
- private Object object;
- public CarEngineProxyHandler(Object object) {
- this.object = object;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if ("maxSpeed".equals(method.getName())) {
- Logger.i(CarEngineProxyHandler.class, "我是动态代理,我已拦截到 maxSpeed 方法,并偷偷返回了另一个值!");
- return 180;
- }
- return method.invoke(object, args);
- }
- }
同理,利用反射替换掉原来的汽车引擎
- public class Test {
- public static void main(String[] args) {
- Car car = new Car();
- Logger.i(Test.class, "------------------替换前----------------");
- car.showMaxSpeed();
- // 怎样在不手动修改CarEngine类和Car类的情况下将大速度提高?
- try {
- Field carEngineField = Car.class.getDeclaredField("carEngine");
- carEngineField.setAccessible(true);
- CarEngine carEngine = (CarEngine)carEngineField.get(car);
- // 方法2
- CarEngineInterface carEngineProxy = (CarEngineInterface) Proxy.newProxyInstance(
- CarEngine.class.getClassLoader(),
- new Class[]{CarEngineInterface.class},
- new CarEngineProxyHandler(carEngine));
- carEngineField.set(car, carEngineProxy);
- } catch (Exception e) {
- e.printStackTrace();
- }
- Logger.i(Test.class, "------------------替换后----------------");
- car.showMaxSpeed();
- }
- }
输出结果与方案一一致。
写到这里,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
小白也能看懂的插件化DroidPlugin原理(二)-- 反射机制和Hook入门的更多相关文章
- 小白也能看懂的插件化DroidPlugin原理(三)-- 如何拦截startActivity方法
前言:在前两篇文章中分别介绍了动态代理.反射机制和Hook机制,如果对这些还不太了解的童鞋建议先去参考一下前两篇文章.经过了前面两篇文章的铺垫,终于可以玩点真刀实弹的了,本篇将会通过 Hook 掉 s ...
- 小白也能看懂的插件化DroidPlugin原理(一)-- 动态代理
前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述.前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以 ...
- 小白也能看懂插件化DroidPlugin原理(一)-- 动态代理
前言:插件化在Android开发中的优点不言而喻,也有很多文章介绍插件化的优势,所以在此不再赘述.前一阵子在项目中用到 DroidPlugin 插件框架 ,近期准备投入生产环境时出现了一些小问题,所以 ...
- 小白也能看懂的Redis教学基础篇——朋友面试被Skiplist跳跃表拦住了
各位看官大大们,双节快乐 !!! 这是本系列博客的第二篇,主要讲的是Redis基础数据结构中ZSet(有序集合)底层实现之一的Skiplist跳跃表. 不知道那些是Redis基础数据结构的看官们,可以 ...
- 【vscode高级玩家】Visual Studio Code❤️安装教程(最新版🎉教程小白也能看懂!)
目录 如果您在浏览过程中发现文章内容有误,请点此链接查看该文章的完整纯净版 下载 Linux Mac OS 安装 运行安装程序 同意使用协议 选择附加任务 准备安装 开始安装 安装完成 如果您在浏览过 ...
- 搭建分布式事务组件 seata 的Server 端和Client 端详解(小白都能看懂)
一,server 端的存储模式为:Server 端 存 储 模 式 (store-mode) 支 持 三 种 : file: ( 默 认 ) 单 机 模 式 , 全 局 事 务 会 话 信 息 内 存 ...
- 小白也能看懂的Redis教学基础篇——做一个时间窗限流就是这么简单
不知道ZSet(有序集合)的看官们,可以翻阅我的上一篇文章: 小白也能看懂的REDIS教学基础篇--朋友面试被SKIPLIST跳跃表拦住了 书接上回,话说我朋友小A童鞋,终于面世通过加入了一家公司.这 ...
- Android插件化技术——原理篇
<Android插件化技术——原理篇> 转载:https://mp.weixin.qq.com/s/Uwr6Rimc7Gpnq4wMFZSAag?utm_source=androi ...
- 小白都能看懂的tcp三次握手
众所周知,TCP在建立连接时需要经过三次握手.许多初学者经常对这个过程感到混乱:SYN是干什么的,怎么一会儿是1一会儿是0?怎么既有大写的ACK又有小写的ack?为什么ACK在第二次握手才开始出现?初 ...
随机推荐
- win10下面visual studio, sublime ctrl+shift+f快捷键失效的原因
在visual studio 和sublime中,经常遇到ctrl+shift+f不能用,然后输入法总是自动切换成繁体中文. 实在百思不得其解. 今天才发现,我用的windows 10自带的微软拼音输 ...
- Redis 集群搭建详细指南
先有鸡还是先有蛋? 最近有朋友问了一个问题,说毕业后去大城市还是小城市?去大公司还是小公司?我的回答都是大城市!大公司! 为什么这么说呢,你想一下,无论女孩男孩找朋友都喜欢找个子高胸大的.同样的道理嘛 ...
- MySQL优化之表结构优化的5大建议(数据类型选择讲的很好)
殊不知,在N年前被奉为"圣经"的数据库设计3范式早就已经不完全适用了.这里我整理了一些比较常见的数据库表结构设计方面的优化技巧,希望对大家有用. 由于MySQL数据库是基于行(Ro ...
- spring非controller类获取service方法
ApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml"); pushMessageServ ...
- 懵懂oracle之存储过程2
上篇<懵懂oracle之存储过程>已经给大家介绍了很多关于开发存储过程相关的基础知识,笔者尽最大的努力总结了所有接触到的关于存储过程的知识,分享给大家和大家一起学习进步.本篇文章既是完成上 ...
- 在ASP.Net MVC 中如何实现跨越Session的分布式TempData
Hi,guys!Long time no see! 1.问题的引出 我相信大家在项目中都使用过TempData,TempData是一个字典集合,一般用于两个请求之间临时缓存数据或者页面之间传递消息.也 ...
- windows端口占用处理工具
一.描述 笔者在最近使用tomcat时,老是会遇到这种端口占用的问题,便写了这个小的exe,用于解决windows下的端口占用问题. 好吧,其实是我实在记不住CMD下的那几行命令.这玩意的实现比较简单 ...
- zend framework 1 安装教程
网上的安装教程总是一笔带过,本人结合已经爬过的坑,为大家展示最简单的安装方式: 博主环境如下: 操作系统:win7 64bit 开发环境:lnmp(phpstudy) 注意: zftest:官方下载的 ...
- Mybatis传参方式
Mybatis传多个参数(三种解决方案) 据我目前接触到的传多个参数的方案有三种. 第一种方案 DAO层的函数方法 ? 1 Public User selectUser(String name,St ...
- 一起学习c++11——c++11中的新增的容器
c++11新增的容器1:array array最早是在boost中出现:http://www.boost.org/doc/libs/1_61_0/doc/html/array.html 当时的初衷是希 ...