Application shell of Android security

Posted by harnacks on Tue, 23 Nov 2021 11:34:23 +0100

Mobile security has been paid more and more attention, and the conventional methods are not applicable. People prefer a method to solve the application security problem. At this time, companies specializing in application reinforcement appear.

First of all, let's talk about what is shelling, why shelling, and what are the advantages and disadvantages of shelling?
What is shelling: it is a method of applying reinforcement to encrypt / hide / confuse the original binary text.
Why to shell: those who have developed Java applications will generally understand that Java code is very easy to be reverse analyzed. Even if some confusion technologies appear, the decompiled assembly code is still more readable than C/C + +. Of course, as the needs of NDK become more, so's shelling has also developed.
Benefits of shelling: protect your core code algorithm, improve the difficulty of cracking / piracy / secondary packaging, and alleviate code injection / dynamic debugging / memory injection attacks
Disadvantages of shelling: affect compatibility / program efficiency;

  1. Compatibility problem: there were applications that failed to work normally on some mobile phones after being shelled;
  2. Operation efficiency: the shell will change the startup entry. Generally, start the shell first, then decrypt the real dex file, and then run the reinforced program, so the startup will be slow

64K method limitations

If you want to know more about the problem of 64 k, you can refer to Official website

With the continuous growth of the Android platform, the size of Android applications is also increasing. When your application and its referenced library reach a specific size, you will encounter construction errors, indicating that your application has reached the limit of Android application construction 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

Although the errors displayed by the newer version of Android build system are different, they indicate the same problem:

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

These error conditions will display the following number: 65536. This number is important because it represents the total number of code callable references in a single Dalvik Executable (DEX) bytecode file. This section describes how to overcome this limitation by enabling the application configuration called Dalvik executable subcontracting, so that your application can build and read Dalvik executable subcontracting DEX files.

About 64K reference restrictions

Dalvik executable subcontracting support before Android 5.0
Platform versions before Android 5.0 (API level 21) use the Dalvik runtime to execute application code. By default, Dalvik restricts each APK applied to a single classes.dex bytecode file. To bypass this limitation, you can use the Dalvik executable subcontracting support library, which will become part of the main DEX file of your application, and then manage access to other DEX files and the code contained therein.

Dalvik executable subcontracting support for Android 5.0 and later
Android 5.0 (API level 21) and later use a runtime called ART, which natively supports loading multiple DEX files from APK files. ART performs precompiling during application installation, scans the classesN.dex files, and compiles them into a single. oat file for Android devices to execute. Therefore, if your minSdkVersion is 21 or higher, the Dalvik executable subcontracting support library is not required.

Resolve 64K limit

  1. If your minSdkVersion is set to 21 or higher, you only need to set multiDexEnabled to true in the module level build.gradle file, as shown here:
android {
    defaultConfig {
        ...
        minSdkVersion 21 
        targetSdkVersion 28
        multiDexEnabled true
    }
    ...
}

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

  • Modify the module 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'
}
  • Current application extensions multidexapplication {...} or MultiDex.install(this);
  1. Open ProGuard by obfuscation to remove unused code and build code compression.

  2. Reduce the direct dependence of third-party libraries, download the source code as much as possible, use whatever you need, and there is no need to rely on the whole project.

Dex encryption and decryption

technological process:

  1. Get APK and extract all dex files.
  2. Use Tools to encrypt, merge 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 entering the proxy decryption, it will get the dexElements reflected and replace the decrypted dex with the dexElements in the DexPathList

Dex file loading process

Since we want to check the Dex loading process, we must first know which source class to start with. Since we don't know, we'll print the ClassLoader first;

Let's take a flowchart to learn more about the Dex loading process

Finally, we know that we traverse the dexElements in findClass(String name,List sup), find the Class and give it to Android to load.

Dex decryption

Now we know the dex loading process, so how do we decrypt dex? We just learned that we need to traverse dexElements to find Class. Can we initialize dexElements before traversal. Get dexElements through reflection, and give our decrypted dex to dexElements. Next, we will decrypt dex and replace dexElements in DexPathList through code;

  1. Get the currently encrypted APK file and unzip it
//Get the currently encrypted APK file
File apkFile=new File(getApplicationInfo().sourceDir);
//Unzip apk app_ name+"_"+app_ The contents in the version directory need boot permission to be used
File versionDir = getDir(app_name+"_"+app_version,MODE_PRIVATE);
File appDir=new File(versionDir,"app");
File dexDir=new File(appDir,"dexDir");
  1. 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 file contents
        byte[] bytes=Utils.getBytes(file);
        //decrypt
        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();
     }
  }
}
  1. Load the decrypted dex into the system
private void loadDex(List<File> dexFiles, File versionDir) throws Exception{
        //1. Get pathlist
        Field pathListField = Utils.findField(getClassLoader(), "pathList");
        Object pathList = pathListField.get(getClassLoader());
        //2. Get the 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 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 the DexPathList
        dexElementsField.set(pathList,newElements);
    }

Decryption has been completed. Let's take a look at encryption. Why do we talk about solving Miller first, because encryption involves signature, packaging and alignment. So leave it to the end.

Dex encryption

Make a dex that contains only decryption code

  1. Execute the following command in SDK \ build tools to get the jar containing dex
dx --dex --output out.dex in.jar
  1. Execute through exec
File aarFile=new 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){
    hrow new RuntimeException("dex error");
}
  1. Encrypt dex files in apk
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 out the dex file and encrypt it
File[] dexFiles=apkTemp.listFiles(new FilenameFilter() {
     @Override
     public boolean accept(File file, String s) {
          return s.endsWith(".dex");
     }
 });
 
//AES encrypted
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();
}
  1. Put dex into apk directory and press it into apk file again
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);

Now you can look at the encrypted files and unencrypted files

Unencrypted apk:


Encrypted apk (only proxy Application can be seen now)

pack

alignment

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

//Align apk
zipalign -c -v 4 output.apk

//Finally, the verification successful prompt indicates that the alignment is successful
  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

Signature packaging apksigner
//SDK \ build tools \ 24.0.3 or above, apk signature tool

apksigner sign  --ks jks File address --ks-key-alias alias --ks-pass pass:jsk password --key-pass pass:Alias password --out  out.apk in.apk

daimachuansoong

Topics: Android