减少入侵性
前言
- 上次我们写的代码会有下面这样的问题,我们看到四大组件获的的application是壳程序里的application,那这样壳程序是不能提供别人使用的,所以这次讲的是怎么减少这样的入侵
-
那就是找到 application的加载过程,然后把真实的application替换成原包里设置的
-
先上两张图
- 上面两张图是用户点击桌面app启动的流程,可以参考Activity启动流程 最后会调用 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的时候会 发送一个消息,注意类型是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;
}
//..........忽略.............
}
继续调用 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 ,然后传了两个参数。 然后看 @4 标记这里,前面说要替换系统所有用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节点的的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);
}
//创建上下文
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
//初始化 传三个参数类加载器、代理类、上下文
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在内存中的表示,可以得到如代码,资料,功能清单等资料
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
这行调用的方法
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();
}
咦,看到这里不就是反射吗,原来是从这里创建的,然后下一行代码 绑定
public class Application extends ContextWrapper implements ComponentCallbacks2 {
//..........忽略.............
/**
* @hide
*/
/* package */ final void attach(Context context) {
attachBaseContext(context);
mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}
//..........忽略.............
}
看到这里明白了把,这就是为什么之前把解密dex和加载dex文件放到attachBaseContext()里面了。
好了,到现在知道了从该哪里加载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回调参数获取
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创建 activity =mInstrumentation.newActivity() 调用了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反射创建 localProvider.attachInfo(c); mContext=context; 让if(context.getPackageName不等于ai.packagename)我们就可以切换 application成功 然后不走前两个if 走下面的 c=context.createPackageContext(); ContextImpl.java中的实现 //这里返回Context,那我么重新这个方法,把创建的application返回就行 了。 @Override public Context createPackageContext(String packageName, int flags)
代码实现
- 今天就不贴代码了,需要的小伙伴在 GitHub上看我的实现吧