前段时间360在github上公开了DroidPlugin的代码,工作中也正好要用到类似的技术,于是打算花点时间研究一下。
在开始之前,首先需要了解一个概念:Java动态代理。这是实现hook的一个关键技术,在代码里被大量运用。那么什么是Java动态代理呢?下面以一个小例子进行说明。
首先我们定义一个IFruit接口,里面只有一个方法,用来打印水果的名字:
public interface IFruit {
/**
* 打印水果的名字
*/
void printName();
}
接下来写两个实现类,一个叫Apple,一个叫Banana:
public class Apple implements IFruit {
private final static String TAG = Apple.class.getSimpleName();
private static String mName = Apple.class.getSimpleName();
@Override
public void printName() {
Log.d(TAG, "I am " + mName);
}
}
public class Banana implements IFruit {
private final static String TAG = Banana.class.getSimpleName();
private static String mName = Banana.class.getSimpleName();
@Override
public void printName() {
Log.d(TAG, "I am " + mName);
}
}
写完了,这时候突然需求变了,要求在这句打印的上面和下面再各打印一行”##########”。
我们该怎么改,是不是直接去修改Apple和Banana类的实现呢?如果有100种水果是不是要改100次?另外,现在接口里只有一个方法,如果里面有50个方法,要求每个方法打印的前后都要加上一行”##########”,是不是每个水果都要改50遍?这简直是个噩梦。。。
幸好有Java动态代理可以完美解决这个问题。其实我们只要“劫持”或者叫“hook”住接口里每个方法的调用,在调用前和调用后各加上一行打印就可以了。
那么怎么“劫持”呢?首先写一个IFruitHook类,实现InvocationHandler接口。需要实现这个接口的invoke()方法:
public class IFruitHook implements InvocationHandler {
private final static String TAG = IFruitHook.class.getSimpleName();
private Object mHookedObj;
public IFruitHook(Object hookedObj) {
mHookedObj = hookedObj;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.d(TAG, "##########");
Object result = method.invoke(mHookedObj, args);
Log.d(TAG, "##########");
return result;
}
}
可以看到,我们在接口每个方法调用的前后各加了一行打印。
接下来我们写一个方法获取代理对象,返回的接口还是IFruit,但是调用接口方法的时候就会调进上面那段代码了:
private IFruit getProxy(IFruit fruit) {
return (IFruit) Proxy.newProxyInstance(
fruit.getClass().getClassLoader(),
fruit.getClass().getInterfaces(),
new IFruitHook(fruit));
}
最后我们来简单测试一下:
IFruit apple = new Apple();
getProxy(apple).printName();
IFruit banana = new Banana();
getProxy(banana).printName();
可以看到,打印结果的上面和下面各增加一行“##########”的打印。
回过头来再看看getProxy()这个方法做了什么?其实它通过Proxy.newProxyInstance()方法动态加载了一个叫做$Proxy0的类(该类extends Proxy implements IFruit),创建了一个该类的对象,并且注册了我们的IFruitHook。在调用这个对象的printName()方法时,它会把它自己、方法对象、参数都传入到IFruitHook的invoke()方法中:
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable
这样我们就可以在这个方法里进行各种处理了,甚至你可以不调用原始对象方法,全部替换成你自己的实现。
Java另一项强大的功能是反射,你可以在运行时动态修改类的实现。比如我们可以通过下面的方法把苹果的名字改成榴莲~~ 再次运行的时候你就会发现打印出来的是”I am Durian”:
private void changeFruitName() {
try {
Class<?> cls = Class.forName("com.xinxin.dynamicproxy.Apple");
Field gQueryInterface = cls.getDeclaredField("mName");
gQueryInterface.setAccessible(true);
gQueryInterface.set(null, "Durian");
} catch (ClassNotFoundException | NoSuchFieldException | IllegalAccessException e) {
e.printStackTrace();
}
}
Java动态代理结合反射可以发挥更强大的作用,因为它可以篡改你传入的参数以及返回值!这个其实就是Droid Plugin“劫持”系统API的主要手段。举个例子:
我们启动activity的时候,最终会通过ActivityManagerNative.getDefault()获取一个全局的IActivityManager代理对象,然后通过binder远程调用到AMS那边。Droid Plugin会先通过反射替换掉这个全局对象,然后通过动态代理技术修改所有系统API实现。这样虽然外部的应用程序看起来还是调用的系统API,但是实际上内部已经被偷梁换柱了。
背景知识学习完毕,下一篇开始正式分析Droid Plugin代码~