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

How to implement Android multi-channel statistics without packaging

Abstract: In fact, multi-channel packaging can be...

How to do Tik Tok short video marketing in 2019?

Tik Tok was launched on September 26, 2016. After...

How much does it cost to be an agent of a catering mini program in Hengshui?

How much does it cost to be an agent of Hengshui ...

Experts use UC information flow, how to deliver UC headlines?

Mobile information flow ads have been very popula...

Wei Buhuo-A short video editing course that novices can learn in one go

To solve the common solutions to your daily editi...

Those Years of the Tomb Raiders VIP

Online fantasy novel "Those Years of the Tomb...

Have you ever used the black technology code? The god-like automatic code app

When we share screenshots, we usually block out o...

Xue Song Behavioral Finance, Vol. 1

Xue Song's Behavioral Finance, Issue 1 Resour...

Practical experience: 5 steps to teach you how to plan an event!

What I want to tell you about event planning is.....

Gu Chuanling-Postpartum Nutrition Course

Gu Chuanling-Postpartum Nutrition Course Resource...

How to activate the mini program? How to activate WeChat Mini Program?

On January 9, 10 years ago, Jobs released the fir...