一、前 言

Android Apk加固的发展已经有一段时间了,相对来说本篇博客要记录的Android加壳的实现思路是4年的东西了,已经被老鸟玩烂了,Android加固的安全厂商也不会采用这么粗犷的方式来进行Android Apk的加固处理。早期Android加固聚焦的两个点主要是在DexClassLoader和Android类加载这两条代码执行流程上去下功夫,后期Android加固会在Android动态库so的加固处理和Android应用程序的自定义smali字节码虚拟机上下功夫来实现与逆向分析的对抗。本篇博客记录主要学习参考的博文是Android APK加壳技术方案【1】Android APK加壳技术方案【2】APK加壳【1】初步方案实现详解 这3篇。Android加固的关注点主要集中在两个方面:dex文件的隐藏和dex文件的加载运行,针对Android应用dex文件的加固也有两种方案即,dex文件的整体加固方案、dex文件的部分加固方案。本篇博客提到的Android Apk的加固属于dex整理加固的方案。

源程序:需要被加壳保护的Android应用程序的代码,可以是Android应用的apk也可以是Android应用的dex文件和so库文件。


加密工具:对源程序进行加密的工具即对Android应用的apk或者Android应用的dex文件和so库文件进行加密的工具,为解壳程序提供源程序的解壳数据,语言和平台不限,加密处理方式根据加壳的需求来处理。


解壳程序:解密解壳数据并通过DexClassLoader动态加载和运行源程序的代码,实现Android解壳程序与Android源程序的无缝替换,Android源程序apk得到执行,Android解壳程序又被称作外壳程序。

二、Android 源程序的加壳思路

1.解壳数据位于解壳程序文件尾部

先将Android源程序的Apk使用加密工具进行加密处理得到需要解密的解壳数据,然后将该解壳数据放到Android解壳程序的dex文件的后面进行隐藏。

加壳源程序工作流程:

1. 加密源程序apk文件为解壳数据。

2. 把解壳数据写入解壳程序dex文件末尾,并在文件尾部添加解壳数据的长度大小。

3. 修改解壳程序dex头中checksum、signature 和file_size头信息。

4. 修改源程序AndroidMainfest.xml文件并覆盖解壳程序AndroidMainfest.xml文件。

源程序Apk加壳处理后,解壳程序dex文件的示意图如下:

解壳程序工作流程:

1.读取解壳程序dex文件末尾的数据,获取解壳数据长度。

2.从解壳程序dex文件中读取解壳数据,解密解壳数据,以文件形式保存解密数据到xx.apk文件。

3.获取到释放出的源程序xx.apk,解壳程序apk应用通过DexClassLoader动态加载xx.apk进行运行。

2.解壳数据位于解壳程序文件头后面

先将Android源程序的Apk使用加密工具进行加密处理得到需要解密的解壳数据,然后将该解壳数据放到Android解壳程序的dex文件的文件头的后面。

源程序Apk加壳处理后,解壳程序dex文件的示意图如下:

加壳源程序工作流程:

1. 加密源程序apk文件为解壳数据。

2. 计算解壳数据长度,添加该数据长度到解壳dex文件头的末尾(插入数据的位置为0x70处),并继续添加解壳数据到文件头末尾。

3. 修改解壳程序dex文件头中checksum、signature、file_size、header_size、string_ids_off、type_ids_off、proto_ids_off、field_ids_off、method_ids_off、class_defs_off和data_off相关项,分析map_off 数据修改相关的数据偏移量。

4. 修改源程序AndroidMainfest.xml文件并覆盖解壳程序AndroidMainfest.xml文件。

解壳程序工作流程:

1.从解壳程序的dex文件位置偏移0x70处读取解壳数据的长度。

2.从解壳程序的dex文件中读取解壳数据并解密解壳数据,以文件形式保存解密数据到xx.apk。

3.获取到释放出的源程序xx.apk,解壳程序apk应用通过DexClassLoader动态加载xx.apk进行运行。

提示:
上面的示意图和加壳步骤是搬运自Jack_Jia大牛的博客修改而来,知道自己语言组织能力不行也不献丑了。尽管现在很多Android加固厂商加密和隐藏源程序apk都不会采取上面的方法,但是对于学习Android的加固拓展思路还是有帮助的。当然了Android加固厂商也不会简单粗暴的直接加密整个源程序的apk,这样做效率不高也没有必要,将源程序的apk进行解包拆分为dex文件、so库文件和资源,然后分别对dex文件、so库文件和资源进行有针对性的加固处理,例如:dex文件的内存加载、so库文件的自定义加载、资源的弱加固处理等等,上面提到的Android加壳方法仅仅是一个大致的实现思路,离商业应用还有一段不小的距离。

三、Android 源程序加壳的代码实现

Jack_Jia提供了两种加密隐藏Android源程序的方法,第1种相对来说比较简单,第2种比较复杂,涉及到Android解壳程序dex文件的修改地方比较多,因此以第1种对整个Android源程序APK包进行加密隐藏处理的方案来实现。Jack_Jia提供的加壳代码是基于Android 2.3的系统代码来实现的,如果要该加壳代码在Android系统以后版本上运行成功,还需参考相关Android系统源码APK运行的流程来进行对应的反射修改。

(1).解壳数据位于解壳程序dex文件尾部的加壳流程:

1.加密源程序apk为解壳数据,加密源程序apk的算法需要自己自定义。

2.把解壳数据写入到解壳程序dex文件末尾,并在文件尾部添加解壳数据的长度大小。

3.修正解壳程序dex文件头中checksum、signature 和file_size字段。

对Android源程序apk进行加密隐藏的功能由java代码编写的加密工具DexShellTool来实现,其中[ g:/payload.apk ]为被加密隐藏保护的源程序apk,[ g:/unshell.dex ]为Android解壳程序apk的原dex文件,[ g:/classes.dex ]为Android解壳程序apk被修改后带有解壳数据的新dex文件。

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.zip.Adler32; // 对加壳的Apk进行加密隐藏处理的工具代码
public class DexShellTool { public static void main(String[] args) { try { // 打开被加壳的apk文件"g:/payload.apk"
File payloadSrcFile = new File("g:/payload.apk");
// 打开加壳apk程序的外壳apk的dex文件"g:/unshell.dex"
File unShellDexFile = new File("g:/unshell.dex"); // 读取payload.apk文件的数据内容进行加密处理
byte[] payloadArray = encrpt(readFileBytes(payloadSrcFile));
// 读取外壳apk的dex文件"g:/unshell.dex"的数据到字节数组中
byte[] unShellDexArray = readFileBytes(unShellDexFile); // 加密后payload.apk文件的数据长度
int payloadLen = payloadArray.length;
// 外壳程序apk的dex文件"g:/unshell.dex"的数据长度
int unShellDexLen = unShellDexArray.length; // 获取加密后"g:/payload.apk"文件隐藏到"g:/unshell.dex"中所需的字节长度
int totalLen = payloadLen + unShellDexLen +4;
// 申请内存空间
byte[] newdex = new byte[totalLen]; // 添加解壳代码"g:/unshell.dex"到申请内存缓冲区中
System.arraycopy(unShellDexArray, 0, newdex, 0, unShellDexLen);
// 添加加密后"g:/payload.apk"文件的解壳数据到申请内存缓冲区中
System.arraycopy(payloadArray, 0, newdex, unShellDexLen, payloadLen);
// 添加加密后"g:/payload.apk"文件的解壳数据长度到末尾的4字节
System.arraycopy(intToByte(payloadLen), 0, newdex, totalLen-4, 4); // 修正DEX file size文件头
fixFileSizeHeader(newdex);
// 修正DEX SHA1签名 文件头
fixSHA1Header(newdex);
//修改DEX CheckSum文件头
fixCheckSumHeader(newdex); String str = "g:/classes.dex";
// 创建或者打开文件"g:/classes.dex"
File file = new File(str);
// 删除已存在旧的文件
if (!file.exists()) { file.createNewFile();
} // 构建新的加壳的外壳dex文件"g:/classes.dex"
FileOutputStream localFileOutputStream = new FileOutputStream(str);
localFileOutputStream.write(newdex);
// 刷新文件流
localFileOutputStream.flush();
// 关闭文件即构建文件完成
localFileOutputStream.close(); } catch (Exception e) { e.printStackTrace();
}
} // 直接返回数据,读者可以添加自己加密方法
private static byte[] encrpt(byte[] srcdata){ // 自定义的加密算法,对原apk文件的数据和apk文件的长度进行加密处理
return srcdata;
} // 修正dex文件头的校验码checksum字段
private static void fixCheckSumHeader(byte[] dexBytes) { Adler32 adler = new Adler32();
adler.update(dexBytes, 12, dexBytes.length - 12);
long value = adler.getValue();
int va = (int) value;
byte[] newcs = intToByte(va);
byte[] recs = new byte[4]; for (int i = 0; i < 4; i++) { recs[i] = newcs[newcs.length - 1 - i];
System.out.println(Integer.toHexString(newcs[i]));
}
// 修正dex文件的校验码checksum
System.arraycopy(recs, 0, dexBytes, 8, 4); System.out.println(Long.toHexString(value));
System.out.println();
} // 将int型数据转换成字节数组的形式存放
public static byte[] intToByte(int number) { byte[] b = new byte[4];
for (int i = 3; i >= 0; i--) { b[i] = (byte) (number % 256);
number >>= 8;
} return b;
} // 修正dex文件头存放dex文件signature(SHA-1签名)的字段
private static void fixSHA1Header(byte[] dexBytes) throws NoSuchAlgorithmException { MessageDigest md = MessageDigest.getInstance("SHA-1");
md.update(dexBytes, 32, dexBytes.length - 32);
byte[] newdt = md.digest();
// 修正dex文件的SHA-1签名
System.arraycopy(newdt, 0, dexBytes, 12, 20); // 打印SHA-1签名
String hexstr = "";
for (int i = 0; i < newdt.length; i++) { hexstr += Integer.toString((newdt[i] & 0xff) + 0x100, 16)
.substring(1);
}
System.out.println(hexstr);
} // 修正dex文件头存放dex文件大小的字段file_size
private static void fixFileSizeHeader(byte[] dexBytes) { byte[] newfs = intToByte(dexBytes.length);
System.out.println(Integer.toHexString(dexBytes.length)); byte[] refs = new byte[4];
for (int i = 0; i < 4; i++) { refs[i] = newfs[newfs.length - 1 - i];
System.out.println(Integer.toHexString(newfs[i]));
} // 修正dex文件的大小
System.arraycopy(refs, 0, dexBytes, 32, 4);
} // 读取文件的数据内容到字节数组中
private static byte[] readFileBytes(File file) throws IOException { byte[] arrayOfByte = new byte[1024];
ByteArrayOutputStream localByteArrayOutputStream = new ByteArrayOutputStream();
FileInputStream fis = new FileInputStream(file); while (true) { // 读取文件中的数据内容
int i = fis.read(arrayOfByte);
if (i != -1) { // 将读取的文件数据写入到内存缓冲区中
localByteArrayOutputStream.write(arrayOfByte, 0, i); } else { // 返回读取的文件的数据的字节流数组
return localByteArrayOutputStream.toByteArray();
}
}
}
}
(2).加壳的Android源程序被Android解壳程序解壳的流程和代码实现:
  1. Android应用程序由不同的组件构成,Android系统在有需要的时候才会启动Android程序的组件。因此,解壳程序必须在Android系统启动组件之前运行,完成对解壳数据的解壳以及解壳后对源程序apk文件的动态加载,否则会使程序出现加载类失败的异常。Android开发者都知道Applicaiton做为整个应用的上下文,会被Android系统第一时间调用,这也是Android应用开发者程序代码的第一执行点。因此,通过对解壳程序AndroidMainfest.xml中的 application 进行配置即继承实现Application类,就可以实现解壳代码第一时间运行,如下图所示:


  2. 如何替换回源程序Apk原有的Application? 当在解壳程序Apk的AndroidMainfest.xml文件配置为解壳代码的Application类时,源程序Apk原有的Applicaiton将被替换。为了不影响源程序Apk的代码逻辑,我们需要在解壳代码运行完成后,替换回源程序Apk原有的Application类对象,我们通过在解壳程序apk的AndroidMainfest.xml文件中配置源程序apk原有Applicaiton类的信息 来达到我们的目的,解壳程序Apk要在运行完毕后,通过创建meta-data配置的Application对象并通过反射修改回源程序Apk的原Application。在解壳程序apk的AndroidMainfest.xml文件中配置源程序apk原有Applicaiton类的信息如下图所示,通过反射的修改见后面的实现代码:


  3. 如何通过DexClassLoader实现对apk代码的动态加载? 我们知道DexClassLoader加载的类是没有组件生命周期的,也就是说即使DexClassLoader通过对APK的动态加载完成了对组件类的加载,当系统启动该组件时,还会出现加载类失败的异常。为什么Android组件类被动态加载入Android虚拟机,但Android系统却出现加载类失败呢? 通过查看Android源代码知道Android组件类的加载是由另一个ClassLoader来完成的,DexClassLoader和系统组件ClassLoader并不存在关系,系统组件ClassLoader当然找不到由DexClassLoader加载的类,如果把系统组件ClassLoader的parent修改成DexClassLoader,就可以实现对apk代码的动态加载。
  4. 如何使解壳后的源程序apk的资源文件被代码动态引用? 代码默认引用的资源文件在最外层的解壳程序中,因此,我们要增加系统的资源加载路径来实现对解壳后APK文件资源的加载,比较简单的做法是 Android解壳程序在代码上只有继承重写的Application类而没有其他的代码实现,图标什么的使用源程序Apk的图标;将源程序apk的全部资源拷贝到解壳程序apk中并将源程序apk的activity、service、content provider、broadcast receiver等的声明描述添加到解壳程序apk的AndroidMainfest.xml文件中。

源程序Apk的解壳代码在解壳程序Apk继承重写Application类的ProxyApplication中实现,在ProxyApplication类的attachBaseContext方法中实现对源程序apk(这里为 payload.apk)的解壳释放和源程序apk的DexClassLoader加载以及将源程序的DexClassLoader与解壳程序的ClassLoader引用关联起来,在ProxyApplication类的onCreate方法中通过反射修改替换掉解壳程序apk的Application为源程序apk的Application并调用执行。

解壳程序的解壳代码 ProxyApplication.java 的实现

package com.example.androidshell;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream; import dalvik.system.DexClassLoader;
import android.app.Application;
import android.app.Instrumentation;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle; public class ProxyApplication extends Application { private static final String appkey = "APPLICATION_CLASS_NAME";
private String apkFileName;
private String odexPath;
private String libPath; protected void attachBaseContext(Context base) { super.attachBaseContext(base);
try { // 在当前apk目录下创建文件夹"payload_odex"
File odex = this.getDir("payload_odex", MODE_PRIVATE);
// 在当前apk目录下创建文件夹"payload_lib"存放被加密隐藏的apk的so库文件
File libs = this.getDir("payload_lib", MODE_PRIVATE); // 获取文件的全路径
odexPath = odex.getAbsolutePath();
libPath = libs.getAbsolutePath(); // 拼接构建文件路径字符串
apkFileName = odex.getAbsolutePath() + "/payload.apk";
// 创建文件"payload.apk"
File dexFile = new File(apkFileName);
// 删除旧的文件
if (!dexFile.exists())
dexFile.createNewFile(); // 读取加壳外壳apk程序的classes.dex文件数据到字节数组中
byte[] dexdata = this.readDexFileFromApk(); // 解密释放被加密隐藏的apk文件并获取该apk的so库文件
this.splitPayLoadFromDex(dexdata); // 配置动态加载环境,用以后面获取当前apk的父ClassLoader
Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",
new Class[] {}, new Object[] {});
// 获取当前外壳apk的包名
String packageName = this.getPackageName();
HashMap mPackages = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mPackages"); // 获取当前外壳apk的引用
WeakReference wr = (WeakReference) mPackages.get(packageName);
// public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
// 动态加载被加隐藏释放的原apk程序
DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath,
(ClassLoader)RefInvoke.getFieldOjbect("android.app.LoadedApk", wr.get(), "mClassLoader")); // 将动态加载的加密隐藏释放的原apk的ClassLoader和当前外壳apk的引用关联起来
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader", wr.get(), dLoader); } catch (Exception e) { e.printStackTrace();
}
} // 先执行的attachBaseContext再执行的onCreate函数
public void onCreate() { // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。
String appClassName = null;
try { // 获取meta-data元数据即获取被加密隐藏的原apk的Application
// 其实APPLICATION_CLASS_NAME不一定要配置在AndroidMainfest.xml文件中,写在代码里也是可以的,只不过通用性不是那么好。
ApplicationInfo ai = this.getPackageManager().getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA);
Bundle bundle = ai.metaData;
if (bundle != null && bundle.containsKey("APPLICATION_CLASS_NAME")) { // 获取被加密隐藏的原apk的Application
appClassName = bundle.getString("APPLICATION_CLASS_NAME"); } else { return;
} } catch (NameNotFoundException e) { e.printStackTrace();
} // 调用静态方法android.app.ActivityThread.currentActivityThread 获取当前activity的线程对象
// public static ActivityThread currentActivityThread() {
// return sCurrentActivityThread;
// }
Object currentActivityThread = RefInvoke.invokeStaticMethod("android.app.ActivityThread", "currentActivityThread",
new Class[] {}, new Object[] {}); // 获取当前activity的线程对象sCurrentActivityThread中mBoundApplication成员变量,该对象是一个AppBindData类对象
Object mBoundApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mBoundApplication");
// 获取mBoundApplication中的info成员变量,其中info是LoadedApk类对象
Object loadedApkInfo = RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData", mBoundApplication, "info");
// 设置loadedApkInfo对象的mApplication成员变量为null(因为不是通过系统加载apk的方式加载的)
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mApplication", loadedApkInfo, null); // 获取currentActivityThread对象sCurrentActivityThread中的mInitialApplication成员变量(存放当前Application)
Object oldApplication = RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mInitialApplication");
// 获取currentActivityThread对象sCurrentActivityThread中的mAllApplications成员变量(存放Application的列表)
ArrayList<Application> mAllApplications = (ArrayList<Application>) RefInvoke.getFieldOjbect("android.app.ActivityThread",
currentActivityThread, "mAllApplications");
// 从当前mAllApplications中移除当前(旧的)Application
mAllApplications.remove(oldApplication); // 获取上面得到LoadedApk对象中的mApplicationInfo成员变量,是个ApplicationInfo对象
ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.LoadedApk",
loadedApkInfo, "mApplicationInfo");
// 获取上面得到AppBindData对象中的appInfo成员变量,也是个ApplicationInfo对象
ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke.getFieldOjbect("android.app.ActivityThread$AppBindData",
mBoundApplication, "appInfo");
// 把上面两个对象的classNam成员变量设置为从meta-data中获取的被隐藏加密的apk的Application路径
appinfo_In_LoadedApk.className = appClassName;
appinfo_In_AppBindData.className = appClassName; // 调用LoadedApk中的makeApplication 方法,构造一个新的application
Application app = (Application) RefInvoke.invokeMethod("android.app.LoadedApk", "makeApplication",
loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null });
// 设置currentActivityThread对象sCurrentActivityThread中的mInitialApplication成员变量的值为新构造的Application
RefInvoke.setFieldOjbect("android.app.ActivityThread", "mInitialApplication", currentActivityThread, app); // 获取当前activity的线程对象sCurrentActivityThread中的成员变量mProviderMap的值
HashMap mProviderMap = (HashMap) RefInvoke.getFieldOjbect("android.app.ActivityThread", currentActivityThread, "mProviderMap");
// 遍历该对象mProviderMap
Iterator it = mProviderMap.values().iterator();
while (it.hasNext()) { Object providerClientRecord = it.next();
// 获取ProviderClientRecord类对象的成员变量mLocalProvider
Object localProvider = RefInvoke.getFieldOjbect("android.app.ActivityThread$ProviderClientRecord",
providerClientRecord, "mLocalProvider"); // 设置mLocalProvider中的成员变量mContext的值为新构建的Application即app
RefInvoke.setFieldOjbect("android.content.ContentProvider", "mContext", localProvider, app);
} // 新构建Application调用onCreate()方法
app.onCreate();
} // 解密释放被加密隐藏的apk文件并获取该apk的so库文件
private void splitPayLoadFromDex(byte[] data) throws IOException { // 获取被加密apk解密后的apk文件相关的数据(包括长度和原apk文件的数据)
byte[] apkdata = decrypt(data);
// 获取解密后apk文件相关的数据长度(存放在最后4字节)
int ablen = apkdata.length; // 获取被加密原apk文件的数据长度的字节数组
byte[] dexlen = new byte[4];
System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); // 获取到被加密原apk文件的int型的数据长度
ByteArrayInputStream bais = new ByteArrayInputStream(dexlen);
DataInputStream in = new DataInputStream(bais);
int readInt = in.readInt();
// 打印原apk文件的数据长度
System.out.println(Integer.toHexString(readInt)); // 获取原apk文件的数据释放出被隐藏的apk文件
byte[] newdex = new byte[readInt];
System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); // 释放被隐藏的apk文件
File file = new File(apkFileName);
try { // 释放被加密隐藏的原apk文件
FileOutputStream localFileOutputStream = new FileOutputStream(file);
localFileOutputStream.write(newdex);
localFileOutputStream.close(); } catch (IOException localIOException) { throw new RuntimeException(localIOException);
} // 解析被加密的原apk文件获取so库文件
ZipInputStream localZipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(file)));
while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry();
if (localZipEntry == null) { localZipInputStream.close();
break;
} // 被加密apk的so库文件保存到指定的文件路径下
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();
} // 读取加壳外壳apk即当前apk的"classes.dex"文件到字节内存缓冲区中
private byte[] readDexFileFromApk() throws IOException { ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream();
// 读取当前外壳apk文件的数据
ZipInputStream localZipInputStream = new ZipInputStream(new BufferedInputStream(new FileInputStream(this.getApplicationInfo().sourceDir)));
while (true) { // 遍历外壳apk压缩文件
ZipEntry localZipEntry = localZipInputStream.getNextEntry();
if (localZipEntry == null) { localZipInputStream.close();
break;
} // 判断是否是加壳外壳apk的"classes.dex"即当前apk的"classes.dex"文件
if (localZipEntry.getName().equals("classes.dex")) { byte[] arrayOfByte = new byte[1024];
while (true) { int i = localZipInputStream.read(arrayOfByte);
if (i == -1)
break; // 把读取的dex文件的数据写到到字节内存缓冲区中
dexByteArrayOutputStream.write(arrayOfByte, 0, i);
}
} localZipInputStream.closeEntry();
}
localZipInputStream.close(); return dexByteArrayOutputStream.toByteArray();
} // 直接返回数据,读者可以添加自己解密方法
// 返回被加密apk解密后的apk文件的数据,例如:payload.apk
private byte[] decrypt(byte[] data) { // 对加密的apk程序的数据进行解密处理
return data;
} }

上面的代码中使用到的反射工具类 RefInvoke.java 的实现

package com.example.androidshell;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; // Android的类反射调用的工具
public class RefInvoke { // 通过类反射调用调用类的静态方法
public static Object invokeStaticMethod(String class_name, String method_name, Class[] pareTyple, Object[] pareVaules) { try { Class obj_class = Class.forName(class_name);
Method method = obj_class.getMethod(method_name, pareTyple); return method.invoke(null, pareVaules); } catch (SecurityException e) { e.printStackTrace();
} catch (IllegalArgumentException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (NoSuchMethodException e) { e.printStackTrace();
} catch (InvocationTargetException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
}
return null; } // 通过类反射调用调用类的实例方法
public static Object invokeMethod(String class_name, String method_name, Object obj ,Class[] pareTyple, Object[] pareVaules){ try { Class obj_class = Class.forName(class_name);
Method method = obj_class.getMethod(method_name, pareTyple); return method.invoke(obj, pareVaules); } catch (SecurityException e) { e.printStackTrace();
} catch (IllegalArgumentException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (NoSuchMethodException e) { e.printStackTrace();
} catch (InvocationTargetException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
}
return null; } // 通过类反射调用获取类的实例成员变量
public static Object getFieldOjbect(String class_name, Object obj, String filedName){ try { Class obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true); return field.get(obj); } catch (SecurityException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace();
} catch (IllegalArgumentException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
}
return null; } // 通过类反射调用获取类的静态成员变量
public static Object getStaticFieldOjbect(String class_name, String filedName){ try { Class<?> obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true); return field.get(null); } catch (SecurityException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace();
} catch (IllegalArgumentException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
}
return null; } // 通过类反射调用设置类的实例成员变量的值
public static void setFieldOjbect(String classname, String filedName, Object obj, Object filedVaule){ try { Class<?> obj_class = Class.forName(classname);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
field.set(obj, filedVaule); } catch (SecurityException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace();
} catch (IllegalArgumentException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
}
} // 通过类反射调用设置类的静态成员变量的值
public static void setStaticOjbect(String class_name, String filedName, Object filedVaule) { try { Class<?> obj_class = Class.forName(class_name);
Field field = obj_class.getDeclaredField(filedName);
field.setAccessible(true);
field.set(null, filedVaule); } catch (SecurityException e) { e.printStackTrace();
} catch (NoSuchFieldException e) { e.printStackTrace();
} catch (IllegalArgumentException e) { e.printStackTrace();
} catch (IllegalAccessException e) { e.printStackTrace();
} catch (ClassNotFoundException e) { e.printStackTrace();
}
}
}

四、后 记

本篇博文主要是对Jack_Jia大牛的加壳原理文章的整理和学习,一些关键地方描述直接照搬Jack_Jia大牛的描述和示意图,知道自己描述不清楚,同时也参考了 桃园小七 的博客,一并感谢。这里仅仅是对Android程序的整体dex加固原理的初步学习,如果要做出一款能用的Android加固壳还需要考虑很多因素,比如Android系统的兼容性、加壳后应用的启动速度等等。由于每个Android系统版本的源码会有所改变,为了兼容性的考虑,DexClassLoader的动态加载和源程序Application的替换需要有针对性的进行修改。

Android Apk加固的初步实现思路(dex整体加固)的更多相关文章

  1. android apk壳

    壳对于有过pc端加解密经验的同学来说并不陌生,android世界中的壳也是相同的存在.看下图(exe = dex):    概念清楚罗,我们就说下:壳最本质的功能就是实现加载器.你看加壳后,系统是先执 ...

  2. 反编译Android APK及防止APK程序被反编译

    怎么逆向工程对Android Apk 进行反编译 google Android开发是开源的,开发过程中有些时候会遇到一些功能,自己不知道该怎么做,然而别的软件里面已经有了,这个时候可以采用反编译的方式 ...

  3. 爱加密Android APk 原理解析

    转载请标明出处:http://blog.csdn.net/u011546655/article/details/45921025 爱加密Android APK加壳原理解析 一.什么是加壳? 加壳是在二 ...

  4. 实例具体解释:反编译Android APK,改动字节码后再回编译成APK

    本文具体介绍了怎样反编译一个未被混淆过的Android APK,改动smali字节码后,再回编译成APK并更新签名,使之可正常安装.破译后的apk不管输入什么样的username和password都能 ...

  5. android apk 防止反编译技术第四篇-对抗JD-GUI

    又到周末一个人侘在家里无事可干,这就是程序员的悲哀啊.好了我们利用周末的时间继续介绍android apk防止反编译技术的另一种方法.前三篇我们讲了加壳技术(http://my.oschina.net ...

  6. Android:apk文件结构

    Android apk文件,即Android application package文件. 每个要安装到Android平台的应用都要被编译打包为一个单独的文件,后缀名为.apk,其中包含了应用的二进制 ...

  7. 反编译android APK

    我们经常会在如下的情况使用反编译 1.看到别人应用中的酷炫功能,想知道是如何实现的 2.别人应用的素材排版好漂亮,想套用模仿   百度一下就已经有一大堆反编译的教程了,我还是坚持学习记录一下.   A ...

  8. Android apk 的安装过程

    Android应用安装有如下四种方式 1.系统应用安装――开机时完成,没有安装界面 2.网络下载应用安装――通过market应用完成,没有安装界面 3.ADB工具安装――没有安装界面. 4.第三方应用 ...

  9. 转: android apk 防止反编译技术(1~5连载)

    转: android apk 防止反编译技术 做android framework方面的工作将近三年的时间了,现在公司让做一下android apk安全方面的研究,于是最近就在网上找大量的资料来学习. ...

随机推荐

  1. CVE-2017-12615 -Tomcat-任意写入文件

    漏洞分析参考 https://www.freebuf.com/vuls/148283.html 漏洞描述: 当 Tomcat运行在Windows操作系统时,且启用了HTTP PUT请求方法(例如,将 ...

  2. Apache配置 10. 访问控制-禁止解析PHP

    (1)简述 对于使用PHP语言编写的网站,有一些目录是有需求上传文件的.如果网站代码有漏洞,让黑客上传了一个用PHP写的木马,由于网站可以执行PHP程序,最终会让黑客拿到服务器权限. 为了避免这种情况 ...

  3. 基础篇:java.security框架之签名、加密、摘要及证书

    前言 和前端进行数据交互时或者和第三方商家对接时,需要对隐私数据进行加密.单向加密,对称加密,非对称加密,其对应的算法也各式各样.java提供了统一的框架来规范(java.security)安全加密这 ...

  4. polay计数原理

    公式: Burnside引理: 1/|G|*(C(π1)+C(π2)+C(π3)+.....+C(πn)): C(π):指不同置换下的等价类数.例如π=(123)(3)(45)(6)(7),X={1, ...

  5. [源码解析] 并行分布式任务队列 Celery 之 Task是什么

    [源码解析] 并行分布式任务队列 Celery 之 Task是什么 目录 [源码解析] 并行分布式任务队列 Celery 之 Task是什么 0x00 摘要 0x01 思考出发点 0x02 示例代码 ...

  6. 201871030127-王明强 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告

    项目 内容 课程班级博客链接 18级卓越班 这个作业要求链接 实验三 软件工程结对项目 我的课程学习目标 1.熟悉PSP流程2. 熟悉github操作3.加深对D{0-1}问题的解法的理解4.熟悉ja ...

  7. OO电梯系列总结与反思

    目录 前言 HW5 度量分析 UML类图与协作图 bug分析 HW6 度量分析 UML类图与协作图 bug分析 HW7 度量分析 UML类图与协作图 bug分析 SOLID原则 感想 前言 紧张刺激的 ...

  8. 记一次metasploitable2内网渗透之512,513,514端口攻击

    512,513,514端口都是R服务: TCP端口512,513和514为著名的rlogin提供服务.在系统中被错误配置从而允许远程访问者从任何地方访问(标准的,rhosts + +). 默认端口:5 ...

  9. 关于js中this的指向详细总结、分析

    目录 this的指向详细剖析 当作为函数直接调用时, this => window 当作为构造函数时,this => 构造出的实例对象 当作为对象的方法调用时,this => 调用方 ...

  10. docker命令快速入门

    docker快速入门系列 Docker hello world hello world $ docker run ubuntu:15.10 /bin/echo "Hello world&qu ...