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

Now download an APK file at will in the application market and decompile, more than 95% are basically confused, encryption, or third party hardening (third party hardening is also this principle), so today we will encrypt and decrypt Dex. Decompiler can not normally read the project source code.

The encrypted structure

APK analysis

Decompile effect

If you want to encrypt Dex, what is the 64K problem

If you want to know more about the 64K problem, please refer to the website

As the Android platform continues to grow, so does the size of Android apps. When your application and the libraries it references reach a certain size, you will experience build errors indicating that your application has reached the limits of the Android application build architecture. Earlier versions of the build system reported this error as follows:

Conversion to Dalvik format failed:
Unable to execute dex: method ID not in [0.0xffff] :65536
Copy the code

Newer builds of Android show different errors, but they indicate the same problem:

trouble writing output:
Too many field references: 131000; max is 65536.
You may try using --multi-dex option.
Copy the code

Each of these error conditions displays the following number: 65,536. This number is important because it represents the total number of references that can be called by the code within a single Dalvik Executable (DEX) bytecode file. This section describes how to get around this limitation by enabling an application configuration called Dalvik executable subcontracting to enable your application to build and read the Dalvik executable subcontracting DEX file.

About 64K reference restrictions

Dalvik executable subcontracting support prior to Android 5.0

Platform versions prior to Android 5.0 (API level 21) used the Dalvik runtime to execute application code. By default, Dalvik restricts an application to a single classes.dex bytecode file per APK. To get around this restriction, you can use the Dalvik executable to subcontract the support library, which will become part of your application’s main DEX file, and then manage access to other DEX files and the code they contain.

Dalvik executable subcontracting support for Android 5.0 and higher

Android 5.0 (API level 21) and later uses a runtime named ART, which natively supports loading multiple DEX files from an APK file. ART performs precompilation at application installation, scanning classesn. dex files and compiling them into a single.oat file for Android devices to execute. Therefore, if your minSdkVersion is 21 or higher, there is no need for the Dalvik executable subcontracting support library.

Resolve 64K limit

  1. If your minSdkVersion is set to 21 or higher, you can simply set multiDexEnabled to true in the modec-level build.gradle file, as follows:

    android {
        defaultConfig {
            ...
            minSdkVersion 21 
            targetSdkVersion 28
            multiDexEnabled true}... }Copy the code

    However, if your minSdkVersion is set to 20 or lower, you must use the Dalvik executable to subcontract the support library as follows:

    • Modify the modul level build.gradle file to enable Dalvik executable subcontracting and add the Dalvik executable subcontracting library as a dependency, as shown here

      android {
          defaultConfig {
              ...
              minSdkVersion 15 
              targetSdkVersion 28
              multiDexEnabled true}... } dependencies { compile'com. Android. Support: multidex: 1.0.3'
      }
      Copy the code
    • Current Application extends MultiDexApplication {… } or MultiDex. Install (this);

  2. Enable ProGuard by obfuscating to remove unused code and build code compression.

  3. Reduce direct dependencies on third-party libraries, download source code whenever possible, and use whatever you need without having to rely on the entire project.

Dex Encryption and decryption

Process:

  1. Get APK and extract all dex files.
  2. Use Tools to encrypt and combine the encrypted dex with the proxy application class.dex, and then re-sign, align, and package.
  3. When the user installs APK and opens the Application for agent decryption, the dexElements are reflected and the decrypted dex is replaced by the dexElements in the DexPathList.

Dex File loading process

If you want to check the Dex loading process, you need to know which source class to start with. If you don’t know which source class to start with, you need to print the ClassLoader first.

The following is a flowchart to understand the Dex loading process in detail

Finally, we know that findClass(String name,List Sup) iterates through the dexElements to find the Class and gives it to Android to load.

Dex decryption

Now that we know the dex loading process, how do we decrypt the dex? We just learned that we need to iterate over the dexElements to find the Class. So can we initialize the dexElements before we iterate over the dexElements? Give the dex we decrypted to dexElements. We decrypt the dex and replace the dexElements in the DexPathList.

  1. Get the currently encrypted APK file and extract it

    // Get the APK file that is currently encrypted
    File apkFile=new File(getApplicationInfo().sourceDir);
    // Unzip apk from app_name+"_"+app_version
    File versionDir = getDir(app_name+"_"+app_version,MODE_PRIVATE);
    File appDir=new File(versionDir,"app");
    File dexDir=new File(appDir,"dexDir");
    Copy the code
  2. Get the Dex file we need to load

    // Unzip apk to appDir
    Zip.unZip(apkFile,appDir);
    // Get all files in the directory
    File[] files=appDir.listFiles();
    for (File file : files) {
         String name=file.getName();
         if(name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")) {try{
            AES.init(AES.DEFAULT_PWD);
            // Read the contents of the file
            byte[] bytes=Utils.getBytes(file);
            / / decryption
            byte[] decrypt=AES.decrypt(bytes);
            // Write to the specified directory
            FileOutputStream fos=new FileOutputStream(file);
            fos.write(decrypt);
            fos.flush();
            fos.close();
            dexFiles.add(file);
    
         }catch(Exception e){ e.printStackTrace(); }}}Copy the code
  3. Load the decrypted dex into the system

    private void loadDex(List<File> dexFiles, File versionDir) throws Exception{
            / / 1. Obtain pathlist
            Field pathListField = Utils.findField(getClassLoader(), "pathList");
            Object pathList = pathListField.get(getClassLoader());
            //2. Get array dexElements
            Field dexElementsField=Utils.findField(pathList,"dexElements");
            Object[] dexElements=(Object[])dexElementsField.get(pathList);
            //3. Reflect to the method of initializing dexElements
            Method makeDexElements=Utils.findMethod(pathList,"makePathElements",List.class,File.class,List.class);
    
            ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
            Object[] addElements=(Object[])makeDexElements.invoke(pathList,dexFiles,versionDir,suppressedExceptions);
    
            // Merge the array
            Object[] newElements= (Object[])Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length+addElements.length);
            System.arraycopy(dexElements,0,newElements,0,dexElements.length);
            System.arraycopy(addElements,0,newElements,dexElements.length,addElements.length);
    
            // Replace the Element array in DexPathList
            dexElementsField.set(pathList,newElements);
        }
    Copy the code

    Now that decrypting is done, let’s look at encryption, and why I say decrypting first, because encryption involves signing, packing, and alignment. So I’ll leave it to the end.

Dex encryption

  1. Make a dex that contains only decryption code

    1Dxdex --dex --output out.dex in.jar2. Run File aarFile= through execnew File("proxy_core/build/outputs/aar/proxy_core-debug.aar");
            File aarTemp=new File("proxy_tools/temp");
            Zip.unZip(aarFile,aarTemp);
            File classesJar=new File(aarTemp,"classes.jar");
            File classesDex=new File(aarTemp,"classes.dex");
            String absolutePath = classesDex.getAbsolutePath();
            String absolutePath1 = classesJar.getAbsolutePath();
            //dx --dex --output out.dex in.jar
            //dx --dex --output //D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.dex //D:\Downloads\android_space\DexDEApplication\proxy_tools\temp\classes.jar
            Process process=Runtime.getRuntime().exec("cmd /c dx --dex --output "+classesDex.getAbsolutePath()
                                        +""+classesJar.getAbsolutePath());
            process.waitFor();
            if(process.exitValue()! =0) {throw new RuntimeException("dex error");
            }
    Copy the code
  2. The dex file in apK is encrypted

     File apkFile=new File("app/build/outputs/apk/debug/app-debug.apk");
            File apkTemp=new File("app/build/outputs/apk/debug/temp");
            Zip.unZip(apkFile,apkTemp);
            // Just take the dex file out and encrypt it
            File[] dexFiles=apkTemp.listFiles(new FilenameFilter() {
                @Override
                public boolean accept(File file, String s) {
                    return s.endsWith(".dex"); }});/ / AES encryption
            AES.init(AES.DEFAULT_PWD);
            for (File dexFile : dexFiles) {
                byte[] bytes = Utils.getBytes(dexFile);
                byte[] encrypt = AES.encrypt(bytes);
                FileOutputStream fos=new FileOutputStream(new File(apkTemp,
                        "secret-"+dexFile.getName()));
                fos.write(encrypt);
                fos.flush();
                fos.close();
                dexFile.delete();
    }
    Copy the code
  3. Put the dex into the APK pressurized directory and press it into an APK file

     File apkTemp=new File("app/build/outputs/apk/debug/temp");
            File aarTemp=new File("proxy_tools/temp");
            File classesDex=new File(aarTemp,"classes.dex");
            classesDex.renameTo(new File(apkTemp,"classes.dex"));
            File unSignedApk=new File("app/build/outputs/apk/debug/app-unsigned.apk");
            Zip.zip(apkTemp,unSignedApk);
    Copy the code

    Now you can look at the encrypted files, and the unencrypted files

    Unencrypted apk:

    Encrypted APK (now you can only see the proxy Application)

packaging

alignment

// The apK alignment tool performs specific byte alignment at the beginning of the uncompressed data relative to the beginning of the file, reducing the application running memory.
zipalign -f 4 in.apk out.apk 

// Check if apK is aligned
zipalign -c -v 4 output.apk

// Finally, "Verification succesful" indicates that the alignment succeeded
  236829 res/mipmap-xxxhdpi-v4/ic_launcher.png (OK - compressed)
  245810 res/mipmap-xxxhdpi-v4/ic_launcher_round.png (OK - compressed)
  260956 resources.arsc (OK - compressed)
  317875 secret-classes.dex (OK - compressed)
 2306140 secret-classes2.dex (OK - compressed)
 2477544 secret-classes3.dex (OK - compressed)
Verification succesful
Copy the code

Signature package apkSigner

// SDK \build-tools\24.0.3 +, apK signature toolApksigner sign --ks JKS file address --ks-key-alias Alias --ks-pass pass: JSK password --key-pass pass: alias password -- outout.apk in.apkCopy the code

conclusion

In fact, the principle is to generate the dex file by the main code through the command dx, and then merge the encrypted dex in the agent class.dex. The code in the agent is still visible, but the main code is not exposed, so we have achieved the desired effect. If it is well encapsulated (JNI implements the main decryption code), it is almost invisible. ClassLoader is still important, as is hot fixes and hot loads. Learn here DEX encryption and decrypting has learned, if you want to see their own try can refer to my code

Code transfer matrix

Projects if used: