這篇博客主要介紹使用 InvocationHandler 這個接口來達到 hook 系統 service ,從而實現一些很有意思特殊功能的詳細步驟。</br>
轉載請注明出處:http://blog.csdn.net/self_study/article/details/55050627</br>
對技術感興趣的同鞋加群 544645972 一起交流。</br>
Java 的動態代理
首先我們要介紹的就是 Java 動態代理,Java 的動態代理涉及到兩個類:InvocationHandler 接口和 Proxy 類,下面我們會著重介紹一下這兩個類,并且結合實例來著重分析一下使用的正確姿勢等。在這之前簡單介紹一下 Java 中 class 文件的生成和加載過程,Java 編譯器編譯好 Java 文件之后會在磁盤中產生 .class 文件。這種 .class 文件是二進制文件,內容是只有 JVM 虛擬機才能識別的機器碼,JVM 虛擬機讀取字節碼文件,取出二進制數據,加載到內存中,解析 .class 文件內的信息,使用相對應的 ClassLoader 類加載器生成對應的 Class 對象:</br>
.class 字節碼文件是根據 JVM 虛擬機規范中規定的字節碼組織規則生成的,具體的 .class 文件格式介紹可以查看博客 深入理解Java Class文件格式 和 Java 虛擬機規范。</br>
通過上面我們知道 JVM 是通過字節碼的二進制信息加載類的,那么我們如果在運行期系統中,遵循 Java 編譯系統組織 .class 文件的格式和結構,生成相應的二進制數據,然后再把這個二進制數據轉換成對應的類,這樣就可以在運行中動態生成一個我們想要的類了:</br>
Java 中有很多的框架可以在運行時根據 JVM 規范動態的生成對應的 .class 二進制字節碼,比如 ASM 和 Javassist 等,這里就不詳細介紹了,感興趣的可以去查閱相關的資料。這里我們就以動態代理模式為例來介紹一下我們要用到這兩個很重要的類,關于動態代理模式,我在 java/android 設計模式學習筆記(9)---代理模式中已經介紹過了,但是當時并沒有詳細分析過 InvocationHandler 接口和 Proxy 類,這里就來詳細介紹一下。在代理模式那篇博客中,我們提到了代理模式分為動態代理和靜態代理:</br>
上面就是靜態代理模式的類圖,當在代碼階段規定這種代理關系時,ProxySubject 類通過編譯器生成 .class 字節碼文件,當系統運行之前,這個 .class 文件就已經存在了。動態代理模式的結構和上面的靜態代理模式的結構稍微有所不同,它引入了一個 InvocationHandler 接口和 Proxy 類。在靜態代理模式中,代理類 ProxySubject 中的方法,都指定地調用到特定 RealSubject 中對應的方法,ProxySubject 所做的事情無非是調用觸發 RealSubject 對應的方法;動態代理工作的基本模式就是將自己方法功能的實現交給 InvocationHandler 角色,外界對 Proxy 角色中每一個方法的調用,Proxy 角色都會交給 InvocationHandler 來處理,而 InvocationHandler 則調用 RealSubject 的方法,如下圖所示:</br>
InvocationHandler 接口和 Proxy 類
我們來分析一下動態代理模式中 ProxySubject 的生成步驟:<ol><li>獲取 RealSubject 上的所有接口列表;</li><li>確定要生成的代理類的類名,系統默認生成的名字為:com.sun.proxy.$ProxyXXXX ;</li><li>根據需要實現的接口信息,在代碼中動態創建該 ProxySubject 類的字節碼;</li><li> 將對應的字節碼轉換為對應的 Class 對象;</li><li> 創建 InvocationHandler 的實例對象 h,用來處理 Proxy 角色的所有方法調用;</li><li>以創建的 h 對象為參數,實例化一個 Proxy 角色對象。</li></ol>具體的代碼為:
Subject.java
public interface Subject {
String operation();
}
RealSubject.java
public class RealSubject implements Subject{
@Override
public String operation() {
return "operation by subject";
}
}
ProxySubject.java
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 包來支持動態代理,我們來看看這個類的表述:
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](https://developer.android.com/reference/java/lang/reflect/Proxy.html#newProxyInstance(java.lang.ClassLoader, java.lang.Class<?>[], java.lang.reflect.InvocationHandler)) 方法來生成動態代理:</br>
Public methods | |
---|---|
static Object | newProxyInstance(ClassLoader loader, Class[]<?> interfaces, InvocationHandler h) </br>Returns an instance of a proxy class for the specified interfaces that dispatches method invocations to the specified invocation handler. |
對應的參數為:
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 類的介紹:</br>
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)</br>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() 方法了,畫一下它們的簡單類圖:</br>
從這個類圖可以很清楚的看明白,動態生成的類 ProxySubject(同名,所以后面加上了 Dynamic)持有了實現 InvocationHandler 接口的 ProxySubject 類的對象 h,然后調用代理對象的 operation 方法時,就會調用到對象 h 的 invoke 方法中,invoke 方法中根據 method 的名字來區分到底是什么方法,然后通過 method.invoke() 方法來調用具體對象的對應方法。
Android 中利用動態代理實現 ServiceHook
通過上面對 InvocationHandler 的介紹,我們對這個接口應該有了大體的了解,但是在運行時動態生成的代理類有什么作用呢,其實它的作用就是在調用真正業務之前或者之后插入一些額外的操作:</br>
</br>
所以簡而言之,代理類的處理邏輯很簡單,就是在調用某個方法前及方法后插入一些額外的業務。而我們在 Android 中的實踐例子就是在真正調用系統的某個 Service 之前和之后選擇性的做一些自己特殊的處理,這種思想在插件化框架上也是很重要的。那么我們具體怎么去實現 hook 系統的 Service ,在真正調用系統 Service 的時候附加上我們需要的業務呢,這就需要介紹 ServiceManager 這個類了。
ServiceManager 介紹以及 hook 的步驟
<font size=4>第一步</font></br>
關于 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。</br>
<font size=4>第二步</font></br>
第一步知道了如何去將改造過后的 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 的相關類圖:</br>
<ul><li>調用代碼中,Proxy.newProxyInstance 函數的第二個參數需要注意,由于 ClipboardService 繼承自三個接口,所以這里需要把所有的接口傳遞進去,但是如果將第二個參數變更為
new Class[] { IBinder.class }
其實也是沒有問題的(感興趣的可以試一下,第一個參數修改為 IBinder.class.getClassLoader(),第二個參數修改為 new Class[]{IBinder.class},也是可以的),因為實際使用的時候,我們只是用到了 IBinder 類的 queryLocalInterface 方法,其他的方法都沒有使用到,接下來我們就會說明 queryLocalInterface 這個函數的作用;</li><li>Proxy.newProxyInstance 函數的第三個參數為 ServcieHook 對象,所以當把這個 Service set 進 sCache 變量的時候,所有調用 ClipboardService 的操作都將調用到 ServiceHook 中的 invoke 方法中;</li><li>接著我們來看看 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 接口的其他方法;</li><li>接下來是 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 對象:</br>
最后也證實了這個結果,為什么是 Proxy 對象這就不用我解釋了吧;
</li><li>最后是 HookHandler 的 invoke 方法,這個方法調用到了 ClipboardHookHandler 對象,我們來看看這個類的實現:</br>
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 對象和參數列表對象,獲取到了這些之后,不用我說了,就可以盡情的去做一些額外的操作了,我這里是在仿照知乎復制文字時,在后面加上類似的版權聲明。
</li></ul>
問題討論
上面就是 ServiceHook 的詳細步驟了,了解它必須要對 InvocationHandler 有詳細的了解,并且還要去看一下 AOSP 源碼,比如要去 hook ClipboardService ,那么就要去先看看 ClipboardService 的源碼,看看這個類中每個函數的名字和作用,參數列表中每個參數的順序和作用,而且有時候這還遠遠不夠,我們知道,隨著 Android 每個版本的更新,這些類可能也會被更新修改甚至刪除,很有可能對于新版本來說老的 hook 方法就不管用了,這時候必須要去了解最新的源碼,看看更新修改的地方,針對新版本去重新制定 hook 的步驟,這是一點需要慎重對待考慮的地方。
步驟
源碼
此為我們公司某位大神代碼,經過整理修改而出,不知道有沒有版權問題,哈哈哈,謝謝周杰大神,雖然已經不在公司,感謝感謝~~</br>
源碼下載地址:https://github.com/zhaozepeng/ServiceHook</br>
先來看看運行的效果:</br>
最后把所有的代碼貼出來:</br>
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 的簡單使用而已。</br>
這里只演示了 hook ClipboardService 的例子,其他的使用方式比如插件化等等在這里就不一一介紹了,感興趣的可以去網上查閱相關的資料。
轉載請注明出處:http://blog.csdn.net/self_study/article/details/55050627
引用
http://blog.csdn.net/luanlouis/article/details/24589193
http://www.cnblogs.com/xiaoluo501395377/p/3383130.html
http://blog.csdn.net/self_study/article/details/51628486
http://paddy-w.iteye.com/blog/841798
http://www.cnblogs.com/flyoung2008/archive/2013/08/11/3251148.html