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

1.DroidPlugin介紹

DroidPlugin 是Andy Zhang在Android系統(tǒng)上實(shí)現(xiàn)了一種新的 插件機(jī)制 :它可以在無(wú)需安裝唐责、修改的情況下運(yùn)行APK文件,此機(jī)制對(duì)改進(jìn)大型APP的架構(gòu),實(shí)現(xiàn)多團(tuán)隊(duì)協(xié)作開發(fā)具有一定的好處巍实。
具體介紹可以詳見(jiàn)官方github介紹筒狠,這里不再贅述
https://github.com/DroidPluginTeam/DroidPlugin/blob/master/readme_cn.md

2.Hook機(jī)制

2.1 類圖

Hook機(jī)制 關(guān)鍵類

2.2 關(guān)鍵類介紹

Hook

抽象基類挺庞,定義了hook需要的一些基本操作讼稚,每一個(gè)被hook的類會(huì)對(duì)應(yīng)一個(gè)Hook具體類。

ProxyHook

ProxyHook extends Hook implements InvocationHandler
ProxyHook在Hook類的基礎(chǔ)上實(shí)現(xiàn)了InvocationHandler接口茧痒,增加了動(dòng)態(tài)代理相關(guān)的操作肮韧。一般Hook具體類都需要?jiǎng)討B(tài)代理,所以一般都會(huì)直接繼承于ProxyHook

BaseHookHandle

Handle是“處理”的意思文黎,所以BaseHookHandle定義了具體的hook操作惹苗。每一個(gè)Hook具體類會(huì)包含一個(gè)BaseHookHandle具體類作為類成員殿较,BaseHookHandle具體類里面定義了該Hook具體類需要進(jìn)行的具體的hook操作

HookedMethodHandler

如果你要hook一個(gè)類耸峭,這個(gè)類里面有多個(gè)方法需要被hook,這時(shí)候每個(gè)方法會(huì)對(duì)應(yīng)一個(gè)HookedMethodHandler類來(lái)定義如何去hook該方法。因?yàn)锽aseHookHandle是定義具體的hook操作的類淋纲,所以BaseHookHandle里會(huì)包含一個(gè)HookedMethodHandler的Map.

2.3 舉個(gè)栗子 Hook PackageManager

IPackageManagerHook

public class IPackageManagerHook extends ProxyHook {

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

首先會(huì)對(duì)應(yīng)一個(gè)Hook具體類 IPackageManagerHook 劳闹,IPackageManagerHook類里會(huì)包含一個(gè)BaseHookHandle具體類 IPackageManagerHookHandle用來(lái)處理具體的hook細(xì)節(jié)

IPackageManagerHookHandle

IPackageManagerHookHandle用來(lái)處理具體的hook細(xì)節(jié),在init方法里添加了所有被hook的方法對(duì)應(yīng)的HookedMethodHandler對(duì)象

    @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在原始方法被調(diào)用之前執(zhí)行某些操作洽瞬,
            如果返回true,說(shuō)明這件事我來(lái)處理了本涕,原始方法你不要管了,此時(shí)原始方法就不會(huì)被調(diào)用伙窃,
            如果返回false,原始方法會(huì)被調(diào)用
            */
            boolean suc = beforeInvoke(receiver, method, args);
            Object invokeResult = null;
            if (!suc) {
                invokeResult = method.invoke(receiver, args);
            }
             /*
            子類可以重寫afterInvoke在原始方法被調(diào)用之后執(zhí)行某些操作菩颖,
            */
            afterInvoke(receiver, method, args, invokeResult);
            //mUseFakedResult 為 true 說(shuō)明 該方法的返回值使用我偽造的結(jié)果--mFakedResult
            if (mUseFakedResult) {
                return mFakedResult;
            } else {//mUseFakedResult 為 false 說(shuō)明 該方法的返回值使用調(diào)用原始方法的返回結(jié)果--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;
    }

    /**
     * 在某個(gè)方法被調(diào)用之前執(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的包名,才需要進(jìn)行hook
                if (instance.isPluginPackage(pkg0) && instance.isPluginPackage(pkg1)) {
                    //調(diào)用了instance.checkSignatures來(lái)進(jìn)行簽名檢測(cè)
                    int result = instance.checkSignatures(pkg0, pkg1);
                    //設(shè)置偽造的結(jié)果 這樣checkSignatures方法的返回值會(huì)使用這個(gè)偽造的結(jié)果
                    setFakedResult(result);
                    //返回true,說(shuō)明這件事我來(lái)處理了鳍怨,原始方法你不要管了呻右,此時(shí)原始方法就不會(huì)被調(diào)用
                    return true;
                }
            }
            //如果包名不是我們的插件apk的包名,比如是宿主的包名鞋喇,此時(shí)就調(diào)用super.beforeInvoke 直接返回false声滥,
            // 此時(shí)會(huì)調(diào)用原始的方法,并且使用原始方法的返回值作為返回值侦香,就跟該方法沒(méi)有被hook一樣
            return super.beforeInvoke(receiver, method, args);
        }
    }

HookedMethodHandler.doHookInner方法在哪被調(diào)用呢落塑,請(qǐng)繼續(xù)看下節(jié)纽疟。

ProxyHook -- 實(shí)現(xiàn)動(dòng)態(tài)代理

public abstract class ProxyHook extends Hook implements InvocationHandler {

    protected Object mOldObj;

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

    /**
     * 設(shè)置被代理的原始的對(duì)象
     */
    public void setOldObj(Object oldObj) {
        this.mOldObj = oldObj;
    }

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

        try {
            //isEnable 返回false 說(shuō)明該hook被設(shè)置為不生效,此時(shí)直接在mOldObj上執(zhí)行該方法憾赁,返回
            if (!isEnable()) {
                return method.invoke(mOldObj, args);
            }
            //mHookHandles里查找該方法對(duì)應(yīng)的HookedMethodHandler對(duì)象
            HookedMethodHandler hookedMethodHandler = mHookHandles.getHookedMethodHandler(method);
            //如果有 就執(zhí)行hookedMethodHandler.doHookInner方法來(lái)具體實(shí)現(xiàn)對(duì)該方法的hook
            if (hookedMethodHandler != null) {
                return hookedMethodHandler.doHookInner(mOldObj, method, args);
            }
            //如果沒(méi)有仰挣,說(shuō)明該方法不需要被hook,直接在mOldObj上執(zhí)行該方法,返回
            return method.invoke(mOldObj, args);
        } catch (Exception e) {
            //一些異常處理 略
        } 
    }
}

IPackageManagerHook --用代理對(duì)象替換原始的PackageManager對(duì)象

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();
        //從主線程對(duì)象里通過(guò)反射拿到sPackageManager對(duì)象缠沈,作為原始對(duì)象賦值給mOldObj
        setOldObj(FieldUtils.readField(currentActivityThread, "sPackageManager"));
        Class<?> iPmClass = mOldObj.getClass();
        //生成代理對(duì)象
        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);
        //用代理對(duì)象替換原始對(duì)象
        FieldUtils.writeField(currentActivityThread, "sPackageManager", newPm);
        //調(diào)用宿主的context的getPackageManager獲取PackageManager對(duì)象
        PackageManager pm = mHostContext.getPackageManager();
        Object mPM = FieldUtils.readField(pm, "mPM");
        //如果該對(duì)象不是我們的代理對(duì)象膘壶,就把該對(duì)象也替換成我們的代理對(duì)象
        if (mPM != newPm) {
            FieldUtils.writeField(pm, "mPM", newPm);
        }
    }

}

IPackageManagerHook對(duì)象的onInstall方法會(huì)在插件框架被安裝的時(shí)候調(diào)用。

被Hook的checkSignatures方法被調(diào)用的完整過(guò)程

代理方法調(diào)用流程

至此洲愤,整個(gè)Hook的過(guò)程就分析完了颓芭。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市柬赐,隨后出現(xiàn)的幾起案子亡问,更是在濱河造成了極大的恐慌,老刑警劉巖肛宋,帶你破解...
    沈念sama閱讀 212,454評(píng)論 6 493
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件州藕,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡酝陈,警方通過(guò)查閱死者的電腦和手機(jī)床玻,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,553評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)沉帮,“玉大人锈死,你說(shuō)我怎么就攤上這事∧潞荆” “怎么了待牵?”我有些...
    開封第一講書人閱讀 157,921評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)喇勋。 經(jīng)常有香客問(wèn)我缨该,道長(zhǎng),這世上最難降的妖魔是什么川背? 我笑而不...
    開封第一講書人閱讀 56,648評(píng)論 1 284
  • 正文 為了忘掉前任贰拿,我火速辦了婚禮,結(jié)果婚禮上渗常,老公的妹妹穿的比我還像新娘壮不。我一直安慰自己,他們只是感情好皱碘,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,770評(píng)論 6 386
  • 文/花漫 我一把揭開白布询一。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪健蕊。 梳的紋絲不亂的頭發(fā)上菱阵,一...
    開封第一講書人閱讀 49,950評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音缩功,去河邊找鬼晴及。 笑死,一個(gè)胖子當(dāng)著我的面吹牛嫡锌,可吹牛的內(nèi)容都是我干的虑稼。 我是一名探鬼主播,決...
    沈念sama閱讀 39,090評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼势木,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蛛倦!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起啦桌,我...
    開封第一講書人閱讀 37,817評(píng)論 0 268
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤溯壶,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后甫男,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體且改,經(jīng)...
    沈念sama閱讀 44,275評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,592評(píng)論 2 327
  • 正文 我和宋清朗相戀三年板驳,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了又跛。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,724評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡笋庄,死狀恐怖效扫,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情直砂,我是刑警寧澤,帶...
    沈念sama閱讀 34,409評(píng)論 4 333
  • 正文 年R本政府宣布浩习,位于F島的核電站静暂,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏谱秽。R本人自食惡果不足惜洽蛀,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,052評(píng)論 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望疟赊。 院中可真熱鬧郊供,春花似錦、人聲如沸近哟。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,815評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至疯淫,卻和暖如春地来,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背熙掺。 一陣腳步聲響...
    開封第一講書人閱讀 32,043評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工未斑, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人币绩。 一個(gè)月前我還...
    沈念sama閱讀 46,503評(píng)論 2 361
  • 正文 我出身青樓蜡秽,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親缆镣。 傳聞我的和親對(duì)象是個(gè)殘疾皇子载城,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,627評(píng)論 2 350

推薦閱讀更多精彩內(nèi)容