Condom學(xué)習(xí)筆記

Condom是用于防止第三方喚醒行為的框架,比較小眾,這里做下最近使用和學(xué)習(xí)筆記。

基本操作

Condom 提供基礎(chǔ)兩個(gè)操作。
提供給第三方Context包裝,監(jiān)控在本進(jìn)程中的喚醒行為。

XxxClient.init(CondomContext.wrap(context, "XxxSDK"),...);

提供進(jìn)程隔離喚醒處理

CondomProcess.installExceptDefaultProcess(this)

另外提供完全自定義隔離入口

CondomProcess.installExcept(final Application app, final CondomOptions options, final String... process_names)

主要結(jié)構(gòu)說(shuō)明

Condom框架功能結(jié)構(gòu)上比較清晰。以下為記錄的主要功能說(shuō)明

類(lèi)名 功能說(shuō)明 備注
CondomCore 提供主要攔截驗(yàn)證邏輯
CondomContext Context代理,用于處理Context方法相關(guān)調(diào)用攔截,其中實(shí)現(xiàn)了內(nèi)部的代理類(lèi)CondomPackageManger(代理 PackageManager)、CondomContentResolver(代理ContentResolver)
CondomProcess 處理非主要進(jìn)程的進(jìn)程隔離,通過(guò)動(dòng)態(tài)代理代理ActivityManager和PackageManager對(duì)象
CondomOptions 提供驗(yàn)證配置邏輯,在CondomContext.wrap和CondomProcess.installExcept 中可以對(duì)兩個(gè)入口進(jìn)行校驗(yàn)配置

CondomOptions配置項(xiàng)說(shuō)明

方法 說(shuō)明 備注
preventBroadcastToBackgroundPackages(final boolean prevent_or_not) 進(jìn)行查詢(xún)啟動(dòng)Broadcast/Receivers動(dòng)作時(shí)是否需要考慮添加FLAG_RECEIVER_EXCLUDE_BACKGROUND(FLAG_RECEIVER_REGISTERED_ONLY)flag,排除后臺(tái)或強(qiáng)制殺死應(yīng)用
preventServiceInBackgroundPackages(final boolean prevent_or_not) 進(jìn)行查詢(xún)校驗(yàn)時(shí)是否排除非正常啟動(dòng)應(yīng)用(僵尸進(jìn)程也不算,進(jìn)程等級(jí)需要高于IMPORTANCE_BACKGROUND)
setOutboundJudge(final OutboundJudge judge) 設(shè)置一個(gè)在進(jìn)行外部喚醒時(shí)的自定義校驗(yàn)
CondomOptions setDryRun(final boolean dry_run) 設(shè)置為true將忽略校驗(yàn)處理(即使校驗(yàn)不通過(guò),還是會(huì)無(wú)條件按正常流進(jìn)行),比如查詢(xún)結(jié)果過(guò)濾

OutboundType

OutboundType提供發(fā)生校驗(yàn)時(shí)所進(jìn)行的操作,結(jié)合OutboundJudge使用可以精確自定義校驗(yàn)處理。
但是發(fā)生自定義校驗(yàn)僅限于所發(fā)生的行為是針對(duì)非項(xiàng)目?jī)?nèi)的的操作才會(huì)執(zhí)行該回調(diào),比如所執(zhí)行startService中Intent的packagename為非本項(xiàng)目。

原理探索

驗(yàn)證器 - CondomCore

CondomCore主要負(fù)責(zé)驗(yàn)證邏輯,無(wú)論在哪種攔截處都需要執(zhí)行這里的驗(yàn)證邏輯,所以需要先熟悉這里面的邏輯。

主要攔截操作

方法 說(shuō)明 備注
processd(final OutboundType type, final Intent intent, final @Nullable R negative_value,final CondomCore.WrappedValueProcedureThrows<R, T> procedure) 處理基本入口,這里負(fù)責(zé)兩件事情:
(1)判定所請(qǐng)求的Intent目標(biāo)包名是否為項(xiàng)目包名,如果一致則忽略驗(yàn)證。
(2)驗(yàn)證自定義攔截 OutboundJudge.showldAllow
(3)調(diào)整Intent Flag 模式,參照 adjustIntentFlags 說(shuō)明。
proceedBroadcast(final Intent intent, final CondomCore.WrappedProcedure procedure) proceed(OutboundType.BROADCAST, intent, null, procedure)代理
proceedQuery(final OutboundType type, final Intent intent, final CondomCore.WrappedValueProcedureThrows<List<ResolveInfo>, T> procedure) 依然是proceed代理,在WrappedProcedure回調(diào)中攔截查詢(xún)所得的ResolveInfo結(jié)果,并進(jìn)行OutboundJudge.showldAllow回調(diào)查詢(xún)是否過(guò)濾。

調(diào)整操作

方法 說(shuō)明 備注
adjustIntentFlags(final OutboundType type, final Intent intent) (1)根據(jù)CondomOptions.mExcludeBackgroundReceivers 參數(shù)決定是否在調(diào)用前對(duì)Intent添加 FLAG_RECEIVER_EXCLUDE_BACKGROUND(FLAG_RECEIVER_REGISTERED_ONLY)flag,該標(biāo)記意味著如果沒(méi)有顯示設(shè)置package name則如果應(yīng)用被強(qiáng)制殺死或者未啟動(dòng)過(guò)則不會(huì)執(zhí)行廣播接收
(2)根據(jù)CondomOptions.mExcludeStoppedPackages 參數(shù)決定是否在調(diào)用前對(duì)Intent移除 FLAG_INCLUDE_STOPPED_PACKAGES(添加FLAG_EXCLUDE_STOPPED_PACKAGES)該標(biāo)記意味著如果應(yīng)用被強(qiáng)制殺死或者未啟動(dòng)過(guò)則不會(huì)執(zhí)行
filterCandidates(final OutboundType type, final Intent original_intent, final @Nullable List<ResolveInfo> candidates, final String tag, final boolean remove) 用于對(duì)ResolveInfo結(jié)果查詢(xún)過(guò)濾,這里會(huì)根據(jù)remove 參數(shù)有兩個(gè)操作(公用的原因不知)
(1)如果remove被設(shè)置true則在如果所查詢(xún)的ResolveInfo的uid和當(dāng)前項(xiàng)目uid不一致則會(huì)被從結(jié)果中過(guò)濾移除。
(2)如果被置為false則會(huì)范圍第一個(gè)復(fù)合條件的ResolveInfo
在不符合uid不為主項(xiàng)目的uid則執(zhí)行驗(yàn)證流程中同時(shí)加入對(duì)OutboundJudge.showldAllow和mExcludeBackgroundServices的驗(yàn)證,如果符合則也視為通過(guò)驗(yàn)證。
shouldAllowProvider(final @Nullable ProviderInfo provider) 過(guò)濾ProviderInfo信息。
(1)查詢(xún)結(jié)果是否為主項(xiàng)目包名信息
(2)驗(yàn)證自定義自定義攔截OutboundJudge.showldAllow
(3)驗(yàn)證查詢(xún)信息應(yīng)用是否非系統(tǒng)應(yīng)用(FLAG_SYSTEM)并且不處于FLAG_STOPPED狀態(tài)

結(jié)論

在核心操作中,可以知道Condom主要在兩個(gè)方面進(jìn)行攔截。

調(diào)用時(shí)驗(yàn)證

在Intent執(zhí)行調(diào)用時(shí),對(duì)Intent信息進(jìn)行監(jiān)控,如果所指向的對(duì)象為關(guān)聯(lián)項(xiàng)目(非其他無(wú)關(guān)應(yīng)用)則視為合法操作,這樣可以防止啟動(dòng)其他無(wú)關(guān)項(xiàng)目,另外為了保證效果在啟動(dòng)的Intent又加入對(duì)應(yīng)的限制性Flag,防止后臺(tái)已經(jīng)被關(guān)閉停止的應(yīng)用被啟動(dòng)。

查詢(xún)后驗(yàn)證

在對(duì)系統(tǒng)、包信息等查詢(xún)信息動(dòng)作時(shí),除了在查詢(xún)動(dòng)作前進(jìn)行攔截外(有些操作需要Intent還是會(huì)被第一項(xiàng)所約束),還會(huì)執(zhí)行對(duì)查詢(xún)結(jié)果的過(guò)濾。如果所查詢(xún)應(yīng)用的uid為非相關(guān)應(yīng)用則會(huì)被過(guò)濾,或者在非相關(guān)uid查詢(xún)操作后,根據(jù)自定義過(guò)濾動(dòng)作進(jìn)行過(guò)濾。

攔截器 - CondomContext 、CondomProcess

驗(yàn)證邏輯有了,剩下的就是尋找切面時(shí)機(jī)。從驗(yàn)證邏輯中可以清楚知道驗(yàn)證所針對(duì)的功能目標(biāo)是啟動(dòng)行為和查詢(xún)行為。

CondomContext 提供兩個(gè)入口:

包裝Context

在大部分情況下第三方應(yīng)用如果需要在主項(xiàng)目中執(zhí)行調(diào)用、查詢(xún)行為,大部分都是使用Context引用進(jìn)行相關(guān)操作,所以較多第三方SDK初始化的第一步就是需要主項(xiàng)目傳入其Context。
應(yīng)對(duì)這種方式,Condom提供對(duì)需要提供給第三方框架Context進(jìn)行包裝的方法。

CondomContext.wrap(Context, String)

通過(guò)代理Context(CondomContext)來(lái)監(jiān)控方法調(diào)用情況。

CondomContext的包裝Context實(shí)現(xiàn)均來(lái)自CondomContext自身構(gòu)造中

private CondomContext(final CondomCore condom, final @Nullable Context app_context, final @Nullable @Size(max=16) String tag) {
        super(condom.mBase);
        final Context base = condom.mBase;
        mCondom = condom;
        mApplicationContext = app_context != null ? app_context : this;
        mBaseContext = new Lazy<Context>() { @Override protected Context create() {
            return new PseudoContextImpl(CondomContext.this);
        }};
        mPackageManager = new Lazy<PackageManager>() { @Override protected PackageManager create() {
            return new CondomPackageManager(base.getPackageManager());
        }};
        mContentResolver = new Lazy<ContentResolver>() { @Override protected ContentResolver create() {
            return new CondomContentResolver(base, base.getContentResolver());
        }};
        TAG = CondomCore.buildLogTag("Condom", "Condom.", tag);
    }

在構(gòu)造中對(duì)PackageManager、ContentResolver、BaseContext、Applicaiton 又進(jìn)行重新包裝并在CondomContext中重寫(xiě)Context的相關(guān)實(shí)現(xiàn)。在代理類(lèi)中實(shí)現(xiàn)對(duì)方法執(zhí)行的調(diào)用校驗(yàn)。

    @Override public ContentResolver getContentResolver() { return mContentResolver.get(); }
    @Override public PackageManager getPackageManager() { return mPackageManager.get(); }
    @Override public Context getApplicationContext() { return mApplicationContext; }
    @Override public Context getBaseContext() {
        mCondom.logConcern(TAG, "getBaseContext");
        return mBaseContext.get();
    }

進(jìn)程隔離

除了包裝層面,Condom還提供進(jìn)程隔離的處理,通過(guò)動(dòng)態(tài)代理,將 ActivityManagerNative 中的 gDefault(IActivityManager) 和 ActivityThread中的sPackageManager(IPackageManager)進(jìn)行代理

以下為相關(guān)代碼:

動(dòng)態(tài)代理ActivityManager

@SuppressLint("PrivateApi") private static void installCondomProcessActivityManager(final CondomCore condom)
            throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException {
        final Class<?> ActivityManagerNative = Class.forName("android.app.ActivityManagerNative");
        Field ActivityManagerNative_gDefault = null;
        if (SDK_INT <= N_MR1) try {
            ActivityManagerNative_gDefault = ActivityManagerNative.getDeclaredField("gDefault");
        } catch (final NoSuchFieldException ignored) {}     // ActivityManagerNative.gDefault is no longer available on Android O.
        if (ActivityManagerNative_gDefault == null) {
            ActivityManagerNative_gDefault = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
        }
        ActivityManagerNative_gDefault.setAccessible(true);
        final Class<?> Singleton = Class.forName("android.util.Singleton");
        final Method Singleton_get = Singleton.getDeclaredMethod("get");
        Singleton_get.setAccessible(true);
        final Field Singleton_mInstance = Singleton.getDeclaredField("mInstance");
        Singleton_mInstance.setAccessible(true);
        final Class<?> IActivityManager = Class.forName("android.app.IActivityManager");

        final Object/* Singleton */singleton = ActivityManagerNative_gDefault.get(null);
        if (singleton == null) throw new IllegalStateException("ActivityManagerNative.gDefault is null");
        final Object/* IActivityManager */am = Singleton_get.invoke(singleton);
        if (am == null) throw new IllegalStateException("ActivityManagerNative.gDefault.get() returns null");

        if (Proxy.isProxyClass(am.getClass()) && Proxy.getInvocationHandler(am) instanceof CondomProcessActivityManager) {
            Log.d(TAG, "CondomActivityManager is already installed in this process.");
            return;
        }

        final Object condom_am = Proxy.newProxyInstance(condom.mBase.getClassLoader(), new Class[] {IActivityManager}, new CondomProcessActivityManager(condom, am));
        Singleton_mInstance.set(singleton, condom_am);
    }

動(dòng)態(tài)代理 PackageManager

@SuppressLint("PrivateApi") private static void installCondomProcessPackageManager(final CondomCore condom)
            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        final Class<?> ActivityThread = Class.forName("android.app.ActivityThread");
        final Field ActivityThread_sPackageManager = ActivityThread.getDeclaredField("sPackageManager");
        ActivityThread_sPackageManager.setAccessible(true);
        final Class<?> IPackageManager = Class.forName("android.content.pm.IPackageManager");

        final Object pm = ActivityThread_sPackageManager.get(null);
        if (Proxy.isProxyClass(pm.getClass()) && Proxy.getInvocationHandler(pm) instanceof CondomProcessPackageManager) {
            Log.d(TAG, "CondomPackageManager is already installed in this process.");
            return;
        }

        final Object condom_pm = Proxy.newProxyInstance(condom.mBase.getClassLoader(), new Class[] {IPackageManager}, new CondomProcessPackageManager(condom, pm));
        ActivityThread_sPackageManager.set(null, condom_pm);
    }

同樣的既然是動(dòng)態(tài)代理也需要實(shí)現(xiàn)其代理類(lèi)

    @VisibleForTesting static class CondomProcessActivityManager extends CondomSystemService {

        private Object proceed(final Object proxy, final Method method, final Object[] args) throws Exception {
            ...
            case "broadcastIntent":
                ...
            case "bindService":
                ...
            case "startService":
                ...
            case "getContentProvider":
                ...
            }
            return super.invoke(proxy, method, args);
        }

        ...
    }
@VisibleForTesting static class CondomProcessPackageManager extends CondomSystemService {

        private Object proceed(final Object proxy, final Method method, final Object[] args) throws Exception {
            ...
            case "queryIntentServices":
                ...
            case "resolveService":
                ...
            case "resolveContentProvider":
                ...
            case "getInstalledApplications":
            case "getInstalledPackages":
                ...
            }
            return super.invoke(proxy, method, args);
        }

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

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容