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
-
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);
-
-
Enable ProGuard by obfuscating to remove unused code and build code compression.
-
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:
- Get APK and extract all dex files.
- Use Tools to encrypt and combine the encrypted dex with the proxy application class.dex, and then re-sign, align, and package.
- 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.
-
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
-
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
-
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
-
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
-
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
-
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: