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

  前言:在上一篇博文《小白也能看懂的插件化DroidPlugin原理(一)-- 動態代理》中詳細介紹了 DroidPlugin 原理中涉及到的動態代理模式,看完上篇博文後你就會發現原來動態代理真的很是簡單,只不過就是實現一個 InvocationHandler 接口重寫一下 invoke 方法而已。不錯,其實不少看似 high level 的技術都並無想象中的那麼晦澀難懂,只要你肯下定決心去了解它,去認識它,去學習它你就會發現,原來都是能夠學得懂的。本篇博文將介紹 DroidPlugin 框架中經常使用到的另外兩個知識點--反射機制和Hook技術。html

  本系列文章的代碼已經上傳至github,下載地址:https://github.com/lgliuwei/DroidPluginStudy 本篇文章對應的代碼在 com.liuwei.proxy_hook.reflect 和 com.liuwei.proxy_hook.hook.simplehook 包內。java

1、反射機制git

  一、反射是什麼?github

  JAVA反射機制是在運行狀態中,對於任意一個類,都可以知道這個類的全部屬性和方法;對於任意一個對象,都可以調用它的任意方法和屬性;這種動態獲取信息以及動態調用對象方法的功能稱爲java語言的反射機制。框架

  二、反射機制的做用:ide

  (1)反射能夠在運行時判斷任意一個對象所屬的類;函數

  (2)反射能夠在運行時構造任意一個類的對象;學習

  (3)反射能夠在運行時判斷任意一個類擁有的任意成員變量和方法;測試

  (4)反射能夠在運行時調用任意一個類的任意方法;this

  (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 // 一、經過反射獲取BeReflected所屬的類
2 Class<?> beReflectedClass = Class.forName("com.liuwei.proxy_hook.reflect.BeReflected");
3 Logger.i(ReflectTest.class, beReflectedClass);
4 
5 // 二、經過反射建立實例化一個類
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 // 三、經過反射調用一個私有方法和成員變量
 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 // 四、經過反射調用一個靜態的方法和變量
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 // 五、經過反射訪問一個帶參數的方法
2 Method method1 = beReflectedClass.getDeclaredMethod("method1", String.class);
3 method1.setAccessible(true);
4 method1.invoke(beReflected, "我是被傳入的參數");

  輸出以下:

  [BeReflected] : I am method1--param = 我是被傳入的參數

   (5)經過反射獲取類中全部的成員變量和方法。

1 // 六、遍歷類中全部的方法和成員變量
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 技術。

2、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 對方法便可。由於這裏的例子爲了簡單起見沒有添加其餘的方法和屬性,因此這一點須要着重說明一下。

3、小結

  其實 Hook 技術簡單來講能夠用替換、攔截來形容,並無用到新技術。Hook 自己並不難,它的難點在於你在對一段代碼 Hook 以前須要找出一個合適的 Hook 點,也就是說分析出從哪下手很關鍵,這就要求你對將要 Hook 的目標代碼的執行流程很是熟悉。本篇博文只是初步認識一下 Hook 技術,下一篇博文將會介紹如何經過 Hook 技術攔截 Android 中 startActivity 方法,並在分析的過程當中介紹哪些纔是合適的 Hook 點。感興趣的朋友能夠關注一下,敬請期待!

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

相關文章
相關標籤/搜索