Android 動態代理以及利用動態代理實現 ServiceHook

  這篇博客主要介紹使用 InvocationHandler 這個接口來達到 hook 系統 service ,從而實現一些頗有意思特殊功能的詳細步驟。

  轉載請註明出處:blog.csdn.net/self_study/…

  對技術感興趣的同鞋加羣 544645972 一塊兒交流。
javascript

Java 的動態代理

  首先咱們要介紹的就是 Java 動態代理,Java 的動態代理涉及到兩個類:InvocationHandler 接口和 Proxy 類,下面咱們會着重介紹一下這兩個類,而且結合實例來着重分析一下使用的正確姿式等。在這以前簡單介紹一下 Java 中 class 文件的生成和加載過程,Java 編譯器編譯好 Java 文件以後會在磁盤中產生 .class 文件。這種 .class 文件是二進制文件,內容是隻有 JVM 虛擬機才能識別的機器碼,JVM 虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析 .class 文件內的信息,使用相對應的 ClassLoader 類加載器生成對應的 Class 對象:

html

這裏寫圖片描述


.class 字節碼文件是根據 JVM 虛擬機規範中規定的字節碼組織規則生成的,具體的 .class 文件格式介紹能夠查看博客 深刻理解Java Class文件格式Java 虛擬機規範

  經過上面咱們知道 JVM 是經過字節碼的二進制信息加載類的,那麼咱們若是在運行期系統中,遵循 Java 編譯系統組織 .class 文件的格式和結構,生成相應的二進制數據,而後再把這個二進制數據轉換成對應的類,這樣就能夠在運行中動態生成一個咱們想要的類了:

這裏寫圖片描述

  Java 中有不少的框架能夠在運行時根據 JVM 規範動態的生成對應的 .class 二進制字節碼,好比 ASM 和 Javassist 等,這裏就不詳細介紹了,感興趣的能夠去查閱相關的資料。這裏咱們就以動態代理模式爲例來介紹一下咱們要用到這兩個很重要的類,關於動態代理模式,我在 java/android 設計模式學習筆記(9)---代理模式中已經介紹過了,可是當時並無詳細分析過 InvocationHandler 接口和 Proxy 類,這裏就來詳細介紹一下。在代理模式那篇博客中,咱們提到了代理模式分爲動態代理和靜態代理:

這裏寫圖片描述


上面就是靜態代理模式的類圖,當在代碼階段規定這種代理關係時,ProxySubject 類經過編譯器生成 .class 字節碼文件,當系統運行以前,這個 .class 文件就已經存在了。動態代理模式的結構和上面的靜態代理模式的結構稍微有所不一樣,它引入了一個 InvocationHandler 接口和 Proxy 類。在靜態代理模式中,代理類 ProxySubject 中的方法,都指定地調用到特定 RealSubject 中對應的方法,ProxySubject 所作的事情無非是調用觸發 RealSubject 對應的方法;動態代理工做的基本模式就是將本身方法功能的實現交給 InvocationHandler 角色,外界對 Proxy 角色中每個方法的調用,Proxy 角色都會交給 InvocationHandler 來處理,而 InvocationHandler 則調用 RealSubject 的方法,以下圖所示:
這裏寫圖片描述

InvocationHandler 接口和 Proxy

  咱們來分析一下動態代理模式中 ProxySubject 的生成步驟:java

  1. 獲取 RealSubject 上的全部接口列表;
  2. 肯定要生成的代理類的類名,系統默認生成的名字爲:com.sun.proxy.$ProxyXXXX ;
  3. 根據須要實現的接口信息,在代碼中動態建立該 ProxySubject 類的字節碼;
  4. 將對應的字節碼轉換爲對應的 Class 對象;
  5. 建立 InvocationHandler 的實例對象 h,用來處理 Proxy 角色的全部方法調用;
  6. 以建立的 h 對象爲參數,實例化一個 Proxy 角色對象。
具體的代碼爲:

Subject.javaandroid

public interface Subject {
    String operation();
}複製代碼

RealSubject.javagit

public class RealSubject implements Subject{
    @Override
    public String operation() {
        return "operation by subject";
    }
}複製代碼

ProxySubject.javagithub

public class ProxySubject implements InvocationHandler{
     protected Subject subject;
    public ProxySubject(Subject subject) {
        this.subject = subject;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //do something before
        return method.invoke(subject, args);
    }
}複製代碼

測試代碼設計模式

Subject subject = new RealSubject();
ProxySubject proxy = new ProxySubject(subject);
Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);
sub.operation();複製代碼

以上就是動態代理模式的最簡單實現代碼,JDK 經過使用 java.lang.reflect.Proxy 包來支持動態代理,咱們來看看這個類的表述:app

Proxy provides static methods for creating dynamic proxy classes and instances, and it is also the 
superclass of all dynamic proxy classes created by those methods.複製代碼

通常狀況下,咱們使用下面的 newProxyInstance) 方法來生成動態代理:
框架

Public methods
static Object newProxyInstance(ClassLoader loader, Class[]<?> interfaces, InvocationHandler h)
Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler.

對應的參數爲:ide

Parameters
loader ClassLoader: the class loader to define the proxy class
interfaces Class: the list of interfaces for the proxy class to implement
h InvocationHandler: the invocation handler to dispatch method invocations to

咱們來仔細看看這個函數的實現:

public static Object newProxyInstance(ClassLoader loader, Class<?>[]interfaces,InvocationHandler h) throws IllegalArgumentException { 
    // 檢查 h 不爲空,不然拋異常
    if (h == null) { 
        throw new NullPointerException(); 
    } 

    // 得到與指定類裝載器和一組接口相關的代理類類型對象
    Class cl = getProxyClass(loader, interfaces); 

    // 經過反射獲取構造函數對象並生成代理類實例
    try { 
        Constructor cons = cl.getConstructor(constructorParams); 
        return (Object) cons.newInstance(new Object[] { h }); 
    } catch (NoSuchMethodException e) { throw new InternalError(e.toString()); 
    } catch (IllegalAccessException e) { throw new InternalError(e.toString()); 
    } catch (InstantiationException e) { throw new InternalError(e.toString()); 
    } catch (InvocationTargetException e) { throw new InternalError(e.toString()); 
    } 
}複製代碼

Proxy 類的 getProxyClass 方法調用了 ProxyGenerator 的 generatorProxyClass 方法去生成動態類:

public static byte[] generateProxyClass(final String name, Class[] interfaces)複製代碼

這個方法咱們下面將會介紹到,這裏先略過,生成這個動態類的字節碼以後,經過反射去生成這個動態類的對象,經過 Proxy 類的這個靜態函數生成了一個動態代理對象 sub 以後,調用 sub 代理對象的每個方法,在代碼內部,都是直接調用了 InvocationHandler 的 invoke 方法,而 invoke 方法根據代理類傳遞給本身的 method 參數來區分是什麼方法,咱們來看看 InvocationHandler 類的介紹:

InvocationHandler is the interface implemented by the invocation handler of a proxy instance.

Each proxy instance has an associated invocation handler. When a method is invoked on a proxy 
instance, the method invocation is encoded and dispatched to the invoke method of its invocation handler.複製代碼
Public methods
abstract Object invoke(Object proxy, Method method, Object[] args)
Processes a method invocation on a proxy instance and returns the result.

方法的參數和返回:

Parameters
proxy Object: the proxy instance that the method was invoked on
method Method: the Method instance corresponding to the interface method invoked on the proxy instance. The declaring class of the Method object will be the interface that the method was declared in, which may be a superinterface of the proxy interface that the proxy class inherits the method through.
args Object: an array of objects containing the values of the arguments passed in the method invocation on the proxy instance, or null if interface method takes no arguments. Arguments of primitive types are wrapped in instances of the appropriate primitive wrapper class, such as java.lang.Integer or java.lang.Boolean.
Returns
Object the value to return from the method invocation on the proxy instance. If the declared return type of the interface method is a primitive type, then the value returned by this method must be an instance of the corresponding primitive wrapper class; otherwise, it must be a type assignable to the declared return type. If the value returned by this method is null and the interface method's return type is primitive, then a NullPointerException will be thrown by the method invocation on the proxy instance. If the value returned by this method is otherwise not compatible with the interface method's declared return type as described above, a ClassCastException will be thrown by the method invocation on the proxy instance.

上面提到的一點須要特別注意的是,若是 Subject 類中定義的方法返回值爲 8 種基本數據類型,那麼在 ProxySubject 類中必需要返回相應的基本類型包裝類,即 int 對應的返回爲 Integer 等等,還須要注意的是若是此時返回 null,則會拋出 NullPointerException,除此以外的其餘狀況下返回值的對象必需要和 Subject 類中定義方法的返回值一致,要否則會拋出 ClassCastException。

生成源碼分析

  那麼經過 Proxy 類的 newProxyInstance 方法動態生成的類是什麼樣子的呢,咱們上面也提到了,JDK 爲咱們提供了一個方法 ProxyGenerator.generateProxyClass(String proxyName,class[] interfaces) 來產生動態代理類的字節碼,這個類位於 sun.misc 包中,是屬於特殊的 jar 包,因而問題又來了,android studio 建立的 android 工程是無法找到 ProxyGenerator 這個類的,這個類在 jre 目錄下,就算我把這個類相關的 .jar 包拷貝到工程裏面而且在 gradle 裏面引用它,雖然最後可以找到這個類,可是編譯時又會出現很奇葩的問題,因此,沒辦法嘍,android studio 沒辦法建立普通的 java 工程,只能本身裝一個 intellij idea 或者求助相關的同事了。建立好 java 工程以後,使用下面這段代碼就能夠將生成的類導出到指定路徑下面:

public static void generateClassFile(Class clazz,String proxyName)
{
    //根據類信息和提供的代理類名稱,生成字節碼 
    byte[] classFile = ProxyGenerator.generateProxyClass(proxyName, clazz.getInterfaces());
    String paths = "D:\\"; // 這裏寫死路徑爲 D 盤,能夠根據實際須要去修改
    System.out.println(paths);
    FileOutputStream out = null;

    try {
        //保留到硬盤中 
        out = new FileOutputStream(paths+proxyName+".class");
        out.write(classFile);
        out.flush();
    } catch (Exception e) {
        e.printStackTrace();
    } finally {
        try {
            out.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}複製代碼

調用代碼的方式爲:

generateClassFile(ProxySubject.class, "ProxySubject");複製代碼

最後就會在 D 盤(若是沒有修改路徑)的根目錄下面生成一個 ProxySubject.class 的文件,使用 jd-gui 就能夠打開該文件:

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.lang.reflect.UndeclaredThrowableException;

public final class ProxySubject extends Proxy implements Subject {
  private static Method m1;
  private static Method m3;
  private static Method m2;
  private static Method m0;

  public ProxySubject(InvocationHandler paramInvocationHandler)
  {
    super(paramInvocationHandler);
  }

  public final boolean equals(Object paramObject)
  {
    try
    {
      return ((Boolean)this.h.invoke(this, m1, new Object[] { paramObject })).booleanValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String operation()
  {
    try
    {
      return (String)this.h.invoke(this, m3, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final String toString()
  {
    try
    {
      return (String)this.h.invoke(this, m2, null);
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  public final int hashCode()
  {
    try
    {
      return ((Integer)this.h.invoke(this, m0, null)).intValue();
    }
    catch (Error|RuntimeException localError)
    {
      throw localError;
    }
    catch (Throwable localThrowable)
    {
      throw new UndeclaredThrowableException(localThrowable);
    }
  }

  static
  {
    try
    {
      m1 = Class.forName("java.lang.Object").getMethod("equals", new Class[] { Class.forName("java.lang.Object") });
      m3 = Class.forName("Subject").getMethod("operation", new Class[0]);
      m2 = Class.forName("java.lang.Object").getMethod("toString", new Class[0]);
      m0 = Class.forName("java.lang.Object").getMethod("hashCode", new Class[0]);
      return;
    }
    catch (NoSuchMethodException localNoSuchMethodException)
    {
      throw new NoSuchMethodError(localNoSuchMethodException.getMessage());
    }
    catch (ClassNotFoundException localClassNotFoundException)
    {
      throw new NoClassDefFoundError(localClassNotFoundException.getMessage());
    }
  }
}複製代碼

能夠觀察到這個生成的類繼承自 java.lang.reflect.Proxy,實現了 Subject 接口,咱們在看看生成動態類的代碼:

Subject sub = (Subject) Proxy.newProxyInstance(subject.getClass().getClassLoader(),
        subject.getClass().getInterfaces(), proxy);複製代碼

可見這個動態生成類會實現 subject.getClass().getInterfaces() 中的全部接口,而且還有一點是類中全部的方法都是 final 的,並且該類也是 final ,因此該類不可繼承,最後就是全部的方法都會調用到 InvocationHandler 對象 h 的 invoke() 方法,這也就是爲何最後會調用到 ProxySubject 類的 invoke() 方法了,畫一下它們的簡單類圖:

這裏寫圖片描述


從這個類圖能夠很清楚的看明白,動態生成的類 ProxySubject(同名,因此後面加上了 Dynamic)持有了實現 InvocationHandler 接口的 ProxySubject 類的對象 h,而後調用代理對象的 operation 方法時,就會調用到對象 h 的 invoke 方法中,invoke 方法中根據 method 的名字來區分究竟是什麼方法,而後經過 method.invoke() 方法來調用具體對象的對應方法。

Android 中利用動態代理實現 ServiceHook

  經過上面對 InvocationHandler 的介紹,咱們對這個接口應該有了大致的瞭解,可是在運行時動態生成的代理類有什麼做用呢,其實它的做用就是在調用真正業務以前或者以後插入一些額外的操做:

這裏寫圖片描述


因此簡而言之,代理類的處理邏輯很簡單,就是在調用某個方法前及方法後插入一些額外的業務。而咱們在 Android 中的實踐例子就是在真正調用系統的某個 Service 以前和以後選擇性的作一些本身特殊的處理,這種思想在插件化框架上也是很重要的。那麼咱們具體怎麼去實現 hook 系統的 Service ,在真正調用系統 Service 的時候附加上咱們須要的業務呢,這就須要介紹 ServiceManager 這個類了。

ServiceManager 介紹以及 hook 的步驟

  第一步

  關於 ServiceManager 的詳細介紹在個人博客:android IPC通訊(下)-AIDL 中已經介紹過了,這裏就不贅述了,強烈建議你們去看一下那篇博客,咱們這裏就着重看一下 ServiceManager 的 getService(String name) 方法:

public final class ServiceManager {
    private static final String TAG = "ServiceManager";

    private static IServiceManager sServiceManager;
    private static HashMap<String, IBinder> sCache = new HashMap<String, IBinder>();

    private static IServiceManager getIServiceManager() {
        if (sServiceManager != null) {
            return sServiceManager;
        }

        // Find the service manager
        sServiceManager = ServiceManagerNative.asInterface(BinderInternal.getContextObject());
        return sServiceManager;
    }

    /** * Returns a reference to a service with the given name. * * @param name the name of the service to get * @return a reference to the service, or <code>null</code> if the service doesn't exist */
    public static IBinder getService(String name) {
        try {
            IBinder service = sCache.get(name);
            if (service != null) {
                return service;
            } else {
                return getIServiceManager().getService(name);
            }
        } catch (RemoteException e) {
            Log.e(TAG, "error in getService", e);
        }
        return null;
    }

    public static void addService(String name, IBinder service) {
    ...
    }
    ....
}複製代碼

咱們能夠看到,getService 方法第一步會去 sCache 這個 map 中根據 Service 的名字獲取這個 Service 的 IBinder 對象,若是獲取到爲空,則會經過 ServiceManagerNative 經過跨進程通訊獲取這個 Service 的 IBinder 對象,因此咱們就以 sCache 這個 map 爲切入點,反射該對象,而後修改該對象,因爲系統的 android.os.ServiceManager 類是 @hide 的,因此只能使用反射,根據這個初步思路,寫下第一步的代碼:

Class c_ServiceManager = Class.forName("android.os.ServiceManager");
if (c_ServiceManager == null) {
    return;
}

if (sCacheService == null) {
    try {
        Field sCache = c_ServiceManager.getDeclaredField("sCache");
        sCache.setAccessible(true);
        sCacheService = (Map<String, IBinder>) sCache.get(null);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
sCacheService.remove(serviceName);
sCacheService.put(serviceName, service);複製代碼

反射 sCache 這個變量,移除系統 Service,而後將咱們本身改造過的 Service put 進去,這樣就能實現當調用 ServiceManager 的 getService(String name) 方法的時候,返回的是咱們改造過的 Service 而不是系統的原生 Service。

  第二步

  第一步知道了如何去將改造事後的 Service put 進系統的 ServiceManager 中,第二步就是去生成一個 hook Service 了,怎麼去生成呢?這就要用到咱們上面介紹到的 InvocationHandler 類,咱們先獲取原生的 Service ,而後經過 InvocationHandler 去構造一個 hook Service,最後經過第一步的步驟 put 進 sCache 這個變量便可,第二步代碼:

public class ServiceHook implements InvocationHandler {
    private static final String TAG = "ServiceHook";

    private IBinder mBase;
    private Class<?> mStub;
    private Class<?> mInterface;
    private InvocationHandler mInvocationHandler;

    public ServiceHook(IBinder mBase, String iInterfaceName, boolean isStub, InvocationHandler InvocationHandler) {
        this.mBase = mBase;
        this.mInvocationHandler = InvocationHandler;

        try {
            this.mInterface = Class.forName(iInterfaceName);
            this.mStub = Class.forName(String.format("%s%s", iInterfaceName, isStub ? "$Stub" : ""));
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }

    @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("queryLocalInterface".equals(method.getName())) {
            return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[] { mInterface },
                    new HookHandler(mBase, mStub, mInvocationHandler));
        }

        Log.e(TAG, "ERROR!!!!! method:name = " + method.getName());
        return method.invoke(mBase, args);
    }

    private static class HookHandler implements InvocationHandler {
        private Object mBase;
        private InvocationHandler mInvocationHandler;

        public HookHandler(IBinder base, Class<?> stubClass,
                           InvocationHandler InvocationHandler) {
            mInvocationHandler = InvocationHandler;

            try {
                Method asInterface = stubClass.getDeclaredMethod("asInterface", IBinder.class);
                this.mBase = asInterface.invoke(null, base);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (mInvocationHandler != null) {
                return mInvocationHandler.invoke(mBase, method, args);
            }
            return method.invoke(mBase, args);
        }
    }
}複製代碼

這裏咱們以 ClipboardService 的調用代碼爲例:

IBinder clipboardService = ServiceManager.getService(Context.CLIPBOARD_SERVICE);
String IClipboard = "android.content.IClipboard";

if (clipboardService != null) {
    IBinder hookClipboardService =
            (IBinder) Proxy.newProxyInstance(clipboardService.getClass().getClassLoader(),
                            clipboardService.getClass().getInterfaces(),
                    new ServiceHook(clipboardService, IClipboard, true, new ClipboardHookHandler()));
    //調用第一步的方法
    ServiceManager.setService(Context.CLIPBOARD_SERVICE, hookClipboardService);
} else {
    Log.e(TAG, "ClipboardService hook failed!");
}複製代碼

分析一下上面的這段代碼,分析以前,先要看一下 ClipboardService 的相關類圖:

這裏寫圖片描述

從這張類圖咱們能夠清晰的看見 ClipboardService 的相關繼承關係,接下來就是分析代碼了:

  • 調用代碼中,Proxy.newProxyInstance 函數的第二個參數須要注意,因爲 ClipboardService 繼承自三個接口,因此這裏須要把全部的接口傳遞進去,可是若是將第二個參數變動爲 new Class[] { IBinder.class } 其實也是沒有問題的(感興趣的能夠試一下,第一個參數修改成 IBinder.class.getClassLoader(),第二個參數修改成 new Class[]{IBinder.class},也是能夠的),由於實際使用的時候,咱們只是用到了 IBinder 類的 queryLocalInterface 方法,其餘的方法都沒有使用到,接下來咱們就會說明 queryLocalInterface 這個函數的做用;
  • Proxy.newProxyInstance 函數的第三個參數爲 ServcieHook 對象,因此當把這個 Service set 進 sCache 變量的時候,全部調用 ClipboardService 的操做都將調用到 ServiceHook 中的 invoke 方法中;
  • 接着咱們來看看 ServiceHook 的 invoke 函數,很簡單,當函數爲 queryLocalInterface 方法的時候返回一個 HookHandler 的對象,其餘的狀況直接調用 method.invoke 原生系統的 ClipboardService 功能,爲何只處理 queryLocalInterface 方法呢,這個我在博客:java/android 設計模式學習筆記(9)---代理模式 中分析 AMS 的時候已經提到了,asInterface 方法最終會調用到 queryLocalInterface 方法,queryLocalInterface 方法最後的返回結果會做爲 asInterface 的結果而返回給 Service 的調用方,因此 queryLocalInterface 方法的最後返回的對象是會被外部直接調用的,這也解釋了爲何調用代碼中的第二個參數變動爲 new Class[] { IBinder.class } 也是沒有問題,由於第一次調用到 queryLocalInterface 函數以後,後續的全部調用都到了 HookHandler 對象中,動態生成的對象中只須要有 IBinder 的 queryLocalInterface 方法便可,而不須要 IClipboard 接口的其餘方法;
  • 接下來是 HookHandler 類,首先咱們看看這個類的構造函數,第一個參數爲系統的 ClipboardService,第二個參數爲

Class.forName(String.format("%s%s", iInterfaceName, isStub ? "$Stub" : ""))//"android.content.IClipboard"複製代碼

這個參數我們對照上面的類圖,這個類爲 ClipboardService 的父類,它裏面有一個 asInterface 的方法,經過反射 asInterface 方法而後將 IBinder 對象變成 IInterface 對象,爲何要這麼作,能夠去看看個人博客: java/android 設計模式學習筆記(9)---代理模式 中的最後總結,經過 ServiceManager.getService 方法獲取一個 IBinder 對象,可是這個 IBinder 對象不能直接調用,必需要經過 asInterface 方法轉成對應的 IInterface 對象纔可使用,因此 mBase 對象實際上是一個 IInterface 對象:

這裏寫圖片描述


最後也證明了這個結果,爲何是 Proxy 對象這就不用我解釋了吧;
  • 最後是 HookHandler 的 invoke 方法,這個方法調用到了 ClipboardHookHandler 對象,咱們來看看這個類的實現:
  • public class ClipboardHook {
    
        private static final String TAG = ClipboardHook.class.getSimpleName();
    
        public static void hookService(Context context) {
            IBinder clipboardService = ServiceManager.getService(Context.CLIPBOARD_SERVICE);
            String IClipboard = "android.content.IClipboard";
    
            if (clipboardService != null) {
                IBinder hookClipboardService =
                        (IBinder) Proxy.newProxyInstance(IBinder.class.getClassLoader(),
                                new Class[]{IBinder.class},
                                new ServiceHook(clipboardService, IClipboard, true, new ClipboardHookHandler()));
                ServiceManager.setService(Context.CLIPBOARD_SERVICE, hookClipboardService);
            } else {
                Log.e(TAG, "ClipboardService hook failed!");
            }
        }
    
        public static class ClipboardHookHandler implements InvocationHandler {
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                int argsLength = args.length;
                //每次從本應用複製的文本,後面都加上分享的出處
                if ("setPrimaryClip".equals(methodName)) {
                    if (argsLength >= 2 && args[0] instanceof ClipData) {
                        ClipData data = (ClipData) args[0];
                        String text = data.getItemAt(0).getText().toString();
                        text += "this is shared from ServiceHook-----by Shawn_Dut";
                        args[0] = ClipData.newPlainText(data.getDescription().getLabel(), text);
                    }
                }
                return method.invoke(proxy, args);
            }
        }
    }複製代碼

    因此 ClipboardHookHandler 類的 invoke 方法最終獲取到了要 hook 的 Service 的 IInterface 對象(即爲 IClipboard.Proxy 對象,最後經過 Binder Driver 調用到了系統的 ClipboardService 中),調用函數的 Method 對象和參數列表對象,獲取到了這些以後,不用我說了,就能夠盡情的去作一些額外的操做了,我這裏是在仿照知乎複製文字時,在後面加上相似的版權聲明。

    問題討論

      上面就是 ServiceHook 的詳細步驟了,瞭解它必需要對 InvocationHandler 有詳細的瞭解,而且還要去看一下 AOSP 源碼,好比要去 hook ClipboardService ,那麼就要去先看看 ClipboardService 的源碼,看看這個類中每一個函數的名字和做用,參數列表中每一個參數的順序和做用,並且有時候這還遠遠不夠,咱們知道,隨着 Android 每一個版本的更新,這些類可能也會被更新修改甚至刪除,頗有可能對於新版原本說老的 hook 方法就無論用了,這時候必需要去了解最新的源碼,看看更新修改的地方,針對新版本去從新制定 hook 的步驟,這是一點須要慎重對待考慮的地方。

    步驟

    這裏寫圖片描述

    源碼

      此爲咱們公司某位大神代碼,通過整理修改而出,不知道有沒有版權問題,哈哈哈,謝謝周杰大神,雖然已經不在公司,感謝感謝~~

      源碼下載地址:github.com/zhaozepeng/…

      先來看看運行的效果:

      

    這裏寫圖片描述


      最後把全部的代碼貼出來:

    ServiceManager.java

    public class ServiceManager {
    
        private static Method sGetServiceMethod;
        private static Map<String, IBinder> sCacheService;
        private static Class c_ServiceManager;
    
        static {
            try {
                c_ServiceManager = Class.forName("android.os.ServiceManager");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        public static IBinder getService(String serviceName) {
            if (c_ServiceManager == null) {
                return null;
            }
    
            if (sGetServiceMethod == null) {
                try {
                    sGetServiceMethod = c_ServiceManager.getDeclaredMethod("getService", String.class);
                    sGetServiceMethod.setAccessible(true);
                } catch (NoSuchMethodException e) {
                    e.printStackTrace();
                }
            }
    
            if (sGetServiceMethod != null) {
                try {
                    return (IBinder) sGetServiceMethod.invoke(null, serviceName);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            return null;
        }
    
        public static void setService(String serviceName, IBinder service) {
            if (c_ServiceManager == null) {
                return;
            }
    
            if (sCacheService == null) {
                try {
                    Field sCache = c_ServiceManager.getDeclaredField("sCache");
                    sCache.setAccessible(true);
                    sCacheService = (Map<String, IBinder>) sCache.get(null);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            sCacheService.remove(serviceName);
            sCacheService.put(serviceName, service);
        }
    }複製代碼

    ServiceManager 這個類就是使用反射的方式去獲取對應 Service (這裏不能使用 Context.getSystemService 函數,由於它的返回不是 IBinder 對象,好比對於 ClipboardService,它就是 ClipboardManager 對象)和設置 service 到 sCache 變量中;

    ServiceHook.java

    public class ServiceHook implements InvocationHandler {
        private static final String TAG = "ServiceHook";
    
        private IBinder mBase;
        private Class<?> mStub;
        private Class<?> mInterface;
        private InvocationHandler mInvocationHandler;
    
        public ServiceHook(IBinder mBase, String iInterfaceName, boolean isStub, InvocationHandler InvocationHandler) {
            this.mBase = mBase;
            this.mInvocationHandler = InvocationHandler;
    
            try {
                this.mInterface = Class.forName(iInterfaceName);
                this.mStub = Class.forName(String.format("%s%s", iInterfaceName, isStub ? "$Stub" : ""));
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
        }
    
        @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if ("queryLocalInterface".equals(method.getName())) {
                return Proxy.newProxyInstance(proxy.getClass().getClassLoader(), new Class[] { mInterface },
                        new HookHandler(mBase, mStub, mInvocationHandler));
            }
    
            Log.e(TAG, "ERROR!!!!! method:name = " + method.getName());
            return method.invoke(mBase, args);
        }
    
        private static class HookHandler implements InvocationHandler {
            private Object mBase;
            private InvocationHandler mInvocationHandler;
    
            public HookHandler(IBinder base, Class<?> stubClass,
                               InvocationHandler InvocationHandler) {
                mInvocationHandler = InvocationHandler;
    
                try {
                    Method asInterface = stubClass.getDeclaredMethod("asInterface", IBinder.class);
                    this.mBase = asInterface.invoke(null, base);
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
    
            @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (mInvocationHandler != null) {
                    return mInvocationHandler.invoke(mBase, method, args);
                }
                return method.invoke(mBase, args);
            }
        }
    }複製代碼

    這個類上面介紹的很詳細了,在這裏就不繼續介紹了;

    ClipboardHook.java

    public class ClipboardHook {
    
        private static final String TAG = ClipboardHook.class.getSimpleName();
    
        public static void hookService(Context context) {
            IBinder clipboardService = ServiceManager.getService(Context.CLIPBOARD_SERVICE);
            String IClipboard = "android.content.IClipboard";
    
            if (clipboardService != null) {
                IBinder hookClipboardService =
                        (IBinder) Proxy.newProxyInstance(IBinder.class.getClassLoader(),
                                new Class[]{IBinder.class},
                                new ServiceHook(clipboardService, IClipboard, true, new ClipboardHookHandler()));
                ServiceManager.setService(Context.CLIPBOARD_SERVICE, hookClipboardService);
            } else {
                Log.e(TAG, "ClipboardService hook failed!");
            }
        }
    
        public static class ClipboardHookHandler implements InvocationHandler {
    
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                int argsLength = args.length;
                //每次從本應用複製的文本,後面都加上分享的出處
                if ("setPrimaryClip".equals(methodName)) {
                    if (argsLength >= 2 && args[0] instanceof ClipData) {
                        ClipData data = (ClipData) args[0];
                        String text = data.getItemAt(0).getText().toString();
                        text += "this is shared from ServiceHook-----by Shawn_Dut";
                        args[0] = ClipData.newPlainText(data.getDescription().getLabel(), text);
                    }
                }
                return method.invoke(proxy, args);
            }
        }
    }複製代碼

    這裏的 hook ClipboardService 使用的是,將複製的文本取出,在結尾處添加咱們自定義的文本,用戶再粘貼到其餘地方的時候,就會出現咱們添加上咱們自定義文本後的文字了,還有須要注意的是這隻會影響應用進程的 ClipboardService,並不會影響主進程的相關 Service,由於無論怎麼 hook,修改的都是應用進程的 ServiceManager 裏面的 sCache 變量,應用進程的 ServiceManager 其實也就至關於一個 Binder Client,sCache 裏面獲取不到對應的 Service,它就會自動經過 Binder Driver 和 Binder Server (主進程的 ServiceManager)通訊去獲取對應的 Service,因此修改 Binder Client,實際上是不會影響 Binder Server的,不明白的建議仍是看看:android IPC通訊(下)-AIDL

    測試代碼

    switch (v.getId()) {
        case R.id.btn_copy:
            String input = mEtInput.getText().toString().trim();
            if (TextUtils.isEmpty(input)) {
                Toast.makeText(this, "input不能爲空", Toast.LENGTH_SHORT).show();
                return;
             }
    
            //複製
            ClipData clip = ClipData.newPlainText("simple text", mEtInput.getText().toString());
            clipboard.setPrimaryClip(clip);
            break;
        case R.id.btn_show_paste:
            //黏貼
            clip = clipboard.getPrimaryClip();
            if (clip != null && clip.getItemCount() > 0) {
                Toast.makeText(this, clip.getItemAt(0).getText(), Toast.LENGTH_SHORT).show();
            }
            break;
    }複製代碼

    測試代碼,我這裏就不須要說明了,Clipboard 的簡單使用而已。
      
      這裏只演示了 hook ClipboardService 的例子,其餘的使用方式好比插件化等等在這裏就不一一介紹了,感興趣的能夠去網上查閱相關的資料。
      轉載請註明出處:blog.csdn.net/self_study/…

    引用

    blog.csdn.net/luanlouis/a…
    www.cnblogs.com/xiaoluo5013…
    blog.csdn.net/self_study/…
    paddy-w.iteye.com/blog/841798
    www.cnblogs.com/flyoung2008…

    相關文章
    相關標籤/搜索