Android插件化與熱修復(三)---DroidPlugin Hook機制

1.DroidPlugin介紹

DroidPlugin 是Andy Zhang在Android系統(tǒng)上實現了一種新的 插件機制 :它可以在無需安裝、修改的情況下運行APK文件,此機制對改進大型APP的架構,實現多團隊協(xié)作開發(fā)具有一定的好處。
具體介紹可以詳見官方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接口,增加了動態(tài)代理相關的操作。一般Hook具體類都需要動態(tài)代理,所以一般都會直接繼承于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細節(jié)

IPackageManagerHookHandle

IPackageManagerHookHandle用來處理具體的hook細節(jié),在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在原始方法被調用之前執(zhí)行某些操作,
            如果返回true,說明這件事我來處理了,原始方法你不要管了,此時原始方法就不會被調用,
            如果返回false,原始方法會被調用
            */
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
             /*
            子類可以重寫afterInvoke在原始方法被調用之后執(zhí)行某些操作,
            */
            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;
    }

    /**
     * 在某個方法被調用之前執(zhí)行,如果返回true,則不執(zhí)行原始的方法,否則執(zhí)行原始方法
     */
    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方法在哪被調用呢,請繼續(xù)看下節(jié)。

ProxyHook -- 實現動態(tài)代理

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上執(zhí)行該方法,返回
            if (!isEnable()) {
                return method.invoke(mOldObj, args);
            }
            //mHookHandles里查找該方法對應的HookedMethodHandler對象
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            //如果有 就執(zhí)行hookedMethodHandler.doHookInner方法來具體實現對該方法的hook
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(mOldObj, method, args);
            }
            //如果沒有,說明該方法不需要被hook,直接在mOldObj上執(zhí)行該方法,返回
            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的過程就分析完了。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
【社區(qū)內容提示】社區(qū)部分內容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發(fā)布,文章內容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內容

友情鏈接更多精彩內容