Share the knowledge of Bat interview, we will continue to update, if you like, please click a follow
More advanced Android Interviews are available on Github (more interview documents, project downloads, source code) github.com/xiangjiana/… At the end of this article, we define the Application class name of the source program APK. If you need more friends, you can click about me to contact me
The interviewer asked how to prevent decompilation completely. So let’s break it down
Interviewer: How to thoroughly prevent decompilation, how to do dex encryption (interviewer wants to know whether you have experience in DEX reinforcement, this question is intended to test the dex reinforcement process, do not know dex coding) Candidate: should start from dex reinforcement process, from the project, DEX reinforcement — packaging — verification. Next, I will explain dex principle analysis
I. Principle analysis
Here’s how Android shells work:
Three objects are required during hardening:
- APK that requires encryption (source APK)
- Shell program APK (responsible for decrypting APK work)
- Encryption tool (encrypt source APK and merge DEX of shell program)
The main step is to encrypt the source program APK with encryption algorithm, and then merge it with the DEX file of the shell program APK to generate a new DEX file, and finally replace the original DEX file in the shell program. Get the new APK also called shell program APK, it is not a complete sense of the APK program, its main job is: responsible for decrypting the source program APK, and then load APK, let it run normally. In this process, the knowledge needed to understand is: how to merge the source program APK and shell program APK, which needs to understand the format of DEX file, the following is a brief introduction:
- Checksum (file checksum) The alder32 algorithm is used to check files for errors, except magic and checksum.
- Signature Uses the SHA-1 algorithm to hash out all remaining file areas except magic, checksum, and signature to uniquely identify the file.
- File_size DEX File size.
We need to write the encrypted source APK file to the DEX, so we need to change the checksum because its value depends on the contents of the file. The same goes for Signature, which is the algorithm that uniquely identifies files, and the size of DEX files. Another operation is to mark the size of the source APK file after encryption, because when running decryption, you need to know the size of the APK, in order to get the source APK correctly. This value can be placed directly at the end of the file. The modified DEX file format is as follows:
Know the principle, the following is the code implementation. There are three projects:
- Source program project (APK requiring encryption)
- Shell project (decrypt source APK and load APK)
- Encrypt the source APK and merge the DEX of the shell project
Ii. Project cases
Let’s look at the source program first
1. Source program project that needs encryption: SourceApk
An Application class is required. Why myApplication.java
package com.example.sourceapk;
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Log.i("demo"."source apk onCreate:"+ this); }}Copy the code
Just print the onCreate method. MainActivity.java
package com.example.sourceapk;
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
TextView content = new TextView(this);
content.setText("I am Source Apk");
content.setOnClickListener(new OnClickListener(){
@Override
public void onClick(View arg0) {
Intent intent = new Intent(MainActivity.this, SubActivity.class);
startActivity(intent);
}});
setContentView(content);
Log.i("demo"."app:"+getApplicationContext()); }}Copy the code
2. Shell program project: DexPackTool
The shell program is actually a Java project, its job is to encrypt the source program APK, and then write it to the shell program DEX file, modify the file header, get a new DEX file. Take a look at the code:
package com.example.packdex;
public class mymain {
public static void main(String[] args) {
try {
File payloadSrcFile = new File("files/SourceApk.apk"); System.out.println(system.out.println ("apk size:"+payloadSrcFile.length());
File packDexFile = new File("files/SourceApk.dex"); Dex byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile)); Byte [] packDexArray = byte[] packDexArray =readFileBytes(packDexFile); // Read dex in binary form /* Merge files */ int payloadLen = payloadarray. length; int packDexLen = packDexArray.length; int totalLen = payloadLen + packDexLen + 4; Byte [] newdex = new byte[totalLen]; System. arrayCopy (packDexArray, 0, newdex, 0, packDexLen); Arraycopy (payloadArray, 0, newdex, packDexLen, payloadLen); // Copy the dex content first. // Add the encrypted unshell data. System. arrayCopy (intToByte(payloadLen), 0, newdex, Totallen-4, 4); // The last 4 bytes are the length. // Change the DEX file size. SHA1 fixSHA1Header(newdex); // Change the DEX CheckSum header fixCheckSumHeader(newdex); String str ="files/classes.dex"; // create a new File File File = new File(STR);if(! file.exists()) { file.createNewFile(); } FileOutputStreamlocalFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex); // Write the newly calculated binary dex data to a filelocalFileOutputStream.flush();
localFileOutputStream.close(); } catch (Exception e) { e.printStackTrace(); Private static byte[] encrpt(byte[] srcdata){for (int i = 0; i < srcdata.length; i++) {
srcdata[i] = (byte)(0xFF ^ srcdata[i]);
}
returnsrcdata; }... }Copy the code
The encryption algorithm is simple, just xor for each byte
For the sake of simplicity, a very simple encryption algorithm is used here. In fact, in order to increase the difficulty of cracking, we should use a more efficient encryption algorithm, and it is better to put the encryption operation into the native layer.
There are two input files required:
- Source program APK file: sourceapk.apk
- DEX file of shell program: sourceapk. DEX
The first file is the compiled APK file of the source project, and the second file is the third project I’ll cover below: the classes.dex file in the shell project, with the name changed.
3. Shell program project: PackApk
Let’s take a look at how shell projects work:
-
Through reflection replacement android. App. ActivityThread mClassLoader for loading in the decryption of the APK DexClassLoader, the DexClassLoader loaded the source program on the one hand, on the other hand with the original mClassLoader as the parent node, This ensures that the source program is loaded without abandoning previously loaded resources and system code. For those who are not familiar with this section, check out this article about dynamic loading of Android without installation.
-
Find the source Application, build and run it through reflection. Note here that we are now loading a full APK and getting it up and running. When an APK is running, there is an Application object, which is also a global class after the program is running, so we must find the decrypted source APK Application class, run its onCreate method, so that the source APK can start its running life cycle. We’ll explain how to get the Application class of the source APK: Set it using the meta tag.
Take a look at the overall flow:
Let’s look at the code: proxyApplication.java
- Get the DEX file in the shell program APK, and then get the source program APK from this file for decryption and loading
@override protected void attachBaseContext(context base) {super.attachBaseContext(base); Try {// Create two folders payload_odex, payload_lib, private, writable File directory File odex = this.getdir ("payload_odex", MODE_PRIVATE);
File libs = this.getDir("payload_lib", MODE_PRIVATE);
odexPath = odex.getAbsolutePath();
libPath = libs.getAbsolutePath();
apkFileName = odex.getAbsolutePath() + "/payload.apk";
File dexFile = new File(apkFileName);
Log.i("demo"."apk size:"+dexFile.length());
if(! dexFile.exists()) { dexFile.createNewFile(); Byte [] dexdata = this.readdexfilefromapk (); // This.splitPayloadFromdex (dexdata) has been dynamically loaded; } / / configure dynamic loading environment Object currentActivityThread = RefInvoke. InvokeStaticMethod ("android.app.ActivityThread"."currentActivityThread", new Class[] {}, new Object[] {}); String packageName = this.getPackagename (); String packageName = this.getPackagename (); // ArrayMap mPackages = (ArrayMap) refinvoke.getFieldojbect ("android.app.ActivityThread", currentActivityThread,
"mPackages"); WeakReference wr = (WeakReference) mPackages.get(packageName); DLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader")); // Set the mClassLoader of the current process to DexClassLoader refinvoke.setFieldojbect ("android.app.LoadedApk"."mClassLoader",
wr.get(), dLoader);
Log.i("demo"."classloader:"+dLoader);
try{
Object actObj = dLoader.loadClass("com.example.sourceapk.MainActivity");
Log.i("demo"."actObj:"+actObj);
}catch(Exception e){
Log.i("demo"."activity:"+Log.getStackTraceString(e));
}
} catch (Exception e) {
Log.i("demo"."error:"+Log.getStackTraceString(e)); e.printStackTrace(); }}Copy the code
One thing to note here is that we need to find a time to load the APK of the source program and execute its onCreate method before the shell program is running, so it’s not too late to run the shell program, not the source program. Look at the source code we know. The Application has a method, attachBaseContext, which is executed before the onCreate method of the Application, so this is where we need to do our work. A) Obtain the DEX file from APK
/** * Get dex from apK package (byte) * @return
* @throws IOException
*/
private byte[] readDexFileFromApk() throws IOException {
ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
ZipInputStream localZipInputStream = new ZipInputStream(
new BufferedInputStream(new FileInputStream(
this.getApplicationInfo().sourceDir)));
while (true) {
ZipEntry localZipEntry = localZipInputStream.getNextEntry();
if (localZipEntry == null) {
localZipInputStream.close();
break;
}
if (localZipEntry.getName().equals("classes.dex")) {
byte[] arrayOfByte = new byte[1024];
while (true) {
int i = localZipInputStream.read(arrayOfByte);
if (i == -1)
break; dexByteArrayOutputStream.write(arrayOfByte, 0, i); }}localZipInputStream.closeEntry();
}
localZipInputStream.close();
return dexByteArrayOutputStream.toByteArray();
}
Copy the code
B) Get the APK file of the source program from the shell program DEX
/** * Release the loaded APK file, So file * @param data * @throws IOException */ private void splitPayLoadFromDex(byte[] apkData) throws IOException {int ablen = apkdata.length; Byte [] dexlen = new byte[4]; System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); DataInputStreamin = new DataInputStream(bais);
int readInt = in.readInt();
System.out.println(Integer.toHexString(readInt));
byte[] newdex = new byte[readInt]; Arraycopy (apkData, ablen-4 -); // Copy the contents of the source program apk to newdexreadInt, newdex, 0, readInt); Newdex = decrypt(newdex); newdex = decrypt(newdex); File File = new File(apkFileName); try { FileOutputStreamlocalFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(newdex);
localFileOutputStream.close();
} catch (IOException localIOException) {
throw new RuntimeException(localIOException); } // Parse the encapsulated APK file ZipInputStreamlocalZipInputStream = new ZipInputStream(
new BufferedInputStream(new FileInputStream(file)));
while (true) {
ZipEntry localZipEntry = localZipInputStream.getNextEntry(); // This also iterates through subdirectoriesif (localZipEntry == null) {
localZipInputStream.close();
break; } // fetch the so file used by the shell apk and place it in libPath (data/data/ package name /payload_lib) String name =localZipEntry.getName();
if (name.startsWith("lib/") && name.endsWith(".so")) {
File storeFile = new File(libPath + "/"
+ name.substring(name.lastIndexOf('/')));
storeFile.createNewFile();
FileOutputStream fos = new FileOutputStream(storeFile);
byte[] arrayOfByte = new byte[1024];
while (true) {
int i = localZipInputStream.read(arrayOfByte);
if (i == -1)
break;
fos.write(arrayOfByte, 0, i);
}
fos.flush();
fos.close();
}
localZipInputStream.closeEntry();
}
localZipInputStream.close();
}
Copy the code
C) Decrypt the source program APK
Private byte[] decrypt(byte[] srcdata) {for(int i=0; i<srcdata.length; i++){ srcdata[i] = (byte)(0xFF ^ srcdata[i]); }return srcdata;
}
Copy the code
- Find the source Application and let it run
@Override
public void onCreate() {
{
//loadResources(apkFileName);
Log.i("demo"."onCreate"); // If the source application has an Appliction object, replace it with the source application Appliciton so that the source program logic is not affected. String appClassName = null; try { ApplicationInfo ai = this.getPackageManager() .getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData;if(bundle ! = null && bundle.containsKey("APPLICATION_CLASS_NAME")) {
appClassName = bundle.getString("APPLICATION_CLASS_NAME"); //className is configured in the XML file. }else {
Log.i("demo"."have no application class name");
return;
}
} catch (NameNotFoundException e) {
Log.i("demo"."error:"+Log.getStackTraceString(e)); e.printStackTrace(); } / / a value called the Applicaiton Object currentActivityThread = RefInvoke. InvokeStaticMethod ("android.app.ActivityThread"."currentActivityThread",
new Class[] {}, new Object[] {});
Object mBoundApplication = RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mBoundApplication");
Object loadedApkInfo = RefInvoke.getFieldOjbect(
"android.app.ActivityThread$AppBindData",
mBoundApplication, "info"); // Set the mApplication of the current process to null refinvoke.setFieldojbect ("android.app.LoadedApk"."mApplication",
loadedApkInfo, null);
Object oldApplication = RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mInitialApplication"); //http://www.codeceo.com/article/android-context.html ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke .getFieldOjbect("android.app.ActivityThread",
currentActivityThread, "mAllApplications"); mAllApplications.remove(oldApplication); // oldApplication ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) Refinvoke.getFieldojbect ("android.app.LoadedApk", loadedApkInfo,
"mApplicationInfo");
ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke
.getFieldOjbect("android.app.ActivityThread$AppBindData",
mBoundApplication, "appInfo");
appinfo_In_LoadedApk.className = appClassName;
appinfo_In_AppBindData.className = appClassName;
Application app = (Application) RefInvoke.invokeMethod(
"android.app.LoadedApk"."makeApplication", loadedApkInfo,
new Class[] { boolean.class, Instrumentation.class },
new Object[] { false, null }); // Execute makeApplication (false, null) RefInvoke. SetFieldOjbect ("android.app.ActivityThread"."mInitialApplication", currentActivityThread, app);
ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mProviderMap");
Iterator it = mProviderMap.values().iterator();
while (it.hasNext()) {
Object providerClientRecord = it.next();
Object localProvider = RefInvoke.getFieldOjbect(
"android.app.ActivityThread$ProviderClientRecord",
providerClientRecord, "mLocalProvider");
RefInvoke.setFieldOjbect("android.content.ContentProvider"."mContext".localProvider, app);
}
Log.i("demo"."app:"+app); app.onCreate(); }}Copy the code
This is done directly in the onCreate method of the shell Application. You can also see that the Application object in the source APK is retrieved using the meta tag in androidmanifest.xml. Let’s take a look at the contents of the Androidmanifest.xml file:
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:name="com.example.packapk.ProxyApplication">
<meta-data android:name="APPLICATION_CLASS_NAME" android:value="com.example.sourceapk.MyApplication"/>
Copy the code
Here we define the Application class name of the source program APK. The project download
Three. Run the program
Here’s how to run the program:
- Step 1: Get the APK file and the DEX file of the shell and run the source and shell projects, then get the two files (rename the classes. DEX file of the shell to sourceapk. DEX), and use the encryption tool to add the shell.
- Step 2: Replace the classes.dex file in the shell. After we get the classes.dex file in step 1, replace it with the original classes.dex file in packapk. apk.
-
Step 3: In step 2, we get the replaced packapk. apk file. This file has been modified, so we need to re-sign it, otherwise we will also run an error. The signed file is ready to run with the following effect:
About me
More advanced Android interviews are available on Github github.com/xiangjiana/…
Need more friends can clickAbout meContact me to obtain
I hope to communicate with you and make progress together
At present, I am a programmer. I not only share the knowledge related to Android development, but also share the growth history of technical people, including personal summary, workplace experience, interview experience, etc., hoping to make you take a little detours.