1. 程式人生 > >小白也能看懂的外掛化DroidPlugin原理(二)-- 反射機制和Hook入門

小白也能看懂的外掛化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

相關推薦

外掛DroidPlugin原理-- 反射機制Hook入門

  前言:在上一篇博文《小白也能看懂的外掛化DroidPlugin原理(一)-- 動態代理》中詳細介紹了 DroidPlugin 原理中涉及到的動態代理模式,看完上篇博文後你就會發現原來動態代理真的非常簡單,只不過就是實現一個 InvocationHandler 介面重寫一

的 Laravel 核心概念講解

bin php 依賴註入 keyword set 即將 函數 routes nds 自動依賴註入 什麽是依賴註入,用大白話將通過類型提示的方式向函數傳遞參數。 實例 1 首先,定義一個類: /routes/web.php class Bar {} 假如我們在其他地方要使用到

netty解碼器詳解

什麼是編解碼器?   首先,我們回顧一下netty的元件設計:Netty的主要元件有Channel、EventLoop、ChannelFuture、ChannelHandler、ChannelPipe等。 ChannelHandler   ChannelHandler充當了處理入站和出站

爬蟲教程」Python做一個簡單爬蟲,的教程

俗話說“巧婦難為無米之炊”,除了傳統的資料來源,如歷史年鑑,實驗資料等,很難有更為簡便快捷的方式獲得資料,在目前網際網路的飛速發展寫,大量的資料可以通過網頁直接採集,“網路爬蟲”應運而生,本篇將會講解簡單的網路爬蟲編寫方法。   開發環境 每個人的開發環境各異,下面上是我的開發

Python做一個簡單爬蟲,的教程

俗話說“巧婦難為無米之炊”,除了傳統的資料來源,如歷史年鑑,實驗資料等,很難有更為簡便快捷的方式獲得資料,在目前網際網路的飛速發展寫,大量的資料可以通過網頁直接採集,“網路爬蟲”應運而生,本篇將會講解簡單的網路爬蟲編寫方法。 開發環境 每個人的開發環境各異,下面上是我的開發環境,對於必須的

「爬蟲教程」Python做一個簡單爬蟲,的教程

俗話說“巧婦難為無米之炊”,除了傳統的資料來源,如歷史年鑑,實驗資料等,很難有更為簡便快捷的方式獲得資料,在目前網際網路的飛速發展寫,大量的資料可以通過網頁直接採集,“網路爬蟲”應運而生,本篇將會講解簡單的網路爬蟲編寫方法。   開發環境 每個人的開發環境各異,下面上是我的開發

的零知識證明與zk-SNARKs

作為剛剛踏入密碼學的領域的一隻小白,我最近在學習ZCash (ZeroCash) 的原理,也就是zk-SNARKs 。將一點點感悟和理解寫下來,以拋磚引玉。(不得不說看得真讓人頭大啊!) zk-SNARKs zk-SNARKs 是 zero knowledge

Git的基本使用方法0基礎詳細教程(含視訊講解)

git指令介紹,下面有詳解指令可以先跳過直接看下面的詳解 $ mkdir learngit     //建立一個learngit資料夾 $ cd learngit         //進入learngit資料夾 $ pwd                   //用於顯

spring成神之路-基礎

基礎 1.什麼是IOC 控制反轉(Inversion of Control,縮寫為IoC),是面向物件程式設計中的一種設計原則,可以用來減低計算機程式碼之間的耦合 度。其中最常見的方式包含: 1)依賴注入(Dependency Injection,簡稱DI) 2)依賴查詢(

全網最簡單!步驟超詳細!的深度學習安裝教程caffe版

想入門深度學習?可是環境就是裝不上?還在對著滿屏的errors抓耳撓腮? 不存在的!!! 小白福利——從零開始手把手教會你安裝一個屬於自己的深度學習環境! 重灌linux os 20多遍、硬著頭皮剛掉了數不盡的error、成功配置了諸多專案苛刻環境的深度學習工作者吐血整理!

入門篇】區塊鏈糖果空投幣免費領取教程,

一、領取空投幣必備軟體:1.Imtoken以太坊錢包軟體,幣圈支付寶,所有ERC2.0代幣都可以用這個來存。如果不會用點選檢視我們的:imtoken錢包新使用者使用教程2.Telegram電報(幣用)幣圈常用交流工具,加密版國外微信;這也是我們空投幣必備的一個工具;如果不會下

分頁技術,(從後臺傳json到前臺解析顯示)

這是一篇我在初學習過程中,遇到的動態資料分頁顯示的問題,前臺採用Ajax傳給後臺,後臺在訪問資料庫取出分頁資料再轉換為json格式傳遞給前臺,前臺再解析顯示到表格中。在此寫出我在做的過程中遇到的問題,可以讓其他人少走彎路。 前臺方面會用到分頁的外掛,這是傳送地址,http:

漫畫 | 的量子物理漫畫終於來了!

選自 Medium & analyticsvidhya 費米和玻色是兩位著名的物理學家,

RSA 非對稱加密原理哦~

RSA 加密原理 步驟 說明 描述 備註 1 找出質數 P 、Q - 2 計算公共模數 N = P * Q - 3 尤拉函式 φ(N) = (P-1)(Q-1) - 4

白話解析分布式系統,

都是 pri 爭論 先後 -o 以及 結果 mysq 自己 西方詩歌有雲,無人是孤島,你我心相系。今天,這句話同樣適用於計算機。我們身邊的服務器,個人電腦以及數據存儲一直都在彼此通信。其實,我們每天使用的(和在開發的)應用和服務也都是構成完整系統的計算元素,彼此進行著交互,

【深度學習】:的卷積神經網路

小編在市面看了很多介紹計算機視覺的知識,感覺都非常深奧,難以理解和入門。因此總結出了一套容易理解的教程,希望能夠和大家分享。 一.人工神經網路 人工神經網路是一種模擬人腦構建出來的神經網路,每一個神經元都具有一定的權重和閾值。僅有單個神經元的圖例如下所示:     從中可以看到每一個神

的Redis教學基礎篇——redis基礎資料結構

各位看官大大們,週末好! 作為一個Java後端開發,要想獲得比較可觀的工資,Redis基本上是必會的(不要問我為什麼知道,問就是被問過無數次)。那麼Redis是什麼,它到底擁有什麼神祕的力量,能獲得眾多公司的青睞?接下來就由小編我帶大家來揭祕Redis的五種基本資料結構。 Redis是C語音編寫的基於記憶體

的Redis教學基礎篇——朋友面試被Skiplist跳躍表攔住了

各位看官大大們,雙節快樂 !!! 這是本系列部落格的第二篇,主要講的是Redis基礎資料結構中ZSet(有序集合)底層實現之一的Skiplist跳躍表。  不知道那些是Redis基礎資料結構的看官們,可以翻閱我的上一篇文章:  小白也能看懂的REDIS教學基礎篇——REDIS基礎資料結構

的ArrayList的擴容機制

來,話不多說進入正題!我們下面用最簡單的程式碼建立ArrayList並新增11個元素,並 一 一 講解底層原始碼;在說之前,給大家先普及一些小知識:   》ArrayList底層是用陣列來實現的   》陣列一旦建立後,大小就是固定的,如果超出了陣列大小後,就會建立一個新的陣列   》接下來所謂陣列的擴容實質上

的JDK1.8前_HashMap的擴容機制原理

  最近在研究hashmap的擴容機制,作為一個小白,相信我的理解,對於一些同樣是剛剛接觸hashmap的白白是有很很大的幫助,畢竟你去看一些已經對資料結構瞭解透徹的大神談hashmap的原理等,人家說的很高大上,時不時會夾著稍許的英文你也看不懂是吧,不過這樣顯得比較有逼格哈哈。在正文之前,我非常有必要給剛剛