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);
}
...
}