美文网首页性能优化
Dex加密(下)—替换Application

Dex加密(下)—替换Application

作者: 追寻米K | 来源:发表于2019-03-21 16:28 被阅读0次

    dex加密中我们使用了解密的ProxyApplication作为了application的name,但是通常我们都会在主App中自定义一个MyApplication,并在其中做一些初始化工作,这个时候我们就需要把ProxyApplication替换成原本的MyApplication。

    在替换之前,我们先看看Application在系统中是什么时候开始创建的。
    ActivityThread的main方法是一个进程的入口,这里会调用attach()方法,从而通过binder机制调用ActivityManagerService的attachApplication()方法,然后这个方法又反过来调用ActivityThread的bindApplication()方法,接着发Handler调用handleBindApplication()方法。

    private void handleBindApplication(AppBindData data) {
              Application app = data.info.makeApplication(data.restrictedBackupMode, null);
                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);
                    }
                }
        .......
                mInstrumentation.callApplicationOnCreate(app);
    }
    

    在handleBindApplication方法中调用makeApplication去创建一个Application对象。

    
        public Application makeApplication(boolean forceDefaultAppClass,
                Instrumentation instrumentation) {
            if (mApplication != null) {
                return mApplication;
            }
    Application app = null;
    String appClass = mApplicationInfo.className;
            if (forceDefaultAppClass || (appClass == null)) {
                appClass = "android.app.Application";
            }
            try {
                java.lang.ClassLoader cl = getClassLoader();
    ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
                app = mActivityThread.mInstrumentation.newApplication(
                        cl, appClass, appContext);
                appContext.setOuterContext(app);
            } catch (Exception e) {}
    mActivityThread.mAllApplications.add(app);
            mApplication = app;
        ..........
            return app;
    

    在newApplication()中才是真的去创建一个新的Application。

        public Application newApplication(ClassLoader cl, String className, Context context)
                throws InstantiationException, IllegalAccessException, 
                ClassNotFoundException {
            return newApplication(cl.loadClass(className), context);
        }
    
    static public Application newApplication(Class<?> clazz, Context context)
                throws InstantiationException, IllegalAccessException, 
                ClassNotFoundException {
            Application app = (Application)clazz.newInstance();
            app.attach(context);
            return app;
        }
    

    回过头来看看实例化完Application之后在哪些地方用到了这个Application。

    • 实例化完成立马调用Application的attach方法。
    • appContext.setOuterContext(app)
    • mActivityThread.mAllApplications.add(app)
    • 赋值给mApplication
    • mInitialApplication = app
    • String appClass = mApplicationInfo.className获得Application的类名。
      在做完上述的操作之后会调用Application的onCreate方法,所以就在onCreate中我们把上面的所有用到Application的地方替换成主App中真实的Application。
    public void bindRealApplication() throws Exception {
            if (isBindReal){
                return;
            }
            //如果用户(使用这个库的开发者) 没有配置Application 就不用管了
            if (TextUtils.isEmpty(app_name)) {
                return;
            }
            //这个就是attachBaseContext传进来的 ContextImpl
            Context baseContext = getBaseContext();
            //反射创建出真实的 用户 配置的Application
            Class<?> delegateClass = Class.forName(app_name);
            delegate = (Application) delegateClass.newInstance();
            //反射获得 attach函数
            Method attach = Application.class.getDeclaredMethod("attach", Context.class);
            //设置允许访问
            attach.setAccessible(true);
            attach.invoke(delegate, baseContext);
    
            /**
             *  替换
             *  ContextImpl -> mOuterContext ProxyApplication->MyApplication
             */
            Class<?> contextImplClass = Class.forName("android.app.ContextImpl");
            //获得 mOuterContext 属性
            Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext");
            mOuterContextField.setAccessible(true);
            mOuterContextField.set(baseContext, delegate);
    
    
            /**
             * ActivityThread  mAllApplications 与 mInitialApplication
             */
            //获得ActivityThread对象 ActivityThread 可以通过 ContextImpl 的 mMainThread 属性获得
            Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread");
            mMainThreadField.setAccessible(true);
            Object mMainThread = mMainThreadField.get(baseContext);
    
            //替换 mInitialApplication
            Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
            Field mInitialApplicationField = activityThreadClass.getDeclaredField
                    ("mInitialApplication");
            mInitialApplicationField.setAccessible(true);
            mInitialApplicationField.set(mMainThread,delegate);
    
            //替换 mAllApplications
            Field mAllApplicationsField = activityThreadClass.getDeclaredField
                    ("mAllApplications");
            mAllApplicationsField.setAccessible(true);
            ArrayList<Application> mAllApplications = (ArrayList<Application>) mAllApplicationsField.get(mMainThread);
            mAllApplications.remove(this);
            mAllApplications.add(delegate);
    
    
            /**
             * LoadedApk -> mApplication ProxyApplication
             */
            //LoadedApk 可以通过 ContextImpl 的 mPackageInfo 属性获得
            Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");
            mPackageInfoField.setAccessible(true);
            Object mPackageInfo = mPackageInfoField.get(baseContext);
    
            Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");
            Field mApplicationField = loadedApkClass.getDeclaredField("mApplication");
            mApplicationField.setAccessible(true);
            mApplicationField.set(mPackageInfo,delegate);
    
            //修改ApplicationInfo className LoadedApk
            Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo");
            mApplicationInfoField.setAccessible(true);
            ApplicationInfo mApplicationInfo = (ApplicationInfo) mApplicationInfoField.get(mPackageInfo);
            mApplicationInfo.className = app_name;
    
            delegate.onCreate();
            isBindReal = true;
        }
    

    替换完成之后看看四大组件中Application是否改动:



    Activity和service已经修改成功,Provider修改失败了,BroadCastReciver有点不同是个ReceiverRestrictedContext。

    先看看BroadCastReciver:
    在ActivityThread中的handleReceiver方法创建一个BroadCastReciver

        private void handleReceiver(ReceiverData data) {
            ......
                receiver.onReceive(context.getReceiverRestrictedContext(),
                        data.intent);
    ......
    }
    
       final Context getReceiverRestrictedContext() {
            if (mReceiverRestrictedContext != null) {
                return mReceiverRestrictedContext;
            }
            return mReceiverRestrictedContext = new ReceiverRestrictedContext(getOuterContext());
        }
    

    在new ReceiverRestrictedContext的时候传入的是getOuterContext(),

       final Context getOuterContext() {
            return mOuterContext;
        }
    

    也就是我们之前替换过的mOuterContext,所以BroadCastReciver我们是替换成功的。

    Provider:
    从打印log可以看出先调用了Provider的onCreate然后在调用Application的onCreate,在handleBindApplication方法中还有个if语句在Application的onCreate之前执行。

                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);
                    }
    
        private ContentProviderHolder installProvider(Context context,
                ContentProviderHolder holder, ProviderInfo info,
                boolean noisy, boolean noReleaseNeeded, boolean stable) {
                Context c = null;
                ApplicationInfo ai = info.applicationInfo;
                if (context.getPackageName().equals(ai.packageName)) {
                    c = context;
                } else if (mInitialApplication != null &&
                        mInitialApplication.getPackageName().equals(ai.packageName)) {
                    c = mInitialApplication;
                } else {
                    try {
                        c = context.createPackageContext(ai.packageName,
                                Context.CONTEXT_INCLUDE_CODE);
                    } catch (PackageManager.NameNotFoundException e) {
                        // Ignore
                    }
                }
                ......
    
                try {
                    final java.lang.ClassLoader cl = c.getClassLoader();
                    localProvider = (ContentProvider)cl.
                        loadClass(info.name).newInstance();
                    provider = localProvider.getIContentProvider();
                    if (provider == null) {
                        Slog.e(TAG, "Failed to instantiate class " +
                              info.name + " from sourceDir " +
                              info.applicationInfo.sourceDir);
                        return null;
                    }
                    if (DEBUG_PROVIDER) Slog.v(
                        TAG, "Instantiating local provider " + info.name);
                    // XXX Need to create the correct context for this provider.
                    localProvider.attachInfo(c, info);
                } catch (java.lang.Exception e) {}
    

    创建完Provider之后调用attachInfo,传入的Context 是c。

     public void attachInfo(Context context, ProviderInfo info) {
            attachInfo(context, info, false);
        }
    
        private void attachInfo(Context context, ProviderInfo info, boolean testing) {
            mNoPerms = testing;
    
            /*
             * Only allow it to be set once, so after the content service gives
             * this to us clients can't change it.
             */
            if (mContext == null) {
                mContext = context;
                }
      }
    

    mContext 就是我们在Provider中getContext()所获得的Context,所以要想替换这个mContext,就得改c。c会通过if else来确定c的值,通常context.getPackageName()和应用的包名都是一致的所以c就是我们替换之前的Application,第二个if中的mInitialApplication在上面也提到了,其实个替换之前的Application是同一个,所以只有else中才又可能满足我们的需求。如果要执行else我们首先需要修改一下context.getPackageName()返回的包名

     @Override
        public String getPackageName() {
            //如果meta-data 设置了 application
            //让ContentProvider创建的时候使用的上下文 在ActivityThread中的installProvider函数
            //命中else
            if (!TextUtils.isEmpty(app_name)){
                return "";
            }
            return super.getPackageName();
        }
    

    接着修改context.createPackageContext返回的值

       @Override
        public Context createPackageContext(String packageName, int flags) throws PackageManager.NameNotFoundException {
            if (TextUtils.isEmpty(app_name)){
                return super.createPackageContext(packageName, flags);
            }
            try {
                bindRealApplication();
            } catch (Exception e) {
                e.printStackTrace();
            }
            return delegate;
        }
    

    到此已经全部替换成功
    demo

    相关文章

      网友评论

        本文标题:Dex加密(下)—替换Application

        本文链接:https://www.haomeiwen.com/subject/eofxvqtx.html