Android插件化与热修复(三)---DroidPlugin Hook机制

1.DroidPlugin介绍

DroidPlugin 是Andy Zhang在Android系统上实现了一种新的 插件机制 :它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。
具体介绍可以详见官方github介绍,这里不再赘述
https://github.com/DroidPluginTeam/DroidPlugin/blob/master/readme_cn.md

2.Hook机制

2.1 类图

Android插件化与热修复(三)---DroidPlugin Hook机制_第1张图片
Hook机制 关键类

2.2 关键类介绍

Hook

抽象基类,定义了hook需要的一些基本操作,每一个被hook的类会对应一个Hook具体类。

ProxyHook

ProxyHook extends Hook implements InvocationHandler
ProxyHook在Hook类的基础上实现了InvocationHandler接口,增加了动态代理相关的操作。一般Hook具体类都需要动态代理,所以一般都会直接继承于ProxyHook

BaseHookHandle

Handle是“处理”的意思,所以BaseHookHandle定义了具体的hook操作。每一个Hook具体类会包含一个BaseHookHandle具体类作为类成员,BaseHookHandle具体类里面定义了该Hook具体类需要进行的具体的hook操作

HookedMethodHandler

如果你要hook一个类,这个类里面有多个方法需要被hook,这时候每个方法会对应一个HookedMethodHandler类来定义如何去hook该方法。因为BaseHookHandle是定义具体的hook操作的类,所以BaseHookHandle里会包含一个HookedMethodHandler的Map.

2.3 举个栗子 Hook PackageManager

IPackageManagerHook

public class IPackageManagerHook extends ProxyHook {

    @Override
    protected BaseHookHandle createHookHandle() {
        return new IPackageManagerHookHandle(mHostContext);
    }
    
}

首先会对应一个Hook具体类 IPackageManagerHook ,IPackageManagerHook类里会包含一个BaseHookHandle具体类 IPackageManagerHookHandle用来处理具体的hook细节

IPackageManagerHookHandle

IPackageManagerHookHandle用来处理具体的hook细节,在init方法里添加了所有被hook的方法对应的HookedMethodHandler对象

    @Override
    protected void init() {
        sHookedMethodHandlers.put("getPackageInfo", new getPackageInfo(mHostContext));
        sHookedMethodHandlers.put("getPackageUid", new getPackageUid(mHostContext));
        sHookedMethodHandlers.put("getPackageGids", new getPackageGids(mHostContext));
        sHookedMethodHandlers.put("currentToCanonicalPackageNames", new currentToCanonicalPackageNames(mHostContext));
//……

    }

分析HookedMethodHandler -- 以被Hook的 "checkSignatures"方法为例

首先看看HookedMethodHandler
public class HookedMethodHandler {

    private static final String TAG = HookedMethodHandler.class.getSimpleName();
    protected final Context mHostContext;
    
    private Object mFakedResult = null;
    private boolean mUseFakedResult = false;
    
    public HookedMethodHandler(Context hostContext) {
        this.mHostContext = hostContext;
    }
    
    public synchronized Object doHookInner(Object receiver, Method method, Object[] args) throws Throwable {
        long b = System.currentTimeMillis();
        try {
            mUseFakedResult = false;
            mFakedResult = null;
            /*
            子类可以重写beforeInvoke在原始方法被调用之前执行某些操作,
            如果返回true,说明这件事我来处理了,原始方法你不要管了,此时原始方法就不会被调用,
            如果返回false,原始方法会被调用
            */
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
             /*
            子类可以重写afterInvoke在原始方法被调用之后执行某些操作,
            */
            afterInvoke(receiver, method, args, invokeResult);
            //mUseFakedResult 为 true 说明 该方法的返回值使用我伪造的结果--mFakedResult
            if (mUseFakedResult) {
                return mFakedResult;
            } else {//mUseFakedResult 为 false 说明 该方法的返回值使用调用原始方法的返回结果--invokeResult
                return invokeResult;
            }
        } finally {
            long time = System.currentTimeMillis() - b;
            if (time > 5) {
                Log.i(TAG, "doHookInner method(%s.%s) cost %s ms", method.getDeclaringClass().getName(), method.getName(), time);
            }
        }
    }

    public void setFakedResult(Object fakedResult) {
        this.mFakedResult = fakedResult;
        mUseFakedResult = true;
    }

    /**
     * 在某个方法被调用之前执行,如果返回true,则不执行原始的方法,否则执行原始方法
     */
    protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
        return false;
    }

    protected void afterInvoke(Object receiver, Method method, Object[] args, Object invokeResult) throws Throwable {
    }

    public boolean isFakedResult() {
        return mUseFakedResult;
    }

    public Object getFakedResult() {
        return mFakedResult;
    }
}
checkSignatures
    private class checkSignatures extends HookedMethodHandler {
        public checkSignatures(Context context) {
            super(context);
        }

        @Override
        protected boolean beforeInvoke(Object receiver, Method method, Object[] args) throws Throwable {
            //API 2.3, 4.01_r1, 4.0.3_r1, 4.1.1_r1, 4.2_r1, 4.3_r1, 4.4_r1, 5.0.2_r1
        /* public int checkSignatures(String pkg1, String pkg2) throws android.os.RemoteException;*/

        //上面的注释是作者写的被hook的方法的声明
            final int index0 = 0, index1 = 1;
            String pkg0 = null, pkg1 = null;
            if (args != null && args[index0] != null && args[index0] instanceof String) {
                pkg0 = (String) args[index0];
            }

            if (args != null && args[index1] != null && args[index1] instanceof String) {
                pkg1 = (String) args[index1];
            }

            if (!TextUtils.isEmpty(pkg0) && !TextUtils.isEmpty(pkg1)) {
                PluginManager instance = PluginManager.getInstance();
                //如果包名是我们的插件apk的包名,才需要进行hook
                if (instance.isPluginPackage(pkg0) && instance.isPluginPackage(pkg1)) {
                    //调用了instance.checkSignatures来进行签名检测
                    int result = instance.checkSignatures(pkg0, pkg1);
                    //设置伪造的结果 这样checkSignatures方法的返回值会使用这个伪造的结果
                    setFakedResult(result);
                    //返回true,说明这件事我来处理了,原始方法你不要管了,此时原始方法就不会被调用
                    return true;
                }
            }
            //如果包名不是我们的插件apk的包名,比如是宿主的包名,此时就调用super.beforeInvoke 直接返回false,
            // 此时会调用原始的方法,并且使用原始方法的返回值作为返回值,就跟该方法没有被hook一样
            return super.beforeInvoke(receiver, method, args);
        }
    }

HookedMethodHandler.doHookInner方法在哪被调用呢,请继续看下节。

ProxyHook -- 实现动态代理

public abstract class ProxyHook extends Hook implements InvocationHandler {

    protected Object mOldObj;

    public ProxyHook(Context hostContext) {
        super(hostContext);
    }

    /**
     * 设置被代理的原始的对象
     */
    public void setOldObj(Object oldObj) {
        this.mOldObj = oldObj;
    }

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

        try {
            //isEnable 返回false 说明该hook被设置为不生效,此时直接在mOldObj上执行该方法,返回
            if (!isEnable()) {
                return method.invoke(mOldObj, args);
            }
            //mHookHandles里查找该方法对应的HookedMethodHandler对象
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            //如果有 就执行hookedMethodHandler.doHookInner方法来具体实现对该方法的hook
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(mOldObj, method, args);
            }
            //如果没有,说明该方法不需要被hook,直接在mOldObj上执行该方法,返回
            return method.invoke(mOldObj, args);
        } catch (Exception e) {
            //一些异常处理 略
        } 
    }
}

IPackageManagerHook --用代理对象替换原始的PackageManager对象

public class IPackageManagerHook extends ProxyHook {

    private static final String TAG = IPackageManagerHook.class.getSimpleName();

    public IPackageManagerHook(Context hostContext) {
        super(hostContext);
    }

    @Override
    protected BaseHookHandle createHookHandle() {
        return new IPackageManagerHookHandle(mHostContext);
    }

    @Override
    protected void onInstall(ClassLoader classLoader) throws Throwable {
        
        Object currentActivityThread = ActivityThreadCompat.currentActivityThread();
        //从主线程对象里通过反射拿到sPackageManager对象,作为原始对象赋值给mOldObj
        setOldObj(FieldUtils.readField(currentActivityThread, "sPackageManager"));
        Class iPmClass = mOldObj.getClass();
        //生成代理对象
        List> interfaces = Utils.getAllInterfaces(iPmClass);
        Class[] ifs = interfaces != null && interfaces.size() > 0 ? interfaces.toArray(new Class[interfaces.size()]) : new Class[0];
        Object newPm = MyProxy.newProxyInstance(iPmClass.getClassLoader(), ifs, this);
        //用代理对象替换原始对象
        FieldUtils.writeField(currentActivityThread, "sPackageManager", newPm);
        //调用宿主的context的getPackageManager获取PackageManager对象
        PackageManager pm = mHostContext.getPackageManager();
        Object mPM = FieldUtils.readField(pm, "mPM");
        //如果该对象不是我们的代理对象,就把该对象也替换成我们的代理对象
        if (mPM != newPm) {
            FieldUtils.writeField(pm, "mPM", newPm);
        }
    }

}

IPackageManagerHook对象的onInstall方法会在插件框架被安装的时候调用。

被Hook的checkSignatures方法被调用的完整过程

Android插件化与热修复(三)---DroidPlugin Hook机制_第2张图片
代理方法调用流程

至此,整个Hook的过程就分析完了。

你可能感兴趣的:(Android插件化与热修复(三)---DroidPlugin Hook机制)