小白也能看懂的插件化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] : 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 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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM