Android apk anti-decompilation technology Part 1 - Packing technology

Android apk anti-decompilation technology Part 1 - Packing technology

I have been working on Android framework for nearly three years . Now the company asked me to do some research on Android APK security, so I recently found a lot of information on the Internet to learn. Now I will summarize the results of my recent study. I will make a series of these results and share them with you so that we can make progress together. This article mainly talks about APK 's shelling technology. Without further ado, I will get straight to the point.

Packing Technology Principle

The so-called apk shelling technology is the same as the PC exe shelling principle, which is to wrap another piece of code outside the program to protect the code inside from being illegally modified or decompiled, and to take control of the program first when the program is running to do some work we want to do. (Haha, it's similar to the principle of viruses)

The principle of PC exe 's packing is as follows:

Android apk packing implementation

The technical points that need to be solved to achieve shelling are as follows:

( 1 ) How to execute our shell program immediately ?

First of all, according to the above principle, if we want to get the control of the program first in apk , as android apk developers, we all know that Application will be called by the system first and our program will be executed here.

( 2 ) How to merge our shell program with the original Android apk file ?

We know that android apk will eventually be packaged to generate a dex file. After our program generates a dex file, we can merge the apk we want to pack and our dex file into one file, and then modify the checksum , signature and file_size information in the dex file header , and attach the length information of the packed apk to the dex file so that we can unpack and ensure the normal operation of the original apk . The structure of the entire file after packing is as follows:

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

According to the merging method in ( 2 ), when our program is first running, the dex file is read in reverse to obtain the original apk file and dynamically loaded through DexClassLoader .

The specific implementation is as follows:

( 1 ) Modify the AndroidMainfest.xml file of the original apk . If the content of 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 modified content 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.shellApplication" >   

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

6.    </application>  

com.android.shellApplication 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 code for merging files is as follows:

  1. public   class ShellTool {
  2. /**
  3. * @param args
  4. */  
  5. public   static   void main(String[] args) {
  6. // TODO Auto-generated method stub  
  7. try {
  8. File payloadSrcFile = new File( "payload.apk" ); //The apk file we want to shell  
  9. File unShellDexFile = new File( "classes.dex" ); //dex file generated by our program  
  10. byte [] payloadArray = encrpt(readFileBytes(payloadSrcFile));
  11. byte [] unShellDexArray = readFileBytes(unShellDexFile);
  12. int payloadLen = payloadArray.length;
  13. int unShellDexLen = unShellDexArray.length;
  14. int totalLen = payloadLen + unShellDexLen + 4 ;
  15. byte [] newdex = new   byte [totalLen];
  16. //Add our program's dex  
  17. System.arraycopy(unShellDexArray, 0 , newdex, 0 , unShellDexLen);
  18. //Add the apk file to be shelled  
  19. System.arraycopy(payloadArray, 0 , newdex, unShellDexLen,
  20. payloadLen);
  21. //Add apk file length  
  22. System.arraycopy(intToByte(payloadLen), 0 , newdex, totalLen- 4 , 4 );
  23. //Modify the DEX file size header  
  24. fixFileSizeHeader(newdex);
  25. //Modify the DEX SHA1 file header  
  26. fixSHA1Header(newdex);
  27. //Modify the DEX CheckSum file header  
  28. fixCheckSumHeader(newdex);
  29.    
  30.    
  31. String str = "outdir/classes.dex" ;
  32. File file = new File(str);
  33. if (!file.exists()) {
  34. file.createNewFile();
  35. }
  36.                   
  37. FileOutputStream localFileOutputStream = new FileOutputStream(str);
  38. localFileOutputStream.write(newdex);
  39. localFileOutputStream.flush();
  40. localFileOutputStream.close();
  41.    
  42.    
  43. } catch (Exception e) {
  44. // TODO Auto-generated catch block  
  45. e.printStackTrace();
  46. }
  47. }
  48.     
  49. // Return data directly, readers can add their own encryption method  
  50. private   static   byte [] encrpt( byte [] srcdata){
  51. return srcdata;
  52. }
  53.    
  54.    
  55. private   static   void fixCheckSumHeader( byte [] dexBytes) {
  56. Adler32 adler = new Adler32();
  57. adler.update(dexBytes, 12 , dexBytes.length - 12 );
  58. long value = adler.getValue();
  59. int va = ( int ) value;
  60. byte [] newcs = intToByte(va);
  61. byte [] recs = new   byte [ 4 ];
  62. for ( int i = 0 ; i < 4 ; i++) {
  63. recs[i] = newcs[newcs.length - 1 - i];
  64. System.out.println(Integer.toHexString(newcs[i]));
  65. }
  66. System.arraycopy(recs, 0 , dexBytes, 8 , 4 );
  67. System.out.println(Long.toHexString(value));
  68. System.out.println();
  69. }
  70.    
  71.    
  72. public   static   byte [] intToByte( int number) {
  73. byte [] b = new   byte [ 4 ];
  74. for ( int i = 3 ; i >= 0 ; i--) {
  75. b[i] = ( byte ) (number % 256 );
  76. number >>= 8 ;
  77. }
  78. return b;
  79. }
  80.    
  81.    
  82. private   static   void fixSHA1Header( byte [] dexBytes)
  83. throws NoSuchAlgorithmException {
  84. MessageDigest md = MessageDigest.getInstance( "SHA-1" );
  85. md.update(dexBytes, 32 , dexBytes.length - 32 );
  86. byte [] newdt = md.digest();
  87. System.arraycopy(newdt, 0 , dexBytes, 12 , 20 );
  88. String hexstr = "" ;
  89. for ( int i = 0 ; i < newdt.length; i++) {
  90. hexstr += Integer.toString((newdt[i] & 0xff ) + 0x100 , 16 )
  91. .substring( 1 );
  92. }
  93. System.out.println(hexstr);
  94. }
  95.    
  96.    
  97. private   static   void fixFileSizeHeader( byte [] dexBytes) {
  98.    
  99.    
  100. byte [] newfs = intToByte(dexBytes.length);
  101. System.out.println(Integer.toHexString(dexBytes.length));
  102. byte [] refs = new   byte [ 4 ];
  103. for ( int i = 0 ; i < 4 ; i++) {
  104. refs[i] = newfs[newfs.length - 1 - i];
  105. System.out.println(Integer.toHexString(newfs[i]));
  106. }
  107. System.arraycopy(refs, 0 , dexBytes, 32 , 4 );
  108. }
  109.    
  110.    
  111. private   static   byte [] readFileBytes(File file) throws IOException {
  112. byte [] arrayOfByte = new   byte [ 1024 ];
  113. ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
  114. FileInputStream fis = new FileInputStream(file);
  115. while ( true ) {
  116. int i = fis.read(arrayOfByte);
  117. if (i != - 1 ) {
  118. localByteArrayOutputStream.write(arrayOfByte, 0 , i);
  119. } else {
  120. return localByteArrayOutputStream.toByteArray();
  121. }
  122. }
  123. }
  124.    
  125.    
  126. }

?

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

  1. public   class shellApplication extends Application {
  2.    
  3.    
  4. private   static   final String appkey = "APPLICATION_CLASS_NAME" ;
  5. private String apkFileName;
  6. private String odexPath;
  7. private String libPath;
  8.    
  9.    
  10. protected   void attachBaseContext(Context base) {
  11. super .attachBaseContext(base);
  12. try {
  13. File odex = this .getDir( "payload_odex" , MODE_PRIVATE);
  14. File libs = this .getDir( "payload_lib" , MODE_PRIVATE);
  15. odexPath = odex.getAbsolutePath();
  16. libPath = libs.getAbsolutePath();
  17. apkFileName = odex.getAbsolutePath() + "/payload.apk" ;
  18. File dexFile = new File(apkFileName);
  19. if (!dexFile.exists())
  20. dexFile.createNewFile();
  21. // Read the program classes.dex file  
  22. byte [] dexdata = this .readDexFileFromApk();
  23. // Separate the unpacked apk file for dynamic loading  
  24. this .splitPayLoadFromDex(dexdata);
  25. //Configure dynamic loading environment  
  26. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  27. "android.app.ActivityThread" , "currentActivityThread" ,
  28. new Class[] {}, new Object[] {});
  29. String packageName = this .getPackageName();
  30. HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect(
  31. "android.app.ActivityThread" , currentActivityThread,
  32. "mPackages" );
  33. WeakReference wr = (WeakReference) mPackages.get(packageName);
  34. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
  35. libPath, (ClassLoader) RefInvoke.getFieldOjbect(
  36. "android.app.LoadedApk" , wr.get(), "mClassLoader" ));
  37. RefInvoke.setFieldOjbect( "android.app.LoadedApk" , "mClassLoader" ,
  38. wr.get(), dLoader);
  39.    
  40.    
  41. } catch (Exception e) {
  42. // TODO Auto-generated catch block  
  43. e.printStackTrace();
  44. }
  45. }
  46.    
  47.    
  48. public   void onCreate() {
  49. {
  50.    
  51.    
  52. // If the source application is configured with an Application object, replace it with the source application Applicaiton so as not to affect the source program logic.  
  53. String appClassName = null ;
  54. try {
  55. ApplicationInfo ai = this .getPackageManager()
  56. .getApplicationInfo( this .getPackageName(),
  57. PackageManager.GET_META_DATA);
  58. Bundle bundle = ai.metaData;
  59. if (bundle != null  
  60. && bundle.containsKey( "APPLICATION_CLASS_NAME" )) {
  61. appClassName = bundle.getString( "APPLICATION_CLASS_NAME" );
  62. } else {
  63. return ;
  64. }
  65. } catch (NameNotFoundException e) {
  66. // TODO Auto-generated catch block  
  67. e.printStackTrace();
  68. }
  69.    
  70.    
  71. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  72. "android.app.ActivityThread" , "currentActivityThread" ,
  73. new Class[] {}, new Object[] {});
  74. Object mBoundApplication = RefInvoke.getFieldOjbect(
  75. "android.app.ActivityThread" , currentActivityThread,
  76. "mBoundApplication" );
  77. Object loadedApkInfo = RefInvoke.getFieldOjbect(
  78. "android.app.ActivityThread$AppBindData" ,
  79. mBoundApplication, "info" );
  80. RefInvoke.setFieldOjbect( "android.app.LoadedApk" , "mApplication" ,
  81. loadedApkInfo, null );
  82. Object oldApplication = RefInvoke.getFieldOjbect(
  83. "android.app.ActivityThread" , currentActivityThread,
  84. "mInitialApplication" );
  85. ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
  86. .getFieldOjbect( "android.app.ActivityThread" ,
  87. currentActivityThread, "mAllApplications" );
  88. mAllApplications.remove(oldApplication);
  89. ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
  90. .getFieldOjbect( "android.app.LoadedApk" , loadedApkInfo,
  91. "mApplicationInfo" );
  92. ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
  93. .getFieldOjbect( "android.app.ActivityThread$AppBindData" ,
  94. mBoundApplication, "appInfo" );
  95. appinfo_In_LoadedApk.className = appClassName;
  96. appinfo_In_AppBindData.className = appClassName;
  97. Application app = (Application) RefInvoke.invokeMethod(
  98. "android.app.LoadedApk" , "makeApplication" , loadedApkInfo,
  99. new Class[] { boolean . class , Instrumentation. class },
  100. new Object[] { false , null });
  101. RefInvoke.setFieldOjbect( "android.app.ActivityThread" ,
  102. "mInitialApplication" , currentActivityThread, app);
  103.    
  104.    
  105. HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect(
  106. "android.app.ActivityThread" , currentActivityThread,
  107. "mProviderMap" );
  108. Iterator it = mProviderMap.values().iterator();
  109. while (it.hasNext()) {
  110. Object providerClientRecord = it.next();
  111. Object localProvider = RefInvoke.getFieldOjbect(
  112. "android.app.ActivityThread$ProviderClientRecord" ,
  113. providerClientRecord, "mLocalProvider" );
  114. RefInvoke.setFieldOjbect( "android.content.ContentProvider" ,
  115. "mContext" , localProvider, app);
  116. }
  117. app.onCreate();
  118. }
  119. }
  120.    
  121.    
  122. private   void splitPayLoadFromDex( byte [] data) throws IOException {
  123. byte [] apkdata = decrypt(data);
  124. int ablen = apkdata.length;
  125. byte [] dexlen = new   byte [ 4 ];
  126. System.arraycopy(apkdata, ablen - 4 , dexlen, 0 , 4 );
  127. ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
  128. DataInputStream in = new DataInputStream(bais);
  129. int readInt = in.readInt();
  130. System.out.println(Integer.toHexString(readInt));
  131. byte [] newdex = new   byte [readInt];
  132. System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0 , readInt);
  133. File file = new File(apkFileName);
  134. try {
  135. FileOutputStream localFileOutputStream = new FileOutputStream(file);
  136. localFileOutputStream.write(newdex);
  137. localFileOutputStream.close();
  138.    
  139.    
  140. } catch (IOException localIOException) {
  141. throw   new RuntimeException(localIOException);
  142. }
  143.    
  144.    
  145. ZipInputStream localZipInputStream = new ZipInputStream(
  146. new BufferedInputStream( new FileInputStream(file)));
  147. while ( true ) {
  148. ZipEntry localZipEntry = localZipInputStream.getNextEntry();
  149. if (localZipEntry == null ) {
  150. localZipInputStream.close();
  151. break ;
  152. }
  153. String name = localZipEntry.getName();
  154. if (name.startsWith( "lib/" ) && name.endsWith( ".so" )) {
  155. File storeFile = new File(libPath + "/"  
  156. + name.substring(name.lastIndexOf( '/' )));
  157. storeFile.createNewFile();
  158. FileOutputStream fos = new FileOutputStream(storeFile);
  159. byte [] arrayOfByte = new   byte [ 1024 ];
  160. while ( true ) {
  161. int i = localZipInputStream.read(arrayOfByte);
  162. if (i == - 1 )
  163. break ;
  164. fos.write(arrayOfByte, 0 , i);
  165. }
  166. fos.flush();
  167. fos.close();
  168. }
  169. localZipInputStream.closeEntry();
  170. }
  171. localZipInputStream.close();
  172.    
  173.    
  174. }
  175.    
  176.    
  177. private   byte [] readDexFileFromApk() throws IOException {
  178. ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
  179. ZipInputStream localZipInputStream = new ZipInputStream(
  180. new BufferedInputStream( new FileInputStream(
  181. this .getApplicationInfo().sourceDir)));
  182. while ( true ) {
  183. ZipEntry localZipEntry = localZipInputStream.getNextEntry();
  184. if (localZipEntry == null ) {
  185. localZipInputStream.close();
  186. break ;
  187. }
  188. if (localZipEntry.getName().equals( "classes.dex" )) {
  189. byte [] arrayOfByte = new   byte [ 1024 ];
  190. while ( true ) {
  191. int i = localZipInputStream.read(arrayOfByte);
  192. if (i == - 1 )
  193. break ;
  194. dexByteArrayOutputStream.write(arrayOfByte, 0 , i);
  195. }
  196. }
  197. localZipInputStream.closeEntry();
  198. }
  199. localZipInputStream.close();
  200. return dexByteArrayOutputStream.toByteArray();
  201. }
  202.    
  203.    
  204. // //Return data directly, readers can add their own decryption method  
  205. private   byte [] decrypt( byte [] data) {
  206. return data;
  207. }
?   Based on the above description, I believe everyone has a certain understanding of apk shelling technology. In the next article, we will explain another android apk anti-decompilation technology - modifying dalvik instructions at runtime (http://my.oschina.net/u/2323218/blog/396203).

<<:  Swift lazy framework

>>:  Do you still remember the text message you left in the corner?

Recommend

Zhihu operates the neglected traffic pool

I am one of the earliest users of Zhihu. During t...

Why do batteries become as “afraid of the cold” as you are in winter?

Produced by: Science Popularization China Author:...

Why can’t I borrow money even though I have a credit limit on JD Gold Bars?

Why can’t I borrow money even though I have a cre...

iOS 12 has these hidden tricks? It’s worth buying an iPhone now!

It has been a while since iOS 12 was released. Wi...

Accenture: AI's impact on 16 industries and 12 economies

The gradual decline in corporate profitability ma...

The mystery behind Shenzhen's rapid rise in the Internet

[[130276]] While Shanghai's Internet industry...

The official TV version of "Three Kingdoms Heroes" is launched

Speaking of Three Kingdoms games, KT Pants-Taking...

Produce special effects for Tik Tok, work 3 hours, monthly salary 20,000-30,000

Produce special effects for Tik Tok, work 3 hours...

How to do a good job in event operation planning process?

The essence of an event is communication, but the...