Android apk encryption plus shell technology

Keywords: Android shell xml SHA1

ABSTRACT: The usage of Chinese fetching doctrine is very good. In order to prevent their apk from being brought, here is a kind of technology to teach you!

I have been working on android framework for nearly three years. Now the company wants to do some research on android apk security, so I am looking for a lot of information to learn on the Internet recently. Now I will make a summary of my recent achievements. I will make a series of achievements to share with you and make progress together. This article mainly talks about the Shell-Adding technology of apk, and goes straight to the topic without much nonsense.

I. Principle of Shelling Technology

The so-called shell technology of apk is the same as the shell principle of pc exe. It is to wrap another piece of code outside the program to protect the code inside from being illegally modified or decompiled, and to give priority to the control of the program when it runs to do what we want to do. (Ha-ha, it's similar to the principle of viruses)

The principle of PC exe shell is as follows:

2. android apk shell implementation

The following technical points need to be solved in order to achieve shell addition:

(1) How to execute our shelling procedure in the first time?

Firstly, according to the above principle, we want to get the control of the program first in apk as the developer of android apk knows that the Application will be called by the system first time and our program will be executed here.

(2) How can we merge our shell program with the original android apk file?

We know that android apk will eventually pack and generate dex files. After our program generates dex files, we can merge the apk we want to shell and our dex files into one file, then modify the checksum, signature and file_size information in the dex file header, and add the length information of the apk to the dex file so that we can unshell and ensure the original. To apk normal operation. After adding the shell, the structure of the whole document is as follows:

(3) How to make the original apk run normally?

According to the merging method in (2), when our program first runs, we retrieve the original apk file from the dex file and load it dynamically through DexClassLoader.

The concrete realization is as follows:

(1) Modify the AndroidMainfest.xml file of the original apk, if the AndroidMainfest.xml file of the original APK is as follows:

1.  <application  

2.      android:icon="@drawable/ic_launcher"  

3.      android:label="@string/app_name"  

4.      android:theme="@style/AppTheme" android:name="com.android.MyApplication" >  

5.  </application>

The revised contents are as follows:

1.  <application  

2.      android:icon="@drawable/ic_launcher"  

3.      android:label="@string/app_name"  

4.      android:theme="@style/AppTheme" android:name="com.android.shellApplication" >  

5.  <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>

6.  </application> 

Com. android. shell application is the name of our application, and

7.  <meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.android.MyApplication"/>

Is the application name of the original apk.

(2) The merged file code is implemented as follows:

public class ShellTool {
  /**
   * @param args
   */
  public static void main(String[] args) {
         // TODO Auto-generated method stub
         try {
                File payloadSrcFile = new File("payload.apk");//We want to shell the apk file
                File unShellDexFile = new File("classes.dex");//dex file generated by our program
                byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
                byte[] unShellDexArray = readFileBytes(unShellDexFile);
                int payloadLen = payloadArray.length;
                int unShellDexLen = unShellDexArray.length;
                int totalLen = payloadLen + unShellDexLen +4;
                byte[] newdex = new byte[totalLen];
                //Add dex to our program
                System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
                //Add the apk file to shell
                System.arraycopy(payloadArray, 0, newdex, unShellDexLen,
                              payloadLen);
                //Add apk file length
                System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-44);
                        //Modify DEX file size file header
                fixFileSizeHeader(newdex);
                //Modify DEX SHA1 File Header
                fixSHA1Header(newdex);
                //Modify the DEX CheckSum file header
                fixCheckSumHeader(newdex);
 
 
                String str = "outdir/classes.dex";
                File file = new File(str);
                if (!file.exists()) {
                       file.createNewFile();
                }
                
                FileOutputStream localFileOutputStream = new FileOutputStream(str);
                localFileOutputStream.write(newdex);
                localFileOutputStream.flush();
                localFileOutputStream.close();
 
 
         } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
         }
  }
  
  //By returning data directly, the reader can add his own encryption method.
  private static byte[] encrpt(byte[] srcdata){
         return srcdata;
  }
 
 
  private static void fixCheckSumHeader(byte[] dexBytes) {
         Adler32 adler = new Adler32();
         adler.update(dexBytes, 12, dexBytes.length - 12);
         long value = adler.getValue();
         int va = (int) value;
         byte[] newcs = intToByte(va);
         byte[] recs = new byte[4];
         for (int i = 0; i < 4; i++) {
                recs[i] = newcs[newcs.length - 1 - i];
                System.out.println(Integer.toHexString(newcs[i]));
         }
         System.arraycopy(recs, 0, dexBytes, 84);
         System.out.println(Long.toHexString(value));
         System.out.println();
  }
 
 
  public static byte[] intToByte(int number) {
         byte[] b = new byte[4];
         for (int i = 3; i >= 0; i--) {
                b[i] = (byte) (number % 256);
                number >>= 8;
         }
         return b;
  }
 
 
  private static void fixSHA1Header(byte[] dexBytes)
                throws NoSuchAlgorithmException {
         MessageDigest md = MessageDigest.getInstance("SHA-1");
         md.update(dexBytes, 32, dexBytes.length - 32);
         byte[] newdt = md.digest();
         System.arraycopy(newdt, 0, dexBytes, 1220);
         String hexstr = "";
         for (int i = 0; i < newdt.length; i++) {
                hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
                              .substring(1);
         }
         System.out.println(hexstr);
  }
 
 
  private static void fixFileSizeHeader(byte[] dexBytes) {
 
 
         byte[] newfs = intToByte(dexBytes.length);
         System.out.println(Integer.toHexString(dexBytes.length));
         byte[] refs = new byte[4];
         for (int i = 0; i < 4; i++) {
                refs[i] = newfs[newfs.length - 1 - i];
                System.out.println(Integer.toHexString(newfs[i]));
         }
         System.arraycopy(refs, 0, dexBytes, 324);
  }
 
 
  private static byte[] readFileBytes(File filethrows IOException {
         byte[] arrayOfByte = new byte[1024];
         ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
         FileInputStream fis = new FileInputStream(file);
         while (true) {
                int i = fis.read(arrayOfByte);
                if (i != -1) {
                       localByteArrayOutputStream.write(arrayOfByte, 0, i);
                } else {
                       return localByteArrayOutputStream.toByteArray();
                }
         }
  }
 
 
}

(3) Load and run the original apk file in our program, the code is as follows:

public class shellApplication extends Application {
 
 
  private static final String appkey = "APPLICATION_CLASS_NAME";
  private String apkFileName;
  private String odexPath;
  private String libPath;
 
 
  protected void attachBaseContext(Context base) {
         super.attachBaseContext(base);
         try {
                File odex = this.getDir("payload_odex", MODE_PRIVATE);
                File libs = this.getDir("payload_lib", MODE_PRIVATE);
                odexPath = odex.getAbsolutePath();
                libPath = libs.getAbsolutePath();
                apkFileName = odex.getAbsolutePath() + "/payload.apk";
                File dexFile = new File(apkFileName);
                if (!dexFile.exists())
                       dexFile.createNewFile();
                //Reader classes.dex file
                byte[] dexdata = this.readDexFileFromApk();
                //Separated apk files have been used for dynamic loading
                this.splitPayLoadFromDex(dexdata);
                //Configuring dynamic loading environment
                Object currentActivityThread = RefInvoke.invokeStaticMethod(
                              "android.app.ActivityThread""currentActivityThread",
                              new Class[] {}, new Object[] {});
                String packageName = this.getPackageName();
                HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mPackages");
                WeakReference wr = (WeakReference) mPackages.get(packageName);
                DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
                              libPath, (ClassLoader) RefInvoke.getFieldOjbect(
                                            "android.app.LoadedApk", wr.get(), "mClassLoader"));
                RefInvoke.setFieldOjbect("android.app.LoadedApk""mClassLoader",
                              wr.get(), dLoader);
 
 
         } catch (Exception e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
         }
  }
 
 
  public void onCreate() {
         {
 
 
                //If the source application is configured with an Action object, it is replaced by the source application Applicaiton so as not to affect the source program logic.
                String appClassName = null;
                try {
                       ApplicationInfo ai = this.getPackageManager()
                                     .getApplicationInfo(this.getPackageName(),
                                                   PackageManager.GET_META_DATA);
                       Bundle bundle = ai.metaData;
                       if (bundle != null
                                     && bundle.containsKey("APPLICATION_CLASS_NAME")) {
                              appClassName = bundle.getString("APPLICATION_CLASS_NAME");
                       } else {
                              return;
                       }
                } catch (NameNotFoundException e) {
                       // TODO Auto-generated catch block
                       e.printStackTrace();
                }
 
 
                Object currentActivityThread = RefInvoke.invokeStaticMethod(
                              "android.app.ActivityThread""currentActivityThread",
                              new Class[] {}, new Object[] {});
                Object mBoundApplication = RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mBoundApplication");
                Object loadedApkInfo = RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread$AppBindData",
                              mBoundApplication, "info");
                RefInvoke.setFieldOjbect("android.app.LoadedApk""mApplication",
                              loadedApkInfo, null);
                Object oldApplication = RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mInitialApplication");
                ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
                              .getFieldOjbect("android.app.ActivityThread",
                                            currentActivityThread, "mAllApplications");
                mAllApplications.remove(oldApplication);
                ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
                              .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
                                            "mApplicationInfo");
                ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
                              .getFieldOjbect("android.app.ActivityThread$AppBindData",
                                            mBoundApplication, "appInfo");
                appinfo_In_LoadedApk.className = appClassName;
                appinfo_In_AppBindData.className = appClassName;
                Application app = (Application) RefInvoke.invokeMethod(
                              "android.app.LoadedApk""makeApplication", loadedApkInfo,
                              new Class[] { boolean.class, Instrumentation.class },
                              new Object[] { falsenull });
                RefInvoke.setFieldOjbect("android.app.ActivityThread",
                              "mInitialApplication", currentActivityThread, app);
 
 
                HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(
                              "android.app.ActivityThread", currentActivityThread,
                              "mProviderMap");
                Iterator it = mProviderMap.values().iterator();
                while (it.hasNext()) {
                       Object providerClientRecord = it.next();
                       Object localProvider = RefInvoke.getFieldOjbect(
                                     "android.app.ActivityThread$ProviderClientRecord",
                                     providerClientRecord, "mLocalProvider");
                       RefInvoke.setFieldOjbect("android.content.ContentProvider",
                                     "mContext", localProvider, app);
                }
                app.onCreate();
         }
  }
 
 
  private void splitPayLoadFromDex(byte[] data) throws IOException {
         byte[] apkdata = decrypt(data);
         int ablen = apkdata.length;
         byte[] dexlen = new byte[4];
         System.arraycopy(apkdata, ablen - 4, dexlen, 04);
         ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
         DataInputStream in = new DataInputStream(bais);
         int readInt = in.readInt();
         System.out.println(Integer.toHexString(readInt));
         byte[] newdex = new byte[readInt];
         System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
         File file = new File(apkFileName);
         try {
                FileOutputStream localFileOutputStream = new FileOutputStream(file);
                localFileOutputStream.write(newdex);
                localFileOutputStream.close();
 
 
         } catch (IOException localIOException) {
                throw new RuntimeException(localIOException);
         }
 
 
         ZipInputStream localZipInputStream = new ZipInputStream(
                       new BufferedInputStream(new FileInputStream(file)));
         while (true) {
                ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                if (localZipEntry == null) {
                       localZipInputStream.close();
                       break;
                }
                String name = localZipEntry.getName();
                if (name.startsWith("lib/") && name.endsWith(".so")) {
                       File storeFile = new File(libPath + "/"
                                     + name.substring(name.lastIndexOf('/')));
                       storeFile.createNewFile();
                       FileOutputStream fos = new FileOutputStream(storeFile);
                       byte[] arrayOfByte = new byte[1024];
                       while (true) {
                              int i = localZipInputStream.read(arrayOfByte);
                              if (i == -1)
                                     break;
                              fos.write(arrayOfByte, 0, i);
                       }
                       fos.flush();
                       fos.close();
                }
                localZipInputStream.closeEntry();
         }
         localZipInputStream.close();
 
 
  }
 
 
  private byte[] readDexFileFromApk() throws IOException {
         ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
         ZipInputStream localZipInputStream = new ZipInputStream(
                       new BufferedInputStream(new FileInputStream(
                                     this.getApplicationInfo().sourceDir)));
         while (true) {
                ZipEntry localZipEntry = localZipInputStream.getNextEntry();
                if (localZipEntry == null) {
                       localZipInputStream.close();
                       break;
                }
                if (localZipEntry.getName().equals("classes.dex")) {
                       byte[] arrayOfByte = new byte[1024];
                       while (true) {
                              int i = localZipInputStream.read(arrayOfByte);
                              if (i == -1)
                                     break;
                              dexByteArrayOutputStream.write(arrayOfByte, 0, i);
                       }
                }
                localZipInputStream.closeEntry();
         }
         localZipInputStream.close();
         return dexByteArrayOutputStream.toByteArray();
  }
 
 
  //// By returning the data directly, the reader can add his own decryption method.
  private byte[] decrypt(byte[] data) {
         return data;
  }

Posted by rosy on Fri, 12 Apr 2019 21:00:33 -0700