Detailed Explanation of Android's Shelling Protection Technologies for Application and Reinforcement

Posted by alex_lana on Fri, 09 Aug 2019 13:51:31 +0200

# 0x00 Preface

Android application reinforcement technology is developed with Android application cracking technology. Reinforcement technology is divided into shell protection and disassembly protection. The mature firms on the market usually use shell protection technology and disassembly technology to reinforce Android applications.

The summary of Android disassembly technology will be discussed in detail in another blog of mine.

http://...

 

The development of Android shell technology can be divided into the following generations:

The first generation shell, dynamic loading shell, obfuscates and encrypts Dex, so and resources of the client, decrypts them at run time, and loads source APK dynamically by customizing ClassLoader.

Second Generation Shell-Code Extraction Shell: The implementation code of the method in Dex is encrypted and stored in the client side. APK runtime calls the shell's Native method to decrypt the encrypted method in Dex, and then decrypts it after execution.

The third generation shell-code obfuscation shell: instruction transformation, flower instruction obfuscation, instruction expansion, code flow obfuscation, etc.

 

# 0x01 First Generation Shell

I. Dex and so confusion

1. Code obfuscation

Modify the class name and method name in source code into the form of a, b and c(), so that it can not be easily found/guessed by class name and method name, thus increasing the difficulty of reverse analysis.

 

2. Compile-time confusion

If APK is compiled using LLVM compiler suite at the time of development, the code can be confused during compilation. It is divided into front-end confusion and IR confusion.

Front-end obfuscation: The obfuscation technique adopted by the compiler front-end in native code analysis

IR obfuscation: A technique for obfuscating the generated intermediate code IR when compiling native code

 

3. Binary confusion

After APK files are generated, the binary files are extracted, and then the binary files are confused. Typical technology is Dex secondary obfuscation, which is to decompile the generated Dex files and then confuse them. This obfuscates Dex's execution process and strings.

 

 1 //Take string obfuscation as an example
 2 
 3 //Before confusion
 4 void methodA()
 5 {
 6     clz.method1("abc");
 7     clz.method3(clz.method2("def"),"ghi");
 8 }
 9 
10 //The disassembly code after code obfuscation using ProGuard may be as follows
11 void methodA()
12 {
13     a.a("abc");
14     a.aaa(a.aa("def"),"ghi");
15 }
16 
17 //The disassembly code after code obfuscation using DexGuard may be as follows, b.a() method
18 void methodA()
19 {
20     a.a(b.a("xxxxxxxxx"));
21     a.aaa(a.aa(b.a("yyyyyyyyy")),b.a("zzzzzzzzz"));
22 }

 

 

2. Dex Encryption

Dex encryption refers to a protection technology that encrypts the DEX files of APK, then decrypts and loads them into memory to execute at run time, so as to prevent the Dex files from being reversed.

The execution process of Dex Encryption APK is as follows:

 

Reinforcement process:

1. Three projects need to be prepared:

(1) Source APK (true APK program)

(2) shell program (Java project, used to encrypt the source APK and merge the encrypted source APK with the dex of the shell program)

(3) Shell program (Android project, used to decrypt encrypted source APK files and load them into memory)

2. The shell program encrypts the source APK into APK files that cannot be read correctly by all operating systems.

3. The classes.dex file of the decrypted APK is extracted from the shell program, and the dex file of the shell program is merged with the encrypted source APK to get a new dex file.

4. Add a 4-bit field at the end of the new dex file to identify the size of the encrypted apk

5. Modify the three header fields of the new dex file: checksum, signature and file_size to ensure the correctness of the new dex file

6. Generate a new classes.dex file, package it, and generate a hardened APK

 

The flow chart of source APK encryption principle is as follows:

 

 

The main logic code analysis of Shell-Adding program:

 1 public static void main(String args[])
 2 {
 3     try
 4     {
 5         //---------get files-------------------------------------------
 6         //Get the source that needs to be encrypted apk program
 7         File payloadSrcFile = new File("xxxx/abc.apk");
 8         System.out.println("SrcAPK size:"+payloadSrcFile.length());
 9         //Acquire shelling(Decrypt)programmatic DEX file,Need to advance the shelling procedure DEX Unzip the file
10         File unshellDexFile = new File("yyyy/classes.dex");
11 
12         //---------Encryption Source apk file----------------------------------------
13         /*
14         Read apk in binary form and encrypt it into payload Array array
15         readFileBytes Read files in binary form
16         encrpt()Is a custom function for encrypting binary streams
17         */
18         byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
19         //Reading Shell in Binary Form dex
20         byte[] unshellDexArray = readFileBytes(unshellDexFile);
21 
22         //---------merge------------------------------------------------
23         //***1.Calculate the new Dex Total length of file
24         int payloadLen = payloadArray.length();    //encryption APK Length
25         int unshellDexLen = unshellDexArray.length();    //Shelling Dex Length
26         int totalLen = payloadLen+unshellDexLen=4    //New Dex File length, last+4 For storage payloadLen Valued
27         //***2.Definition New dex file
28         //Define NEW dex Length of file
29         byte[] newDex = new byte[totalLen];
30         //***3.Start merging
31         //Shell dex Write the contents of the file to the new dex
32         System.arraycopy(unshellDexArray,0,newDex,0,unshellDexLen);
33         //Encrypted source APK Content links at the back
34         /*
35         arraycopy(arr1,x,arr2,y,m)Represents the element that starts arr1 with an index of x, duplicates m elements into arr2, and pastes from the y index of arr2
36         */
37         System.arraycopy(payloadArray,0,newDex,unshellDexLen,payloadLen);
38         //Finally, the encrypted apk Length recorded in the new dex end of file
39         /*
40         intToByte()Converting int-type data to Byte
41         total It's type int, and newDex is type Byte
42         total It's the shelled dex length + the encrypted apk length + 4, so the payload length is finally written from the total-4 index of newDex
43         A byte is one byte, or eight bits; an int is four bytes, or 32 bits; so the last four represents the four elements written to intToByte (payload Len).
44         */
45         System.arraycopy(intToByte(payloadLen),0,newDex,totalLen-4,4);
46 
47         //---------Modify the new dex Header information of files----------------------------------
48         //modify file_size field
49         fixFileSizeHeader(newDex);
50         //modify signature field
51         fixSHA1Header(newDex);
52         //modify checksum field
53         fixCheckSumHeader(newDex);
54 
55         //---------Generate NEW dex file------------------------------------------
56         //Define the path and name of the file to be generated and create an empty file
57         String str = "zzzz/classes.dex";
58         File file = new File(str);
59         if (!file.exists())
60         {
61             file.createNewFile();
62         }
63         //Write merged data to a file
64         FileOutputStream localFileOutputStream = new FileOutputStream(str);
65         localFileOutputStream.write(newDex);
66         localFileOutputStream.flush();
67         localFileOutputStream.close();
68     }catch (Exception e) {
69         e.printStackTrace();
70     }
71 }
72     
73 //Encryption function encrpt()Definition
74 private static byte[] encrpt(byte data[]) 
75 {
76     //Encryption algorithm implementation, do not write here
77     return data;
78 }

 

The principle flow chart of shelling is as follows:

Users implement hardened APP, in fact, they enter the code of the shelled APK. The shelled APK extracts and decrypts the real APK first, but at this time the real APK is not loaded into the memory of the mobile phone. Therefore, it is necessary to extract the real APK decryption and load it into memory before the user interface enters the shell APK at an appropriate time. When is the real APK extracted, decrypted and loaded? The optimal timing is shown in the following analysis:

As shown in the figure above,

(1) The best time to extract and decrypt APK is before the Activeness of the shelled APK is pulled up. Activity is pulled up by onCreate() method. Therefore, the source APK should be extracted and decrypted before onCreate() method.

(2) After extracting and decrypting the source APK, we rewrite the onCreate() method of the shelled APK to make it originate from APK.

 

The main logic code analysis of the shelling program:

 

  1 //********Rewrite attachBaseContext()function***********
  2 protected void attachBaseContext(Context base)
  3 {
  4     super.attachBaseContext(base);
  5     try
  6     {
  7         //---------Extract Encryption apk---------------------------------------------
  8         //Create two private, writable directories payload_odex and payload_lib
  9         File odex = this.getDir("payload_odex",MODE_PRIVATE);    //For storage sources APK Of dex file
 10         File libs = this.getDir("payload_lib",MODE_PRIVATE);    //For storage sources APK Of so file
 11         odexPath = odex.getAbsolutePath();
 12         libPath = libs.getAbsolutePath();
 13         apkFileName = odex.getAbsolutePath()+"/abc.apk";
 14         File dexFile = new File(apkFileName);
 15         Log.i("demo","apk size:"+dexFile.length());
 16         if (!dexFile.exists())
 17         {
 18             //stay payload_odex Under the file directory, create abc.apk Empty files
 19             dexFile.createNewFile();
 20             //Read out the whole shell dex file
 21             //readDexFileFromApk()The method is attachBaseContext()Method Outside Implementation
 22             byte[] dexdata = this.readDexFileFromApk();
 23             //Shelling(Separate encrypted sources apk And decryption),Separation apk Files for dynamic loading
 24             //splitPayloadFromDex()The method is attachBaseContext()Method Outside Implementation
 25             this.splitPayloadFromDex(dexdata);
 26         }
 27         
 28         //---------Load the source after decryption apk program------------------------------------
 29         //Configuring Dynamic Loading Environment
 30         //Obtaining the Current through Reflective Mechanisms activity Thread objects
 31         Object currentActivityThread = RefInvoke.invokeStaticMethod(
 32             "android.app.ActivityThread",
 33             "currentActivityThread",
 34             new Class[] {},
 35             new Object[] {});
 36 
 37         //Capturing Shell apk The name of the package
 38         String packageName = this.getPackageName();
 39         //Getting and setting the current through reflection APK Various information
 40         ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldObject(
 41             "android.app.ActivityThread",
 42             currentActivityThread,
 43             "mPackages");
 44         WeakReference wr = (WeakReference) mPackages.get(packageName);
 45 
 46         //Customize a DexClassLoader Object to load the loaded and decrypted source apk Sum source apk Classes within and C/C++Code
 47         DexClassLoader dLoader = new DexClassLoader(
 48             apkFileName,
 49             odexPath,
 50             libPath,
 51             (ClassLoader) RefInvoke.getFieldObject(
 52                 "android.app.LoadedApk",
 53                 wr.get(),
 54                 "mClassLoader"));
 55         //Put the current process in place DexClassLoader()Set to Shell apk Customized on the previous line DexClassLoader()
 56         RefInvoke.setFiedObject(
 57             "android.app.LoadedApk",
 58             "mClassLoader",
 59             wr.get(),
 60             dLoader);
 61         
 62         Log.i("demo","classloader:"+dLoader);
 63 
 64         try
 65         {
 66             Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
 67             Log.i("demo","actObj:"+actObj);
 68         }
 69         catch (Exception e)
 70         {
 71             Log.i("demo","activity:"+Log.getStackTraceString(e));
 72         }
 73     }catch (Exception e)
 74     {
 75         Log.i("demo","error:"+Log.getStackTraceString(e));
 76         e.printStackTrace();
 77     }
 78 }
 79 
 80 
 81 /*
 82 *@name :readDexFileFromApk()
 83 *@function :Extraction of DEX File Content from Shelling Program
 84 *@param     :nothing
 85 *@throws :IOException
 86 *@return :byte[]
 87 */
 88 private byte[] readDexFileFromApk() throws IOException
 89 {
 90     //Define a Byte Type of array object for storage dex Content of the document
 91     ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
 92     //Define a ZIP Object for decompression APK
 93     ZipInputStream localZipInputStream = new ZipInputStream(
 94         new BufferedInputStream(new FileInputStream(
 95             this.getApplicationInfo().sourceDir)));
 96 
 97     while (true)
 98     {    
 99         ////Unzip and traverse ZIP file,Read arrays as binary streams arrayOfByte[]
100         ZipEntry localZipEntry = localZipInputStream.getNextEntry();
101         if (localZipEntry == null)
102         {
103             localZipInputStream.close();
104             break;
105         }
106         //Find shelling dex file
107         if (localZipEntry.getName().equals("classes.dex"))
108         {
109             
110             byte[] arrayOfByte = new byte[1024];
111             while (true)
112             {
113                 int i = localZipInputStream.read(arrayOfByte);
114                 if (i == -1)
115                 {
116                     break;
117                 }
118                 dexByteArrayOutputStream.write(arrayOfByte,0,i);
119             }
120         }
121         localZipInputStream.closeEntry();
122     }
123     localZipInputStream.close();
124     return dexByteArrayOutputStream.toByteArray();
125 }
126 
127 /*
128 *@name :splitPayloadFromDex(byte apkdata[])
129 *@function :Extract encrypted apk files and so files from dehulled dex files and decrypt them
130 *@param     :apkdata -DEX file binary stream to be decrypted
131 *@throws :IOException
132 *@return :nothing
133 */
134 private void splitPayloadFromDex(byte apkdata[]) throws IOException
135 {
136     //---------Peel off the encryption source APK---------------------------------------------
137     //Get the whole dex Length of file
138     int ablen = apkdata.length();
139     //Acquire shelling dex The last four bits of data, i.e. obtaining source encryption APK Length
140     //ablen-4 stay arraycopy()Chinese means from apkdata The penultimate fourth place begins
141     byte[] dexlen = new byte[4];
142     System.arraycopy(apkdata,ablen-4,dexlen,0,4);
143 
144     //Convert the obtained 4-byte length value to a computable binary array type int type
145     ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
146     DataInputStream in = new DataInputStream(bais);
147     int readInt = in.readInt();
148     System.out.println(Integer.toHexString(readInt));
149 
150     //Define a binary stream array to store the encryption to be extracted apk
151     byte[] newdex = new byte[readInt];
152     //Encryption apk Save to newdex in
153     /*
154     apkdata Is the data content of the entire dex file
155     ablen Is the length of the entire dex file - 4, that is, the location of the entire dex file from the beginning to the end of the encrypted apk
156     readInt Represents the length of the encrypted apk
157     ablen-4 Re-readInt denotes the position from the beginning of the whole DEX to the end of the shelled dex.
158     */
159     System.arraycopy(apkdata,ablen-4-readInt,newdex,0,readInt);
160 
161     //---------Decrypt Source APK-------------------------------------------------------
162     //decrypt()The function is in splitPayloadFromDex()Extrafunctional Realization
163     newdex = decrypt(newdex);
164 
165     //---------Release the decrypted source APK To Disk-----------------------------------------
166     File file = new File(apkFileName);
167     try
168     {
169         FileOutputStream localFileOutputStream = new FileOutputStream(file);
170         localFileOutputStream.write(newdex);
171         localFileOutputStream.close();
172     }catch (IOException e)
173     {
174         throw new RuntimeException(localIOException);
175     }
176     
177     //---------Analyse the source of release APK----------------------------------------------
178     //Define a ZIP object
179     ZipInputStream localZipInputStream = new ZipInputStream(
180         new BufferedInputStream(new FileOutputStream(file)));
181     while (true)
182     {
183         //Unzip and traverse ZIP package
184         ZipEntry localZipEntry = localZipInputStream.getNextEntry();
185         if (localZipInputStream == null)
186         {
187             localZipInputStream.close();
188             break;
189         }
190         //Extraction APK Medium so File, and release it to the sheller program that was initially created on disk payload_lib To go in the catalogue
191         String name = localZipEntry.getName();
192         if (name.startWith("lib/") && name.endwith(".so"))    //Verify whether the traversed file is so file
193         {
194             File storeFile = new File(libPath+"/"+name.substring(name.lastIndexOf('/')));
195             storeFile.createNewFile();
196             FileOutputStream fos = new FileOutputStream(storeFile);
197             byte[] arrayOfByte = new byte[1024];
198             while (true)
199             {
200                 int i = localZipInputStream.read(arrayOfByte);
201                 if (i == -1)
202                 {
203                     break;
204                 }
205                 fos.write(arrayOfByte,0,i);
206             }
207             fos.flush();
208             fos.close();
209         }
210         localZipInputStream.closeEntry();
211     }
212     localZipInputStream.close();
213 }
214 
215 /*
216 *@name :decrypt(byte srcdata[])
217 *@function :Decrypt the encrypted apk file
218 *@param     :srcdata -Binary stream of apk file to be decrypted
219 *@throws :No byte
220 *@return :srcdata -byte[]type
221 */
222 private byte[] decrypt(byte[] srcdata)
223 {
224     //Decryption algorithm, according to the encryption algorithm to write the corresponding decryption process
225     return srcdata;
226 }
227 
228 
229 //**********Rewrite onCreate()Method***********
230 public void onCreate ()
231 {
232     //To be continued
233 }

 

 

 

# 0x02 Second Generation Shell

Updating...

 

 

 

# 3rd Generation Shell 0x03

Updating...

Topics: Android shell Java Mobile