DroidPlugin代码分析(一) 背景知识

来源:互联网 发布:mac桌面图标移动 编辑:程序博客网 时间:2024/04/29 18:43

前段时间360github上公开了DroidPlugin的代码,工作中也正好要用到类似的技术,于是打算花点时间研究一下。

在开始之前,首先需要了解一个概念:Java动态代理。这是实现hook的一个关键技术,在代码里被大量运用。那么什么是Java动态代理呢?下面以一个小例子进行说明。

首先我们定义一个IFruit接口,里面只有一个方法,用来打印水果的名字:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public interface IFruit {  
  2.     /** 
  3.      * 打印水果的名字 
  4.      */  
  5.     void printName();  
  6. }  

接下来写两个实现类,一个叫Apple,一个叫Banana

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class Apple implements IFruit {  
  2.     private final static String TAG = Apple.class.getSimpleName();  
  3.     private static String mName = Apple.class.getSimpleName();  
  4.     @Override  
  5.     public void printName() {  
  6.         Log.d(TAG, "I am " + mName);  
  7.     }  
  8. }  
  9.   
  10. public class Banana implements IFruit {  
  11.     private final static String TAG = Banana.class.getSimpleName();  
  12.     private static String mName = Banana.class.getSimpleName();  
  13.     @Override  
  14.     public void printName() {  
  15.         Log.d(TAG, "I am " + mName);  
  16.     }  
  17. }  

写完了,这时候突然需求变了,要求在这句打印的上面和下面再各打印一行”##########”。

我们该怎么改,是不是直接去修改AppleBanana类的实现呢?如果有100种水果是不是要改100次?另外,现在接口里只有一个方法,如果里面有50个方法,要求每个方法打印的前后都要加上一行”##########”,是不是每个水果都要改50遍?这简直是个噩梦。。。

幸好有Java动态代理可以完美解决这个问题。其实我们只要“劫持”或者叫“hook”住接口里每个方法的调用,在调用前和调用后各加上一行打印就可以了。

那么怎么“劫持”呢?首先写一个IFruitHook类,实现InvocationHandler接口。需要实现这个接口的invoke()方法:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. public class IFruitHook implements InvocationHandler {  
  2.     private final static String TAG = IFruitHook.class.getSimpleName();  
  3.     private Object mHookedObj;  
  4.   
  5.     public IFruitHook(Object hookedObj) {  
  6.         mHookedObj = hookedObj;  
  7.     }  
  8.   
  9.     @Override  
  10.     public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {  
  11.         Log.d(TAG, "##########");  
  12.         Object result = method.invoke(mHookedObj, args);  
  13.         Log.d(TAG, "##########");  
  14.   
  15.         return result;  
  16.     }  
  17. }  

可以看到,我们在接口每个方法调用的前后各加了一行打印。

接下来我们写一个方法获取代理对象,返回的接口还是IFruit,但是调用接口方法的时候就会调进上面那段代码了:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private IFruit getProxy(IFruit fruit) {  
  2.     return (IFruit) Proxy.newProxyInstance(  
  3.             fruit.getClass().getClassLoader(),  
  4.             fruit.getClass().getInterfaces(),  
  5.             new IFruitHook(fruit));  
  6. }  

最后我们来简单测试一下:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. IFruit apple = new Apple();  
  2. getProxy(apple).printName();  
  3. IFruit banana = new Banana();  
  4. getProxy(banana).printName();  

可以看到,打印结果的上面和下面各增加一行“##########”的打印。

回过头来再看看getProxy()这个方法做了什么?其实它通过Proxy.newProxyInstance()方法动态加载了一个叫做$Proxy0的类(该类extends Proxy implements IFruit),创建了一个该类的对象,并且注册了我们的IFruitHook。在调用这个对象的printName()方法时,它会把它自己、方法对象、参数都传入到IFruitHookinvoke()方法中:

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable

这样我们就可以在这个方法里进行各种处理了,甚至你可以不调用原始对象方法,全部替换成你自己的实现。

 

Java另一项强大的功能是反射,你可以在运行时动态修改类的实现。比如我们可以通过下面的方法把苹果的名字改成榴莲~~ 再次运行的时候你就会发现打印出来的是”I am Durian”:

[java] view plain copy
 在CODE上查看代码片派生到我的代码片
  1. private void changeFruitName() {  
  2.     try {  
  3.         Class<?> cls = Class.forName("com.xinxin.dynamicproxy.Apple");  
  4.         Field gQueryInterface = cls.getDeclaredField("mName");  
  5.         gQueryInterface.setAccessible(true);  
  6.         gQueryInterface.set(null"Durian");  
  7.     } catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {  
  8.         e.printStackTrace();  
  9.     }  
  10. }  

Java动态代理结合反射可以发挥更强大的作用,因为它可以篡改你传入的参数以及返回值!这个其实就是Droid Plugin“劫持”系统API的主要手段。举个例子:

我们启动activity的时候,最终会通过ActivityManagerNative.getDefault()获取一个全局的IActivityManager代理对象,然后通过binder远程调用到AMS那边。Droid Plugin会先通过反射替换掉这个全局对象,然后通过动态代理技术修改所有系统API实现。这样虽然外部的应用程序看起来还是调用的系统API,但是实际上内部已经被偷梁换柱了。

背景知识学习完毕,下一篇开始正式分析Droid Plugin代码~


转载自:http://blog.csdn.net/turkeycock/article/details/50959786

0 0