This article is from netease Cloud community

Author: Wang Chenyan


Application processing

We have already inserted Hack references to all class files, and dex is inserted in Application. Application must be loaded before Application starts, but dex is not inserted yet. Therefore, ClassNotFoundException must be caused, so we cannot make the Application reference Hack.

When modifying class files, how do you know which one is Application? Some people might say that it is not enough, but I think that as a plug-in, it should be compatible and minimize the user’s manual configuration.

The processDebugManifest Task is used to find the name of the Application.

As we all know, an Application needs to be registered in the Manifest, so just find the Manifest file to get the name of the Application.

That’s right, the Manifest file is in processDebugManifest’s exoperation. files. Find the Manifest and parse the application tag.

  • What happens when you turn on obfuscation?

The applications we officially launched will be confused. The debug unconfused mode we tested just now is used. If we turn on the confused mode, will the Task be exactly the same as the above one?

We turn on the confusion of Release, then execute assembleRelease and observe the Gradle Console output

App: preBuild the UP - TO - DATE / / omit part of the Task: app: processReleaseJavaRes NO - SOURCE :app:transformResourcesWithMergeJavaResForRelease :app:transformClassesAndResourcesWithProguardForRelease :app:transformClassesWithDexForRelease :app:mergeReleaseJniLibFolders :app:transformNativeLibsWithMergeJniLibsForRelease  :app:validateSigningRelease :app:packageRelease :app:assembleReleaseCopy the code

Can see much yet to confuse a transformClassesAndResourcesWithProguardForRelease compared, then the Proguard Task useful?

Useful!

We need to insert obfuscation logic in front of Proguard Task in order to ensure that the class name will not change when packaging APK and Patch

This is done using -Applymapping of Proguard.

Therefore, we also need to save the mapping file generated after packaging APK.

Plug-in code implementation

static applymapping(TransformTask proguardTask, File mappingFile) {    if (proguardTask) {
        ProGuardTransform transform = (ProGuardTransform) proguardTask.getTransform()        if (mappingFile.exists()) {
            transform.applyTestedMapping(mappingFile)
        } else {
            CFixLogger.i("${mappingFile} does not exist")}}}Copy the code
  • Patch signature

For security, we’d better add signature verification to the patch to ensure that the patch signature is consistent with the APK signature.

Use jarsigner in JDK for signature

List<String> command = [JavaEnvUtils.getJdkExecutable('jarsigner'),                        '-verbose'.'-sigalg'.'MD5withRSA'.'-digestalg'.'SHA1'.'-keystore', extension.storeFile.absolutePath,                        '-keypass', extension.keyPassword,                        '-storepass', extension.storePassword,
                        patchFile.absolutePath,
                        extension.keyAlias]Process proc = command.execute()Copy the code

Check the signature code I will not paste, corresponding to the source code in the SignChecker class.

Inspection results

Above we have made the patch, the process of importing the patch roughly combed through, next we need to sort out the above code.

For ease of use, we made it a Gradle plug-in. If you don’t know how to make Gradle plug-ins, go ahead and learn

I have uploaded the plugin and dependency library to JCenter to introduce the plugin into the app.

// root build.gradlebuildscript {
    repositories {
        jcenter()
    }

    dependencies {
        classpath 'com. Android. Tools. Build: gradle: 2.3.3'
        classpath 'me. Wcy: cfix - gradle: 1.1'
    }
}// app build.gradleapply plugin: 'com.android.application'apply plugin: 'me.wcy.cfix'cfix {
    includePackage = ['me/wcy/cfix/sample'] // excludeClass = [] // Class debugOn = that does not need to insert a patchtrue// Debug mode Whether to insert patch sign =trueStoreFile = file("release.jks")
    storePassword = 'android'
    keyAlias = 'cfix'
    keyPassword = 'android'} dependencies {compile fileTree(dir)'libs', include: ['*.jar'])
    compile 'com. Android. Support: appcompat - v7:25.3.1'
    compile 'me. Wcy: cfix: 1.0'}Copy the code

Insert Hack dex and Patch in Application

@Overrideprotected void attachBaseContext(Context base) {
    super.attachBaseContext(base);
    CFix.init(this);
    CFix.loadPatch(Environment.getExternalStorageDirectory().getPath().concat("/patch.jar"), !BuildConfig.DEBUG);
}Copy the code

First, run the project without making any changes

Familiar Hello World

Check whether the class files are introduced into Hack, compiled class is located in the app/build/intermediates/classes

As you can see, the Application does not introduce a Hack class. The Activity has successfully introduced a Hack class.

We then add a dialog class and call it in the Activity to display the dialog

public class FixDialog {    public void show(Context context) {        new AlertDialog.Builder(context)
                .setTitle("Congratulations")
                .setMessage("Patch Success!")
                .setPositiveButton("OK", null)
                .show();
    }
}// MainActivity@Overrideprotected void onCreate(Bundle savedInstanceState) {    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    FixDialog dialog = new FixDialog();
    dialog.show(this);
}Copy the code

Save the Hash file and create a patch package

Open the terminal and run the following command

gradlew clean cfixXiaomiDebugPatch -P cfixDir=D:\Android\AndroidStudioProjects\CFix\app\cfixCopy the code

Xiaomi indicates productFlavor, Debug indicates buildType

Push the generated patch.jar to the SD root of the phone

adb push D:\Users\wcy\Desktop\patch.jar /sdcard/Copy the code

Restart the application

Note that since we are only testing, we put the patch pack in SD, so we need to add read SD permissions, change targetSdk to less than 23 or manually grant permissions.

The source code

The framework is an improved version of Nuwa and supports almost all Gradle versions 1.5.0-3.0.1 (previous versions are too old to be adapted).

Thanks again to Nuwa author for giving us a good example.

This framework has been verified in netease Qiyu Android customer service workbench.

See README for the framework usage

Disclaimer: This framework has not been tested for compatibility, so it is not guaranteed to be compatible with all models. Compatibility testing is recommended for use in commercial projects.

conclusion

Today, we mainly discussed the feasibility of the hot repair scheme of Qzone, sorted out the whole process, and finally realized the whole scheme, which passed the verification.

In fact, I made a lot of mistakes during this period. For example, I mentioned in qzone blog that I used Javassist to modify the class. After USING Javassist, I could modify the class normally in demo at the beginning. But to a large number of code in the online project has been reported to find v4 package class, resulting in unable to modify the class file to introduce Hack class. Log and found that the class has been loaded normally, and sometimes can find sometimes can not find, each can not find the class is not the same, WTF.

Finally, the implementation of Nuwa is referenced and replaced with ASM, and the problem is solved.

In the past two years, many thermal repair frameworks have emerged, and there are many articles about thermal repair. I believe you have read a lot, but no matter how much you read, it is not as profound as hands-on practice.


Netease Cloud Free experience pavilion, 0 cost experience 20+ cloud products!

For more information about NETEASE’s r&d, product and operation experience, please visit netease Cloud Community.


SparkSQL big data combat: Uncover the mystery of Join 【 recommended 】 From the details of Android cold start optimization