首先声明,只针对 Android 8.1 后版本;
https://blog.csdn.net/q610098308/article/details/105199419
这个存在的问题主要有:
1、解密之后的apk源程序放在指定目录的话,还是存在被破解的风险,因为这种落地方式解密,是很容易获取解密之后的apk的
2、在解密得到源程序apk,然后再用DexClassLoader进行加载,这里相当于两次把apk加载到内存中,第一次是解密的时候,第二次是加载apk的时候,那么这效率就会大大降低了
参考了 百度 apk 和 360 apk 加固;
Android 8.0 后有了 api 支持一个是 26,一个是 27;使用时注意
InMemoryDexClassLoader
// sdk 27
public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) {
super(dexBuffers, parent);
}
// sdk 26
public InMemoryDexClassLoader(ByteBuffer dexBuffer, ClassLoader parent) {
this(new ByteBuffer[] { dexBuffer }, parent);
}
InMemoryDexClassLoader继承于BaseDexClassLoader,是API26新增的类加载器。dexBuffers数组构造了一个DexPathList,可用于加载内存中的dex。
很显然,我们可以在内存中加载 dex,这样,安全和性能方面大大提高了;
关于这个怎么用,网上也有说明,不过例子很少,用法和DexClassLoader 类似,功能也类似,可能是比较新,说明网上基本没找到使用的例子;
下面说一下流程,这里是参照上版本说的:
1.attachBaseContext 方法用,NDK 解密后,直接返回 ByteBuffer,存在一个数组中;
/**
* ActivityThread创建Application之后调用的第一个方法
* 可以在这个方法中进行解密,同时把dex交给Android去加载
* @param base
*/
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
//获取用户填入的metaData
getMetaData();
//得到当前apk文件
File apkFile = new File(getApplicationInfo().sourceDir);
//把apk解压 这个目录中的内容需要root权限才能使用
File versionDir = getDir(app_name+"_" + app_version,MODE_PRIVATE);
File appDir = new File(versionDir,"app");
File dexDir = new File(appDir,"dexDir");
//得到我们需要加载的dex文件
List<File> dexFiles = new ArrayList<>();
List<ByteBuffer> ByteBufferList = new ArrayList<>();
//进行解密 (最好做md5文件校验)
if (!dexDir.exists() || dexDir.list().length == 0){
//把apk解压到appDir
Zip.unZip(apkFile,appDir);
//获取目录下所有的文件
File[] files = appDir.listFiles();
for (File file:files){
String name = file.getName();
if (name.endsWith(".dex") && !TextUtils.equals(name,"classes.dex")){
try{
Log.e("filename","---"+file.getAbsolutePath());
byte[] bytes = Utils.native_rc4_de(file.getAbsolutePath(),file.getAbsolutePath());
dexFiles.add(file);
if(null != bytes)
{
ByteBuffer buffer = ByteBuffer.wrap(bytes);
ByteBufferList.add(buffer);
}
Log.e("filename","---"+file.getAbsolutePath());
}catch (Exception e){
e.printStackTrace();
}
}
}
}else {
for (File file:dexDir.listFiles()){
dexFiles.add(file);
}
}
try {
loadDexByMem(base,ByteBufferList);
} catch (Exception e) {
e.printStackTrace();
}
}
2.使用 InMemoryDexClassLoader 加载到内存,然后替换 父节点的 ClassLoader
private void loadDexByMem(Context base, List<ByteBuffer> ByteBufferList) throws Exception{
try {
ByteBuffer[] byteBuffers = new ByteBuffer[ByteBufferList.size()];
int i =0;
for(ByteBuffer buffer:ByteBufferList)
{
byteBuffers[i]=buffer;
i++;
}
// 配置动态加载环境
//反射获取主线程对象,并从中获取所有已加载的package信息,并中找到当前的LoadApk对象的弱引用
//创建一个新的inMemoryDexClassLoader用于加载源Apk,
// 父节点的inMemoryDexClassLoader使其遵循双亲委托模型
//getClassLoader()等同于 (ClassLoader) RefInvoke.getFieldOjbect()
//但是为了替换掉父节点我们需要通过反射来获取并修改其值
Object currentActivityThread = RefInvoke.invokeStaticMethod(
"android.app.ActivityThread", "currentActivityThread",
new Class[] {}, new Object[] {});
String packageName = base.getPackageName();
Log.e("filename","packageName:"+packageName);
ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect(
"android.app.ActivityThread", currentActivityThread,
"mPackages");
WeakReference wr = (WeakReference) mPackages.get(packageName);
if(null==wr )
Log.e("filename","null==packageInfo");
Log.e("filename","android.app.LoadedApk");
InMemoryDexClassLoader inMemoryDexClassLoader = new InMemoryDexClassLoader(byteBuffers,
(ClassLoader) RefInvoke.getFieldOjbect(
"android.app.LoadedApk", wr.get(), "mClassLoader"));
Log.i(TAG,"父classloader:"+(ClassLoader) RefInvoke.getFieldOjbect(
"android.app.LoadedApk", wr.get(), "mClassLoader"));
//将父节点DexClassLoader替换
RefInvoke.setFieldOjbect("android.app.LoadedApk", "mClassLoader",
wr.get(), inMemoryDexClassLoader);
Log.i(TAG,"子classloader:"+inMemoryDexClassLoader);
}catch (Exception e){
e.printStackTrace();
}
}
小结:
相对 原生 apk 其实还是会有性能问题,不过相对 上一版本,性能要好一些,安全性会好很多;
到这里dex 加固基本告一段落。后面的安全问题其实就是 so 问题,这个基本搞java 没办法 搞,后面整理了再发出来;
这里只是展示 思路,可能并不能直接拿来用,见谅;