Performance optimization series

APP startup optimization

UI rendering optimization

Memory optimization

Image compression

Long figure optimization

Power optimization

Dex encryption

Dynamic replacement Application

Exploring the principle of hot fix for APP stability

APP continuous running process alive implementation

ProGuard compresses code and resources

APK limit compression

Introduction to the

The last article about dex encryption and decryption has not seen can first go to understand how dex encryption and decryption, this article will take you to complete the rest of the work, dex decryption after the completion of the need to remove the agent ProxyApplication, Then add our own Application to our Application. Want to replace ProxyApplication is not a simple thing, first of all must be familiar with the Application source code to operate on it, I will take you together into the world of source code.

Application binding process

ActivityThread => main() : ActivityThread => main()

How to parse our Application in XML

  • ActivityThread.java

    Mian () -> thread.attach() -> attachApplication() -> sendMessage (h.bind_application) -> Processing after receiving the parameter from AMS BIND_APPLICATION – > handleBindApplication () here ready to application – > application app = data. Info. MakeApplication () – > mInitialApplication = app;

  • LoadedApk.java

    This class is the in-memory representation of APK for information such as code, data, and function lists

    1. Through mApplicationInfo. ClassName we registered a full class name
    2. App = mActivityThread. MInstrumentation. NewApplication () to create an application
    3. Then use the appContext. SetOuterContext (app)
    4. mApplication = app

Reflect what needs to be replaced

  • ContextImpl -> mOuterContext(app) Is obtained by the attachBaseContext callback parameter of the Application
  • ActivityThread -> mAllApplication(arrayList) is obtained by ContextImpl mMainThread property
  • LoadedApk -> mApplication is obtained by ContextImpl’s mPackageInfo attribute

Reflection begins to replace Application

    boolean isBindReal;
    Application delegate;
    private void bindRealApplicatin(a) throws Exception {
        if (isBindReal) {
            return;
        }
        if (TextUtils.isEmpty(app_name)) {
            return;
        }
        AttachBaseContext (context); // attachBaseContext(context)
        Context baseContext = getBaseContext();
        // Create user's real application (MyApplication)Class<? > delegateClass = Class.forName(app_name); delegate = (Application) delegateClass.newInstance();// Get attach() method
        Method attach = Application.class.getDeclaredMethod("attach", Context.class);
        attach.setAccessible(true);
        attach.invoke(delegate, baseContext);


// ContextImpl---->mOuterContext(app) is obtained by the attachBaseContext callback parameter of ApplicationClass<? > contextImplClass = Class.forName("android.app.ContextImpl");
        // Get the mOuterContext attribute
        Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext");
        mOuterContextField.setAccessible(true);
        mOuterContextField.set(baseContext, delegate);

// ActivityThread-- >mAllApplications(ArrayList) ContextImpl mMainThread property
        Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread");
        mMainThreadField.setAccessible(true);
        Object mMainThread = mMainThreadField.get(baseContext);

// ActivityThread--->>mInitialApplicationClass<? > activityThreadClass=Class.forName("android.app.ActivityThread");
        Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication");
        mInitialApplicationField.setAccessible(true);
        mInitialApplicationField.set(mMainThread,delegate);
// ActivityThread-- >mAllApplications(ArrayList) ContextImpl mMainThread property
        Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications");
        mAllApplicationsField.setAccessible(true);
        ArrayList<Application> mAllApplications =(ArrayList<Application>) mAllApplicationsField.get(mMainThread);
        mAllApplications.remove(this);
        mAllApplications.add(delegate);

// LoadedApk------->mApplication ContextImpl mPackageInfo attribute
        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);

        // Modify ApplicationInfo className LooadedApk
        Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo");
        mApplicationInfoField.setAccessible(true);
        ApplicationInfo mApplicationInfo = (ApplicationInfo)mApplicationInfoField.get(mPackageInfo);
        mApplicationInfo.className=app_name;

        delegate.onCreate();
        isBindReal = true;
    }
Copy the code

Now that the re-signed package is complete, start our APK and look at the Log

2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication@1ec3c70
2019-06-04 23:17:30.892 6064-6064/com.yk.dexdeapplication I/DevYK: provider onCreate:com.example.proxy_core.ProxyApplication
2019-06-04 23:17:30.895 6064-6064/com.yk.dexdeapplication I/DevYK: MyApplication onCreate(a)The 2019-06-04 23:17:30. 995, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Activity: com. Yk. Dexdeapplication. App @ 300 b5f6 23:17:30 2019-06-04. 995, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Activity: com. Yk. Dexdeapplication. App @ 300 b5f6 23:17:30 2019-06-04. 995, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Activity: com. Yk. Dexdeapplication. App 23:17:31. 2019-06-04, 001, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: The provider delete: com. Example. Proxy_core. ProxyApplication @ 1 ec3c70 23:17:31 2019-06-04. 021 6064-6064/com.yk.dexdeapplication I/DevYK: Service: com. Yk. Dexdeapplication. App @ 300 b5f6 23:17:31 2019-06-04. 021, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Service: com. Yk. Dexdeapplication. App @ 300 b5f6 23:17:31 2019-06-04. 021, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Service: com. Yk. Dexdeapplication. App 23:17:31. 2019-06-04, 022, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Reciver: android app. ReceiverRestrictedContext @ 9 b92293 23:17:31. 2019-06-04, 022, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: Reciver: com. Yk. Dexdeapplication. App @ 300 b5f6 23:17:31 2019-06-04. 022, 6064-6064 / com. Yk. Dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.AppCopy the code

Look at the LOG

MyApplication onCreate(a)
Copy the code

This has been replaced with our own MyApplication, and the Activity and Service fetch context has been replaced with our successful Applicaton. But… Maybe some of the better eyes have already figured out why the content provider Context is still the proxy Application and executes before our own Application, so let’s look at what Application onCreate does with that in mind.

Let’s hit installlContentProviders(app,providers);

Notice that we’re still passing in the proxy Context

The point is at the end

Notice the logic in the circle that I’ve drawn, to see if the package name of the current application is the same as the package name in the XML, and if it is, we assign a value, and again the context here is our surrogate context, so what do we do, We’re going to rewrite the PackageName in the agent as long as it’s not equal then we’re going to go else and we’re going to create a Context based on the PackageName

    /** * let the code go into the third paragraph of if *@return* /
    @Override
    public String getPackageName(a) {
        if(! TextUtils.isEmpty(app_name)){return "";
        }
        return super.getPackageName();
    }

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

    }
Copy the code
  1. First determine whether app_name in our own XML is empty
  2. If not, we pass in an empty packet
  3. The SDK will determine if it’s the same as the PCK in the XML, and then else we’re rewriting the createPackageContext to pass in the package name of our own application. It’s going to generate a Context

Finally, let’s verify:

2019-06-05 00:12:30.271 7570-7570/com.yk.dexdeapplication I/DevYK: MyApplication onCreate(a)The 2019-06-05 00:12:30. 273, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: The provider onCreate: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 273, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: The provider onCreate: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 273, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: The provider onCreate: com. Yk. Dexdeapplication. App 00:12:30. 2019-06-05, 381, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Activity: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 381, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Activity: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 381, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Activity: com. Yk. Dexdeapplication. App 00:12:30. 2019-06-05, 387, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: The provider delete: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 406, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Service: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 406, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Service: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 406, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Service: com. Yk. Dexdeapplication. App 00:12:30. 2019-06-05, 408, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Reciver: android app. ReceiverRestrictedContext @ b7a3b82 00:12:30. 2019-06-05, 408, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: Reciver: com. Yk. Dexdeapplication. App @ 1 ec3c70 00:12:30 2019-06-05. 408, 7570-7570 / com. Yk. Dexdeapplication I/DevYK: reciver:com.yk.dexdeapplication.AppCopy the code

All of the Context in the log is the Application Context that we replaced, except for the BroadCase Context which is system. Perfect solution. But there is a hidden BUG here. The interview question is said to ask “So what is it?”

Can a context be used in a broadcast to start a broadcast or bind a service?

We can actually look at the source code with this in mind

H -> RECEIVER Message

Sure enough, registering the broadcast and binding services throws an exception.

conclusion

To this point our reinforcement has been finished, from dex subcontracting -> encryption -> Alignment > Signature -> package compression into APK. A complete set of processes and code has been written. With the market reinforcement process principle is almost the same. Understand the principle and then use the third party will be familiar with the road.

Transfer code