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 类图

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<Class<?>> 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方法被调用的完整过程

代理方法调用流程

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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容