減少入侵性
前言
- 上次我們寫的代碼會有下面這樣的問題糯累,我們看到四大組件獲的的application是殼程序里的application刹碾,那這樣殼程序是不能提供別人使用的,所以這次講的是怎么減少這樣的入侵
-
那就是找到 application的加載過程旬薯,然后把真實的application替換成原包里設置的
-
先上兩張圖
- 上面兩張圖是用戶點擊桌面app啟動的流程般又,可以參考Activity啟動流程 最后會調(diào)用 bindApplication 方法
public final void bindApplication(String processName, ApplicationInfo appInfo,
List<ProviderInfo> providers, ComponentName instrumentationName,
ProfilerInfo profilerInfo, Bundle instrumentationArgs,
IInstrumentationWatcher instrumentationWatcher,
IUiAutomationConnection instrumentationUiConnection, int debugMode,
boolean enableBinderTracking, boolean trackAllocation,
boolean isRestrictedBackupMode, boolean persistent, Configuration config,
CompatibilityInfo compatInfo, Map services, Bundle coreSettings,
String buildSerial, boolean autofillCompatibilityEnabled) {
if (services != null) {
if (false) {
// Test code to make sure the app could see the passed-in services.
for (Object oname : services.keySet()) {
if (services.get(oname) == null) {
continue; // AM just passed in a null service.
}
String name = (String) oname;
// See b/79378449 about the following exemption.
switch (name) {
case "package":
case Context.WINDOW_SERVICE:
continue;
}
if (ServiceManager.getService(name) == null) {
Log.wtf(TAG, "Service " + name + " should be accessible by this app");
}
}
}
// Setup the service cache in the ServiceManager
ServiceManager.initServiceCache(services);
}
setCoreSettings(coreSettings);
AppBindData data = new AppBindData();
data.processName = processName;
data.appInfo = appInfo;
data.providers = providers;
data.instrumentationName = instrumentationName;
data.instrumentationArgs = instrumentationArgs;
data.instrumentationWatcher = instrumentationWatcher;
data.instrumentationUiAutomationConnection = instrumentationUiConnection;
data.debugMode = debugMode;
data.enableBinderTracking = enableBinderTracking;
data.trackAllocation = trackAllocation;
data.restrictedBackupMode = isRestrictedBackupMode;
data.persistent = persistent;
data.config = config;
data.compatInfo = compatInfo;
data.initProfilerInfo = profilerInfo;
data.buildSerial = buildSerial;
data.autofillCompatibilityEnabled = autofillCompatibilityEnabled;
sendMessage(H.BIND_APPLICATION, data);
}
綁定 application的時候會 發(fā)送一個消息,注意類型是BIND_APPLICATION丐黄,然后我們看處理的地方
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
case BIND_APPLICATION:
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "bindApplication");
AppBindData data = (AppBindData)msg.obj;
handleBindApplication(data);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
break;
}
//..........忽略.............
}
繼續(xù)調(diào)用 handleBindApplication(data); 從字面意思翻譯是 處理綁定application
private void handleBindApplication(AppBindData data) {
Application app;
final StrictMode.ThreadPolicy savedPolicy = StrictMode.allowThreadDiskWrites();
final StrictMode.ThreadPolicy writesAllowedPolicy = StrictMode.getThreadPolicy();
try {
// If the app is being launched for full backup or restore, bring it up in
// a restricted environment with the base application class.
app = data.info.makeApplication(data.restrictedBackupMode, null);
// Propagate autofill compat state
app.setAutofillCompatibilityEnabled(data.autofillCompatibilityEnabled);
// @4
mInitialApplication = app;
// don't bring up providers in restricted mode; they may depend on the
// app's custom Application class
if (!data.restrictedBackupMode) {
if (!ArrayUtils.isEmpty(data.providers)) {
installContentProviders(app, data.providers);
// For process that contains content providers, we want to
// ensure that the JIT is enabled "at some point".
mH.sendEmptyMessageDelayed(H.ENABLE_JIT, 10*1000);
}
}
// Do this after providers, since instrumentation tests generally start their
// test thread at this point, and we don't want that racing.
try {
mInstrumentation.onCreate(data.instrumentationArgs);
}
catch (Exception e) {
throw new RuntimeException(
"Exception thrown in onCreate() of "
+ data.instrumentationName + ": " + e.toString(), e);
}
try {
mInstrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
} finally {
// If the app targets < O-MR1, or doesn't change the thread policy
// during startup, clobber the policy to maintain behavior of b/36951662
if (data.appInfo.targetSdkVersion < Build.VERSION_CODES.O_MR1
|| StrictMode.getThreadPolicy().equals(writesAllowedPolicy)) {
StrictMode.setThreadPolicy(savedPolicy);
}
}
//................忽略...................
}
主要是對application的各種初始化斋配,主要看 app = data.info.makeApplication(data.restrictedBackupMode, null);
這行代碼就是生成application ,然后傳了兩個參數(shù)。 然后看 @4 標記這里艰争,前面說要替換系統(tǒng)所有用application的地方這里就是一處十偶,制作出來后的application賦值給mInitialApplication
public final class LoadedApk {
//.........忽略............
private Application mApplication;
//.........忽略............
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
//初始化了直接返回
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
//獲取功能清單下application節(jié)點的的name屬性
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
//創(chuàng)建上下文
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//初始化 傳三個參數(shù)類加載器、代理類园细、上下文
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
//@2
mActivityThread.mAllApplications.add(app);
//@3
mApplication = app;
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!instrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
// Rewrite the R 'constants' for all library apks.
SparseArray<String> packageIdentifiers = getAssets().getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++) {
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f) {
continue;
}
rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return app;
}
//..........忽略.............
}
LoadedApk
這個類就 APK在內(nèi)存中的表示惦积,可以得到如代碼,資料猛频,功能清單等資料
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
這行調(diào)用的方法
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
//一個工廠 制作出來了Application
Application app = getFactory(context.getPackageName())
.instantiateApplication(cl, className);
//綁定上下文
app.attach(context);
return app;
}
public @NonNull Application instantiateApplication(@NonNull ClassLoader cl,
@NonNull String className)
throws InstantiationException, IllegalAccessException, ClassNotFoundException {
return (Application) cl.loadClass(className).newInstance();
}
咦狮崩,看到這里不就是反射嗎,原來是從這里創(chuàng)建的鹿寻,然后下一行代碼 綁定
public class Application extends ContextWrapper implements ComponentCallbacks2 {
//..........忽略.............
/**
* @hide
*/
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
//..........忽略.............
}
看到這里明白了把睦柴,這就是為什么之前把解密dex和加載dex文件放到attachBaseContext()里面了。
好了毡熏,到現(xiàn)在知道了從該哪里加載application了坦敌,那就好辦了, appContext.setOuterContext(app);
這里加載進來的,我只需要把 ContextImpl 這個類反射出來痢法,加載好的 的application設置到mOuterContext這個屬性上就好了
然后這里 @2 mActivityThread.mAllApplications.add(app);
還用到了 把application加載到一個集合里
還有這里 @3 mApplication = app;
最后 @4 instrumentation.callApplicationOnCreate(app);
還有有時會用到 getContext().getApplicationInfo().className
所有要替換 ApplicationInfo -> className
總結要替換的 application
ContextImpl->mOuterContext(app) 通過Application的attachBaseContext回調(diào)參數(shù)獲取
ActivityThread->mActivityThread(ArrayList) 通過ContextImpl的mMainThread屬性獲取
LoadedApk->mApplication 通過ContextImpl的mPackageInfo屬性獲取
ActivityThread->mInitialApplication 通過ContextImpl的mMainThread屬性獲取
ApplicationInfo -> className
application與四大組件的關系
- Activity
- getApplication()得到application狱窘,在attach() 方法里
mApplication = application;
賦值 - 在ActivityThread中會接收消息 RELAUNCH_ACTIVITY
handleRelaunchActivity()---> handleRelaunchActivityInner() -->handleLaunchActivity(); final Activity a = performLaunchActivity(r, customIntent); //activity創(chuàng)建 activity =mInstrumentation.newActivity() 調(diào)用了activity.attach() 完成綁定 Application app=r.packageInfo.makApplicatio()
- getApplication()得到application狱窘,在attach() 方法里
- Service
在ActivityThread中會接收消息CREATE_SERVICE 源碼和Acitivty類同
- BroadCastReciver
在ActivityThread中會接收消息 RECEIVER handlerReceiver() receiver.onReceive(context.getReceiverRestrictedContext(),data.intent); 把上下文件封裝了一層,以防止用戶在接收者中進行注冊廣播和綁定服務 ReceiverRestrictedContext -> registerReceiver(){ throw new ReceiverCallNotAllowedException( "BroadcastReceiver components are not allowed to register to receive intents"); } 在ActivityThread中會接收消息BIND_APPLICATION
- ContentProvider
ContentProvider 在ActivityThread中會接收消息 BIND_APPLICATION handleBindApplication() AcitivityThread中 handleBindApplication方法中 if(!data.restrictedBackupMode){} //安裝 installContentProviders() installProvider() 同樣是通過newInstance反射創(chuàng)建 localProvider.attachInfo(c); mContext=context; 讓if(context.getPackageName不等于ai.packagename)我們就可以切換 application成功 然后不走前兩個if 走下面的 c=context.createPackageContext(); ContextImpl.java中的實現(xiàn) //這里返回Context财搁,那我么重新這個方法蘸炸,把創(chuàng)建的application返回就行 了。 @Override public Context createPackageContext(String packageName, int flags)
代碼實現(xiàn)
- 今天就不貼代碼了尖奔,需要的小伙伴在 GitHub上看我的實現(xiàn)吧