One 、 Preface

It's the weekend again , I'm going to blog again after a long time , Let's introduce today Android How to deal with the problems in Apk The principle of reinforcement . At this stage . We know Android The decompilation work in is more and more skillful , We worked hard to develop a apk, It turned out to be decompiled , I'm really upset . Although we are confused , Achieve native layer , But it's all temporary, not permanent . Decompilation technology is being updated , So protect Apk Technology can't stop . Now there are a lot of them on the Internet Apk Reinforced third party platforms , The most famous should belong to : Love encryption and bang bang reinforcement . In fact, some people think that the technology is very advanced , It's not , To put it simply, the source Apk To encrypt , Then put a shell on it , Of course, there are still some details to deal with , This is what this article needs to introduce .

Two 、 Principle analysis

Let's take a look Android The principle of adding shell to China :

We need three objects in the process of reinforcement :

1、 Need to be encrypted Apk( Source Apk)

2、 Shell program Apk( Responsible for decryption Apk Work )

3、 encryption tool ( Will source Apk Encryption and shell Dex Merge into a new Dex)

 

Main steps :

We got what we need to encrypt Apk And your own shell program Apk, Then encrypt the source with an encryption algorithm Apk Encrypts in the shell Apk Merge to get new Dex file , Finally, replace... In the shell program dex File can , Get new Apk, So this new Apk We also call it shelling procedure Apk. He is not a complete person Apk Procedure , His main job is : Responsible for decrypting the source Apk. Then load Apk, Let it work .

One of the things we may need to know in this process is : How to make the source Apk And shell Apk To merge into a new Dex

Here we need to understand Dex The format of the file has changed . Here is a brief introduction Dex File format

Specifically Dex For a detailed description of the file format, you can view this file :http://download.csdn.net/detail/jiangwei0910410003/9102599

Let's mainly take a look at Dex The header information of the file , Actually Dex Document and Class The principle of file format analysis is the same , They all have a fixed format , We know some tools for decompilation now :

1、jd-gui: You can see jar Class in , In fact, he is just analyzing class file , Just know class The format of the file is OK

2、dex2jar: take dex File to jar, The principle is the same , As long as you know Dex File format , It can be resolved that dex The class information in the file is OK

Of course, when we analyze this file , The most important thing is the head information , It should be the beginning of a document , It's also the index part , Internal information is important .

Today we just need to focus on the three parts marked in red :

1) checksum 

File check code , Use alder32 Algorithm check file removal maigc ,checksum All remaining file areas outside , Used to check for file errors .

2) signature 

Use SHA-1 Algorithm hash remove magic ,checksum and signature All remaining file areas outside , Used to uniquely identify this document .

3) file_size

Dex File size .

Why do we only need to focus on these three fields ?

Because we need to put a file ( Encrypted source Apk) Write to Dex in , Then we must modify the file check code (checksum). Because he was checking the documents for errors . that signature Is the same , It's also the only algorithm for identifying files . There is also the need to modify dex File size .

But there's still an operation to be done , That's what we encrypted Apk Size , Because when we were shelling , Need to know Apk Size , In order to get Apk. So where is the value ? This value can be placed directly at the end of the file .

So to summarize, we need to do : modify Dex Three file headers of , Will source Apk The size of is added to the shell dex At the end of .

We get a new one after we modify it Dex The file style is as follows :

So we know the principle , Here's the code implementation . So here are three projects :

1、 Source program project ( Need to be encrypted Apk)

2、 Shelling project ( Decrypt the source Apk And load Apk)

3、 To the source Apk For encryption and shelling projects Dex The merger of

3、 ... and 、 Project case

Let's take a look at the source program first

1、 Source programs that need to be encrypted Apk project :ForceApkObj

Need one Application class , Let's go to the back and say why we need :

MyApplication.java

  1. package com.example.forceapkobj;
  2. import android.app.Application;
  3. import android.util.Log;
  4. public class MyApplication extends Application{
  5. @Override
  6. public void onCreate() {
  7. super.onCreate();
  8. Log.i("demo", "source apk onCreate:"+this);
  9. }
  10. }

Just print it out onCreate Method .

MainActivity.java

  1. package com.example.forceapkobj;
  2. import android.app.Activity;
  3. import android.content.Intent;
  4. import android.os.Bundle;
  5. import android.util.Log;
  6. import android.view.View;
  7. import android.view.View.OnClickListener;
  8. import android.widget.TextView;
  9. public class MainActivity extends Activity {
  10. @Override
  11. protected void onCreate(Bundle savedInstanceState) {
  12. super.onCreate(savedInstanceState);
  13. TextView content = new TextView(this);
  14. content.setText("I am Source Apk");
  15. content.setOnClickListener(new OnClickListener(){
  16. @Override
  17. public void onClick(View arg0) {
  18. Intent intent = new Intent(MainActivity.this, SubActivity.class);
  19. startActivity(intent);
  20. }});
  21. setContentView(content);
  22. Log.i("demo", "app:"+getApplicationContext());
  23. }
  24. }

It's also printing the content .

2、 Shell program project :DexShellTools

The shell program is actually a Java engineering , Because we can see from the above analysis that , His job is to encrypt the source Apk, Then write it to shelling Dex In file , Modify file header , Get a new one Dex File can .

So let's look at the code :

  1. package com.example.reforceapk;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.io.FileInputStream;
  5. import java.io.FileOutputStream;
  6. import java.io.IOException;
  7. import java.security.MessageDigest;
  8. import java.security.NoSuchAlgorithmException;
  9. import java.util.zip.Adler32;
  10. public class mymain {
  11. /**
  12. * @param args
  13. */
  14. public static void main(String[] args) {
  15. // TODO Auto-generated method stub
  16. try {
  17. File payloadSrcFile = new File("force/ForceApkObj.apk");   // A program that needs to be shelled
  18. System.out.println("apk size:"+payloadSrcFile.length());
  19. File unShellDexFile = new File("force/ForceApkObj.dex");    // Jieke dex
  20. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));// Read in binary form apk, And encrypt it // To the source Apk Perform encryption operation
  21. byte[] unShellDexArray = readFileBytes(unShellDexFile);// Read in binary form dex
  22. int payloadLen = payloadArray.length;
  23. int unShellDexLen = unShellDexArray.length;
  24. int totalLen = payloadLen + unShellDexLen +4;// More 4 Bytes are the length of storage .
  25. byte[] newdex = new byte[totalLen]; //  Applied for a new length
  26. // Add shelling code
  27. System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);// Copy first dex Content
  28. // Add encrypted shelling data
  29. System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);// And then dex Copy behind the content apk The content of
  30. // Add shelling data length
  31. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);// Last 4 For the length
  32. // modify DEX file size The file header
  33. fixFileSizeHeader(newdex);
  34. // modify DEX SHA1  The file header
  35. fixSHA1Header(newdex);
  36. // modify DEX CheckSum The file header
  37. fixCheckSumHeader(newdex);
  38. String str = "force/classes.dex";
  39. File file = new File(str);
  40. if (!file.exists()) {
  41. file.createNewFile();
  42. }
  43. FileOutputStream localFileOutputStream = new FileOutputStream(str);
  44. localFileOutputStream.write(newdex);
  45. localFileOutputStream.flush();
  46. localFileOutputStream.close();
  47. } catch (Exception e) {
  48. e.printStackTrace();
  49. }
  50. }
  51. // Direct return data , Readers can add their own encryption methods
  52. private static byte[] encrpt(byte[] srcdata){
  53. for(int i = 0;i<srcdata.length;i++){
  54. srcdata[i] = (byte)(0xFF ^ srcdata[i]);
  55. }
  56. return srcdata;
  57. }
  58. /**
  59. *  modify dex head ,CheckSum  Check code
  60. * @param dexBytes
  61. */
  62. private static void fixCheckSumHeader(byte[] dexBytes) {
  63. Adler32 adler = new Adler32();
  64. adler.update(dexBytes, 12, dexBytes.length - 12);// from 12 Calculate the check code at the end of the file
  65. long value = adler.getValue();
  66. int va = (int) value;
  67. byte[] newcs = intToByte(va);
  68. // High in the former , Low down in front
  69. byte[] recs = new byte[4];
  70. for (int i = 0; i < 4; i++) {
  71. recs[i] = newcs[newcs.length - 1 - i];
  72. System.out.println(Integer.toHexString(newcs[i]));
  73. }
  74. System.arraycopy(recs, 0, dexBytes, 8, 4);// Validation code assignment (8-11)
  75. System.out.println(Long.toHexString(value));
  76. System.out.println();
  77. }
  78. /**
  79. * int  turn byte[]
  80. * @param number
  81. * @return
  82. */
  83. public static byte[] intToByte(int number) {
  84. byte[] b = new byte[4];
  85. for (int i = 3; i >= 0; i--) {
  86. b[i] = (byte) (number % 256);
  87. number >>= 8;
  88. }
  89. return b;
  90. }
  91. /**
  92. *  modify dex head  sha1 value
  93. * @param dexBytes
  94. * @throws NoSuchAlgorithmException
  95. */
  96. private static void fixSHA1Header(byte[] dexBytes)
  97. throws NoSuchAlgorithmException {
  98. MessageDigest md = MessageDigest.getInstance("SHA-1");
  99. md.update(dexBytes, 32, dexBytes.length - 32);// from 32 For the end of the calculation sha--1
  100. byte[] newdt = md.digest();
  101. System.arraycopy(newdt, 0, dexBytes, 12, 20);// modify sha-1 value (12-31)
  102. // Output sha-1 value , not essential
  103. String hexstr = "";
  104. for (int i = 0; i < newdt.length; i++) {
  105. hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
  106. .substring(1);
  107. }
  108. System.out.println(hexstr);
  109. }
  110. /**
  111. *  modify dex head  file_size value
  112. * @param dexBytes
  113. */
  114. private static void fixFileSizeHeader(byte[] dexBytes) {
  115. // New file length
  116. byte[] newfs = intToByte(dexBytes.length);
  117. System.out.println(Integer.toHexString(dexBytes.length));
  118. byte[] refs = new byte[4];
  119. // High in the former , Low down in front
  120. for (int i = 0; i < 4; i++) {
  121. refs[i] = newfs[newfs.length - 1 - i];
  122. System.out.println(Integer.toHexString(newfs[i]));
  123. }
  124. System.arraycopy(refs, 0, dexBytes, 32, 4);// modify (32-35)
  125. }
  126. /**
  127. *  Read out the contents of the file in binary
  128. * @param file
  129. * @return
  130. * @throws IOException
  131. */
  132. private static byte[] readFileBytes(File file) throws IOException {
  133. byte[] arrayOfByte = new byte[1024];
  134. ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
  135. FileInputStream fis = new FileInputStream(file);
  136. while (true) {
  137. int i = fis.read(arrayOfByte);
  138. if (i != -1) {
  139. localByteArrayOutputStream.write(arrayOfByte, 0, i);
  140. } else {
  141. return localByteArrayOutputStream.toByteArray();
  142. }
  143. }
  144. }
  145. }

Let's analyze :

The red part is actually the core work :

1>、 Encryption source Apk file

  1. byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));// Read in binary form apk, And encrypt it // To the source Apk Perform encryption operation

The encryption algorithm is simple :

  1. // Direct return data , Readers can add their own encryption methods
  2. private static byte[] encrpt(byte[] srcdata){
  3. for(int i = 0;i<srcdata.length;i++){
  4. srcdata[i] = (byte)(0xFF ^ srcdata[i]);
  5. }
  6. return srcdata;
  7. }

XOR each byte .

( explain : This is for simplicity , So it uses a very simple encryption algorithm , In fact, in order to increase the difficulty of cracking , We should use more efficient encryption algorithms , Colleagues had better put the encryption operation in native Layer to do )

2>、 Merge files : Will be encrypted after Apk And the original shelling Dex A merger

  1. int payloadLen = payloadArray.length;
  2. int unShellDexLen = unShellDexArray.length;
  3. int totalLen = payloadLen + unShellDexLen +4;// More 4 Bytes are the length of storage .
  4. byte[] newdex = new byte[totalLen]; //  Applied for a new length
  5. // Add shelling code
  6. System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);// Copy first dex Content
  7. // Add encrypted shelling data
  8. System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);// And then dex Copy behind the content apk The content of

3>、 Append the source program at the end of the file Apk The length of

  1. // Add shelling data length
  2. System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4);// Last 4 For the length

4>、 Modify new Dex File header information of the file :file_size; sha1; check_sum

  1. // modify DEX file size The file header
  2. fixFileSizeHeader(newdex);
  3. // modify DEX SHA1  The file header
  4. fixSHA1Header(newdex);
  5. // modify DEX CheckSum The file header
  6. fixCheckSumHeader(newdex);

The specific modification can refer to the file header format mentioned before , Modify the byte value of the specified location .

Here we need two more input files :

1>、 Source Apk file :ForceApkObj.apk

2>、 The shelling procedure is Dex file :ForceApkObj.dex

So the first document we all know , After the source program above is compiled Apk file , So how do we get the second document ? This is the third project we're going to talk about : Shelling program project , He is a Android project , After we compile , To be able to get his classes.dex file , Then change the name .

3、 Shelling project :ReforceApk

Before we talk about this project , Let's take a look at the work of this shelling project first :

1>、 To displace by reflection android.app.ActivityThread Medium mClassLoader Decrypt for load APK Of DexClassLoader, The DexClassLoader On the one hand, the source program is loaded 、 On the other hand, with the original mClassLoader Parent node , This ensures that the source program is loaded without giving up the previously loaded resources and system code .

About this part , If you don't know, you can have a look ActivityThread.java Source code :

Or just look at this article :

http://blog.csdn.net/jiangwei0910410003/article/details/48104455

How to get the system load Apk Class loader for , And then how do we load it in Apk Running and other issues are mentioned in this article .

2>、 Find the source Application, Build and run... By reflection .

What needs to be noted here is , We are now loading a complete Apk, Let him run , So we know a Apk When running, there is one Application Object's , This is also a global class after the program runs . So we have to find the source after decryption Apk Of Application class , Running his onCreate Method , Such a source Apk Just started his life cycle . How do we get the source here Apk Of Application The class of ? This we will say later . Use meta Label to set .

Let's take a look at the overall flow chart :

So we see that there is still a core technology needed here, which is dynamic loading . About dynamic loading technology , Students who don't know can read this article :

http://blog.csdn.net/jiangwei0910410003/article/details/48104581

Let's take a look at the code :

  1. package com.example.reforceapk;
  2. import java.io.BufferedInputStream;
  3. import java.io.ByteArrayInputStream;
  4. import java.io.ByteArrayOutputStream;
  5. import java.io.DataInputStream;
  6. import java.io.File;
  7. import java.io.FileInputStream;
  8. import java.io.FileOutputStream;
  9. import java.io.IOException;
  10. import java.lang.ref.WeakReference;
  11. import java.lang.reflect.Method;
  12. import java.util.ArrayList;
  13. import java.util.HashMap;
  14. import java.util.Iterator;
  15. import java.util.zip.ZipEntry;
  16. import java.util.zip.ZipInputStream;
  17. import android.app.Application;
  18. import android.app.Instrumentation;
  19. import android.content.Context;
  20. import android.content.pm.ApplicationInfo;
  21. import android.content.pm.PackageManager;
  22. import android.content.pm.PackageManager.NameNotFoundException;
  23. import android.content.res.AssetManager;
  24. import android.content.res.Resources;
  25. import android.content.res.Resources.Theme;
  26. import android.os.Bundle;
  27. import android.util.ArrayMap;
  28. import android.util.Log;
  29. import dalvik.system.DexClassLoader;
  30. public class ProxyApplication extends Application{
  31. private static final String appkey = "APPLICATION_CLASS_NAME";
  32. private String apkFileName;
  33. private String odexPath;
  34. private String libPath;
  35. // This is a context  assignment
  36. @Override
  37. protected void attachBaseContext(Context base) {
  38. super.attachBaseContext(base);
  39. try {
  40. // Create two folders payload_odex,payload_lib  Private , Writable file directory
  41. File odex = this.getDir("payload_odex", MODE_PRIVATE);
  42. File libs = this.getDir("payload_lib", MODE_PRIVATE);
  43. odexPath = odex.getAbsolutePath();
  44. libPath = libs.getAbsolutePath();
  45. apkFileName = odex.getAbsolutePath() + "/payload.apk";
  46. File dexFile = new File(apkFileName);
  47. Log.i("demo", "apk size:"+dexFile.length());
  48. if (!dexFile.exists())
  49. {
  50. dexFile.createNewFile();  // stay payload_odex In the folder , establish payload.apk
  51. //  Read program classes.dex file
  52. byte[] dexdata = this.readDexFileFromApk();
  53. //  After separating the shell apk The file has been used for dynamic loading
  54. this.splitPayLoadFromDex(dexdata);
  55. }
  56. //  Configure the dynamic loading environment
  57. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  58. "android.app.ActivityThread", "currentActivityThread",
  59. new Class[] {}, new Object[] {});// Get the main thread object  http://blog.csdn.net/myarrow/article/details/14223493
  60. String packageName = this.getPackageName();// At present apk The package name
  61. // The following two sentences are not very clear
  62. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
  63. "android.app.ActivityThread", currentActivityThread,
  64. "mPackages");
  65. WeakReference wr = (WeakReference) mPackages.get(packageName);
  66. // Create a shell apk Of DexClassLoader object    load apk Classes and local code in (c/c++ Code )
  67. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
  68. libPath, (ClassLoader) RefInvoke.getFieldOjbect(
  69. "android.app.LoadedApk", wr.get(), "mClassLoader"));
  70. //base.getClassLoader();  Is it the same as  (ClassLoader) RefInvoke.getFieldOjbect()?  If you have time to verify //?
  71. // Put the current process of DexClassLoader  Set to be shelled apk Of DexClassLoader  ---- somewhat c++ Process environment in the process ~~
  72. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
  73. wr.get(), dLoader);
  74. Log.i("demo","classloader:"+dLoader);
  75. try{
  76. Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
  77. Log.i("demo", "actObj:"+actObj);
  78. }catch(Exception e){
  79. Log.i("demo", "activity:"+Log.getStackTraceString(e));
  80. }
  81. } catch (Exception e) {
  82. Log.i("demo", "error:"+Log.getStackTraceString(e));
  83. e.printStackTrace();
  84. }
  85. }
  86. @Override
  87. public void onCreate() {
  88. {
  89. //loadResources(apkFileName);
  90. Log.i("demo", "onCreate");
  91. //  If the source application is configured with Appliction object , Then replace with the source application Applicaiton, So as not to affect the source program logic .
  92. String appClassName = null;
  93. try {
  94. ApplicationInfo ai = this.getPackageManager()
  95. .getApplicationInfo(this.getPackageName(),
  96. PackageManager.GET_META_DATA);
  97. Bundle bundle = ai.metaData;
  98. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
  99. appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className  Is configured in xml In the document .
  100. } else {
  101. Log.i("demo", "have no application class name");
  102. return;
  103. }
  104. } catch (NameNotFoundException e) {
  105. Log.i("demo", "error:"+Log.getStackTraceString(e));
  106. e.printStackTrace();
  107. }
  108. // If there is a value, call the Applicaiton
  109. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  110. "android.app.ActivityThread", "currentActivityThread",
  111. new Class[] {}, new Object[] {});
  112. Object mBoundApplication = RefInvoke.getFieldOjbect(
  113. "android.app.ActivityThread", currentActivityThread,
  114. "mBoundApplication");
  115. Object loadedApkInfo = RefInvoke.getFieldOjbect(
  116. "android.app.ActivityThread$AppBindData",
  117. mBoundApplication, "info");
  118. // Put the current process of mApplication  Set up a null
  119. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
  120. loadedApkInfo, null);
  121. Object oldApplication = RefInvoke.getFieldOjbect(
  122. "android.app.ActivityThread", currentActivityThread,
  123. "mInitialApplication");
  124. //http://www.codeceo.com/article/android-context.html
  125. ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
  126. .getFieldOjbect("android.app.ActivityThread",
  127. currentActivityThread, "mAllApplications");
  128. mAllApplications.remove(oldApplication);// Delete oldApplication
  129. ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
  130. .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
  131. "mApplicationInfo");
  132. ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
  133. .getFieldOjbect("android.app.ActivityThread$AppBindData",
  134. mBoundApplication, "appInfo");
  135. appinfo_In_LoadedApk.className = appClassName;
  136. appinfo_In_AppBindData.className = appClassName;
  137. Application app = (Application) RefInvoke.invokeMethod(
  138. "android.app.LoadedApk", "makeApplication", loadedApkInfo,
  139. new Class[] { boolean.class, Instrumentation.class },
  140. new Object[] { false, null });// perform  makeApplication(false,null)
  141. RefInvoke.setFieldOjbect("android.app.ActivityThread",
  142. "mInitialApplication", currentActivityThread, app);
  143. ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(
  144. "android.app.ActivityThread", currentActivityThread,
  145. "mProviderMap");
  146. Iterator it = mProviderMap.values().iterator();
  147. while (it.hasNext()) {
  148. Object providerClientRecord = it.next();
  149. Object localProvider = RefInvoke.getFieldOjbect(
  150. "android.app.ActivityThread$ProviderClientRecord",
  151. providerClientRecord, "mLocalProvider");
  152. RefInvoke.setFieldOjbect("android.content.ContentProvider",
  153. "mContext", localProvider, app);
  154. }
  155. Log.i("demo", "app:"+app);
  156. app.onCreate();
  157. }
  158. }
  159. /**
  160. *  Release the shelled apk file ,so file
  161. * @param data
  162. * @throws IOException
  163. */
  164. private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
  165. int ablen = apkdata.length;
  166. // Take the shell apk The length of     The value of length here is , We can simplify the assignment of the length of the shell
  167. byte[] dexlen = new byte[4];
  168. System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
  169. ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
  170. DataInputStream in = new DataInputStream(bais);
  171. int readInt = in.readInt();
  172. System.out.println(Integer.toHexString(readInt));
  173. byte[] newdex = new byte[readInt];
  174. // Shell it apk Copy the content to newdex in
  175. System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
  176. // It should be added here for apk Decryption operation , If shelling is encryption
  177. //?
  178. // To the source program Apk To decrypt
  179. newdex = decrypt(newdex);
  180. // write in apk file
  181. File file = new File(apkFileName);
  182. try {
  183. FileOutputStream localFileOutputStream = new FileOutputStream(file);
  184. localFileOutputStream.write(newdex);
  185. localFileOutputStream.close();
  186. } catch (IOException localIOException) {
  187. throw new RuntimeException(localIOException);
  188. }
  189. // Analyze the shelled apk file
  190. ZipInputStream localZipInputStream = new ZipInputStream(
  191. new BufferedInputStream(new FileInputStream(file)));
  192. while (true) {
  193. ZipEntry localZipEntry = localZipInputStream.getNextEntry();// I don't know if this also traverses subdirectories , It seems to be traversal
  194. if (localZipEntry == null) {
  195. localZipInputStream.close();
  196. break;
  197. }
  198. // Take out the shell apk Use of so file , Put it in  libPath in (data/data/ Package name /payload_lib)
  199. String name = localZipEntry.getName();
  200. if (name.startsWith("lib/") && name.endsWith(".so")) {
  201. File storeFile = new File(libPath + "/"
  202. + name.substring(name.lastIndexOf('/')));
  203. storeFile.createNewFile();
  204. FileOutputStream fos = new FileOutputStream(storeFile);
  205. byte[] arrayOfByte = new byte[1024];
  206. while (true) {
  207. int i = localZipInputStream.read(arrayOfByte);
  208. if (i == -1)
  209. break;
  210. fos.write(arrayOfByte, 0, i);
  211. }
  212. fos.flush();
  213. fos.close();
  214. }
  215. localZipInputStream.closeEntry();
  216. }
  217. localZipInputStream.close();
  218. }
  219. /**
  220. *  from apk Get it in the bag dex The contents of the document (byte)
  221. * @return
  222. * @throws IOException
  223. */
  224. private byte[] readDexFileFromApk() throws IOException {
  225. ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
  226. ZipInputStream localZipInputStream = new ZipInputStream(
  227. new BufferedInputStream(new FileInputStream(
  228. this.getApplicationInfo().sourceDir)));
  229. while (true) {
  230. ZipEntry localZipEntry = localZipInputStream.getNextEntry();
  231. if (localZipEntry == null) {
  232. localZipInputStream.close();
  233. break;
  234. }
  235. if (localZipEntry.getName().equals("classes.dex")) {
  236. byte[] arrayOfByte = new byte[1024];
  237. while (true) {
  238. int i = localZipInputStream.read(arrayOfByte);
  239. if (i == -1)
  240. break;
  241. dexByteArrayOutputStream.write(arrayOfByte, 0, i);
  242. }
  243. }
  244. localZipInputStream.closeEntry();
  245. }
  246. localZipInputStream.close();
  247. return dexByteArrayOutputStream.toByteArray();
  248. }
  249. // // Direct return data , Readers can add their own decryption methods
  250. private byte[] decrypt(byte[] srcdata) {
  251. for(int i=0;i<srcdata.length;i++){
  252. srcdata[i] = (byte)(0xFF ^ srcdata[i]);
  253. }
  254. return srcdata;
  255. }
  256. // Here's how to load resources
  257. protected AssetManager mAssetManager;// Explorer
  258. protected Resources mResources;// resources
  259. protected Theme mTheme;// The theme
  260. protected void loadResources(String dexPath) {
  261. try {
  262. AssetManager assetManager = AssetManager.class.newInstance();
  263. Method addAssetPath = assetManager.getClass().getMethod("addAssetPath", String.class);
  264. addAssetPath.invoke(assetManager, dexPath);
  265. mAssetManager = assetManager;
  266. } catch (Exception e) {
  267. Log.i("inject", "loadResource error:"+Log.getStackTraceString(e));
  268. e.printStackTrace();
  269. }
  270. Resources superRes = super.getResources();
  271. superRes.getDisplayMetrics();
  272. superRes.getConfiguration();
  273. mResources = new Resources(mAssetManager, superRes.getDisplayMetrics(),superRes.getConfiguration());
  274. mTheme = mResources.newTheme();
  275. mTheme.setTo(super.getTheme());
  276. }
  277. @Override
  278. public AssetManager getAssets() {
  279. return mAssetManager == null ? super.getAssets() : mAssetManager;
  280. }
  281. @Override
  282. public Resources getResources() {
  283. return mResources == null ? super.getResources() : mResources;
  284. }
  285. @Override
  286. public Theme getTheme() {
  287. return mTheme == null ? super.getTheme() : mTheme;
  288. }
  289. }

First of all, let's take a look at the code implementation of the specific steps :

1>、 Get shelled Apk Medium dex file , Then get the source program from this file Apk. To decrypt , Then load

  1. // This is a context  assignment
  2. @Override
  3. protected void attachBaseContext(Context base) {
  4. super.attachBaseContext(base);
  5. try {
  6. // Create two folders payload_odex,payload_lib  Private , Writable file directory
  7. File odex = this.getDir("payload_odex", MODE_PRIVATE);
  8. File libs = this.getDir("payload_lib", MODE_PRIVATE);
  9. odexPath = odex.getAbsolutePath();
  10. libPath = libs.getAbsolutePath();
  11. apkFileName = odex.getAbsolutePath() + "/payload.apk";
  12. File dexFile = new File(apkFileName);
  13. Log.i("demo", "apk size:"+dexFile.length());
  14. if (!dexFile.exists())
  15. {
  16. dexFile.createNewFile();  // stay payload_odex In the folder , establish payload.apk
  17. //  Read program classes.dex file
  18. byte[] dexdata = this.readDexFileFromApk();
  19. //  After separating the shell apk The file has been used for dynamic loading
  20. this.splitPayLoadFromDex(dexdata);
  21. }
  22. //  Configure the dynamic loading environment
  23. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  24. "android.app.ActivityThread", "currentActivityThread",
  25. new Class[] {}, new Object[] {});// Get the main thread object  http://blog.csdn.net/myarrow/article/details/14223493
  26. String packageName = this.getPackageName();// At present apk The package name
  27. // The following two sentences are not very clear
  28. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
  29. "android.app.ActivityThread", currentActivityThread,
  30. "mPackages");
  31. WeakReference wr = (WeakReference) mPackages.get(packageName);
  32. // Create a shell apk Of DexClassLoader object    load apk Classes and local code in (c/c++ Code )
  33. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
  34. libPath, (ClassLoader) RefInvoke.getFieldOjbect(
  35. "android.app.LoadedApk", wr.get(), "mClassLoader"));
  36. //base.getClassLoader();  Is it the same as  (ClassLoader) RefInvoke.getFieldOjbect()?  If you have time to verify //?
  37. // Put the current process of DexClassLoader  Set to be shelled apk Of DexClassLoader  ---- somewhat c++ Process environment in the process ~~
  38. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
  39. wr.get(), dLoader);
  40. Log.i("demo","classloader:"+dLoader);
  41. try{
  42. Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
  43. Log.i("demo", "actObj:"+actObj);
  44. }catch(Exception e){
  45. Log.i("demo", "activity:"+Log.getStackTraceString(e));
  46. }
  47. } catch (Exception e) {
  48. Log.i("demo", "error:"+Log.getStackTraceString(e));
  49. e.printStackTrace();
  50. }
  51. }

Here's a problem to pay attention to , It's that we need to find an opportunity , It's when the shelling program hasn't run yet , To load the source program Apk, Carry out his onCreate Method , Well, it can't be too late , otherwise , Just run the shelling program , Instead of the source program . Looking at the source code, we know .Application There is a method in :attachBaseContext This method , He was in Application Of onCreate The method is executed before it is executed , So our work needs to be done here

1)、 From the shelling process Apk Find the source program in Apk, And decrypt

  1. // Create two folders payload_odex,payload_lib  Private , Writable file directory
  2. File odex = this.getDir("payload_odex", MODE_PRIVATE);
  3. File libs = this.getDir("payload_lib", MODE_PRIVATE);
  4. odexPath = odex.getAbsolutePath();
  5. libPath = libs.getAbsolutePath();
  6. apkFileName = odex.getAbsolutePath() + "/payload.apk";
  7. File dexFile = new File(apkFileName);
  8. Log.i("demo", "apk size:"+dexFile.length());
  9. if (!dexFile.exists())
  10. {
  11. dexFile.createNewFile();  // stay payload_odex In the folder , establish payload.apk
  12. //  Read program classes.dex file
  13. byte[] dexdata = this.readDexFileFromApk();
  14. //  After separating the shell apk The file has been used for dynamic loading
  15. this.splitPayLoadFromDex(dexdata);
  16. }

This shelling and decryption operation must correspond to our previous shelling and encryption operations , Or there will be Dex Load error problem

A) from Apk Get to the Dex file

  1. /**
  2. *  from apk Get it in the bag dex The contents of the document (byte)
  3. * @return
  4. * @throws IOException
  5. */
  6. private byte[] readDexFileFromApk() throws IOException {
  7. ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
  8. ZipInputStream localZipInputStream = new ZipInputStream(
  9. new BufferedInputStream(new FileInputStream(
  10. this.getApplicationInfo().sourceDir)));
  11. while (true) {
  12. ZipEntry localZipEntry = localZipInputStream.getNextEntry();
  13. if (localZipEntry == null) {
  14. localZipInputStream.close();
  15. break;
  16. }
  17. if (localZipEntry.getName().equals("classes.dex")) {
  18. byte[] arrayOfByte = new byte[1024];
  19. while (true) {
  20. int i = localZipInputStream.read(arrayOfByte);
  21. if (i == -1)
  22. break;
  23. dexByteArrayOutputStream.write(arrayOfByte, 0, i);
  24. }
  25. }
  26. localZipInputStream.closeEntry();
  27. }
  28. localZipInputStream.close();
  29. return dexByteArrayOutputStream.toByteArray();
  30. }

It's just decompression Apk file , Directly obtained dex File can

B) From shelling Dex Get the source from Apk file

  1. /**
  2. *  Release the shelled apk file ,so file
  3. * @param data
  4. * @throws IOException
  5. */
  6. private void splitPayLoadFromDex(byte[] apkdata) throws IOException {
  7. int ablen = apkdata.length;
  8. // Take the shell apk The length of     The value of length here is , We can simplify the assignment of the length of the shell
  9. byte[] dexlen = new byte[4];
  10. System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4);
  11. ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
  12. DataInputStream in = new DataInputStream(bais);
  13. int readInt = in.readInt();
  14. System.out.println(Integer.toHexString(readInt));
  15. byte[] newdex = new byte[readInt];
  16. // Shell it apk Copy the content to newdex in
  17. System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt);
  18. // It should be added here for apk Decryption operation , If shelling is encryption
  19. //?
  20. // To the source program Apk To decrypt
  21. newdex = decrypt(newdex);
  22. // write in apk file
  23. File file = new File(apkFileName);
  24. try {
  25. FileOutputStream localFileOutputStream = new FileOutputStream(file);
  26. localFileOutputStream.write(newdex);
  27. localFileOutputStream.close();
  28. } catch (IOException localIOException) {
  29. throw new RuntimeException(localIOException);
  30. }
  31. // Analyze the shelled apk file
  32. ZipInputStream localZipInputStream = new ZipInputStream(
  33. new BufferedInputStream(new FileInputStream(file)));
  34. while (true) {
  35. ZipEntry localZipEntry = localZipInputStream.getNextEntry();// I don't know if this also traverses subdirectories , It seems to be traversal
  36. if (localZipEntry == null) {
  37. localZipInputStream.close();
  38. break;
  39. }
  40. // Take out the shell apk Use of so file , Put it in  libPath in (data/data/ Package name /payload_lib)
  41. String name = localZipEntry.getName();
  42. if (name.startsWith("lib/") && name.endsWith(".so")) {
  43. File storeFile = new File(libPath + "/"
  44. + name.substring(name.lastIndexOf('/')));
  45. storeFile.createNewFile();
  46. FileOutputStream fos = new FileOutputStream(storeFile);
  47. byte[] arrayOfByte = new byte[1024];
  48. while (true) {
  49. int i = localZipInputStream.read(arrayOfByte);
  50. if (i == -1)
  51. break;
  52. fos.write(arrayOfByte, 0, i);
  53. }
  54. fos.flush();
  55. fos.close();
  56. }
  57. localZipInputStream.closeEntry();
  58. }
  59. localZipInputStream.close();
  60. }

C) Decrypt the source program Apk

  1. //// Direct return data , Readers can add their own decryption methods
  2. private byte[] decrypt(byte[] srcdata) {
  3. for(int i=0;i<srcdata.length;i++){
  4. srcdata[i] = (byte)(0xFF ^ srcdata[i]);
  5. }
  6. return srcdata;
  7. }

The decryption algorithm is consistent with the encryption algorithm

2>、 Load the source program after decryption Apk

  1. // Configure the dynamic loading environment
  2. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  3. "android.app.ActivityThread", "currentActivityThread",
  4. new Class[] {}, new Object[] {});// Get the main thread object  http://blog.csdn.net/myarrow/article/details/14223493
  5. String packageName = this.getPackageName();// At present apk The package name
  6. // The following two sentences are not very clear
  7. ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
  8. "android.app.ActivityThread", currentActivityThread,
  9. "mPackages");
  10. WeakReference wr = (WeakReference) mPackages.get(packageName);
  11. // Create a shell apk Of DexClassLoader object    load apk Classes and local code in (c/c++ Code )
  12. DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath,
  13. libPath, (ClassLoader) RefInvoke.getFieldOjbect(
  14. "android.app.LoadedApk", wr.get(), "mClassLoader"));
  15. //base.getClassLoader();  Is it the same as  (ClassLoader) RefInvoke.getFieldOjbect()?  If you have time to verify //?
  16. // Put the current process of DexClassLoader  Set to be shelled apk Of DexClassLoader  ---- somewhat c++ Process environment in the process ~~
  17. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
  18. wr.get(), dLoader);
  19. Log.i("demo","classloader:"+dLoader);
  20. try{
  21. Object actObj = dLoader.loadClass("com.example.forceapkobj.MainActivity");
  22. Log.i("demo", "actObj:"+actObj);
  23. }catch(Exception e){
  24. Log.i("demo", "activity:"+Log.getStackTraceString(e));
  25. }

2)、 Find the source Application Program , Let it run

  1. @Override
  2. public void onCreate() {
  3. {
  4. //loadResources(apkFileName);
  5. Log.i("demo", "onCreate");
  6. //  If the source application is configured with Appliction object , Then replace with the source application Applicaiton, So as not to affect the source program logic .
  7. String appClassName = null;
  8. try {
  9. ApplicationInfo ai = this.getPackageManager()
  10. .getApplicationInfo(this.getPackageName(),
  11. PackageManager.GET_META_DATA);
  12. Bundle bundle = ai.metaData;
  13. if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
  14. appClassName = bundle.getString("APPLICATION_CLASS_NAME");//className  Is configured in xml In the document .
  15. } else {
  16. Log.i("demo", "have no application class name");
  17. return;
  18. }
  19. } catch (NameNotFoundException e) {
  20. Log.i("demo", "error:"+Log.getStackTraceString(e));
  21. e.printStackTrace();
  22. }
  23. // If there is a value, call the Applicaiton
  24. Object currentActivityThread = RefInvoke.invokeStaticMethod(
  25. "android.app.ActivityThread", "currentActivityThread",
  26. new Class[] {}, new Object[] {});
  27. Object mBoundApplication = RefInvoke.getFieldOjbect(
  28. "android.app.ActivityThread", currentActivityThread,
  29. "mBoundApplication");
  30. Object loadedApkInfo = RefInvoke.getFieldOjbect(
  31. "android.app.ActivityThread$AppBindData",
  32. mBoundApplication, "info");
  33. // Put the current process of mApplication  Set up a null
  34. RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication",
  35. loadedApkInfo, null);
  36. Object oldApplication = RefInvoke.getFieldOjbect(
  37. "android.app.ActivityThread", currentActivityThread,
  38. "mInitialApplication");
  39. //http://www.codeceo.com/article/android-context.html
  40. ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke
  41. .getFieldOjbect("android.app.ActivityThread",
  42. currentActivityThread, "mAllApplications");
  43. mAllApplications.remove(oldApplication);// Delete oldApplication
  44. ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke
  45. .getFieldOjbect("android.app.LoadedApk", loadedApkInfo,
  46. "mApplicationInfo");
  47. ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
  48. .getFieldOjbect("android.app.ActivityThread$AppBindData",
  49. mBoundApplication, "appInfo");
  50. appinfo_In_LoadedApk.className = appClassName;
  51. appinfo_In_AppBindData.className = appClassName;
  52. Application app = (Application) RefInvoke.invokeMethod(
  53. "android.app.LoadedApk", "makeApplication", loadedApkInfo,
  54. new Class[] { boolean.class, Instrumentation.class },
  55. new Object[] { false, null });// perform  makeApplication(false,null)
  56. RefInvoke.setFieldOjbect("android.app.ActivityThread",
  57. "mInitialApplication", currentActivityThread, app);
  58. ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(
  59. "android.app.ActivityThread", currentActivityThread,
  60. "mProviderMap");
  61. Iterator it = mProviderMap.values().iterator();
  62. while (it.hasNext()) {
  63. Object providerClientRecord = it.next();
  64. Object localProvider = RefInvoke.getFieldOjbect(
  65. "android.app.ActivityThread$ProviderClientRecord",
  66. providerClientRecord, "mLocalProvider");
  67. RefInvoke.setFieldOjbect("android.content.ContentProvider",
  68. "mContext", localProvider, app);
  69. }
  70. Log.i("demo", "app:"+app);
  71. app.onCreate();
  72. }
  73. }

Directly in the shelling process Application Medium onCreate Just do it in the method . Here we can also see that it is through AndroidManifest.xml Medium meta Tag access source Apk Medium Application Object's .

So let's see AndoridManifest.xml Contents of the file :

Here we define the source program Apk Of Application Class name .

Project Download :http://download.csdn.net/detail/jiangwei0910410003/9102741

Four 、 Run the program

So here we are , The content of these three projects , Let's see how it works :

Running steps :

First step : Get the source program Apk File and shelling program Dex file

Run source and sheller projects , And then I got these two files ( Remember to classes.dex Change the name of the document ForceApkObj.dex), Then use the shell program to shell :

there ForceApkObj.apk Document and ForceApkObj.dex A file is an input file , The output is classes.dex file .

The second step : Replace... In shelling program classes.dex file

In the first step, we get the shelled classes.dex After the document , And we got one in the first step of running the shelling project ReforceApk.apk file , At this time, we use decompression software to replace :