Android Apk 加固之Dex文件 完善篇 InMemoryDexClassLoader 之内存加载dex

首先声明,只针对 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 没办法 搞,后面整理了再发出来;

这里只是展示 思路,可能并不能直接拿来用,见谅;

demo 原码

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章