深入理解Apk加固之Dex保护

2019-08-01 约 3856 字 预计阅读 8 分钟

声明:本文 【深入理解Apk加固之Dex保护】 由作者 yong夜 于 2019-08-01 09:31:00 首发 先知社区 曾经 浏览数 37 次

感谢 yong夜 的辛苦付出!

[TOC]

预知识

Apk应用启动过程

1.(Launcher应用程序中执行)点击图标,Launcher应用程序通知ActivityManagerService开启activity

2.(ActivityManagerService组件中执行)ActivityManagerService通过IBinder向Launcher组件发送SCHEDULE_PAUSE_ACTIVITY_TRANSACTION

3.(Launcher应用程序中执行)Launcher的ActivityThread来处理发送来的消息,暂停Launcher组件,接着发送ACTIVITY_PAUSED_TRANSACTION消息给ActivityManagerService组件

4.(ActivityManagerService组件中执行)ActivityManagerService处理Launcher发送来的消息,接着创建应用程序进程,主要通过zygoto来创建新的应用进程和新的ActivityThread

5.(应用进程内部执行)应用进程向ActivityManagerService发送进程通信请求ATTACH_APPLICATION_TRANSACTION(表示应用启动完成)

6.(ActivityManagerService组件中执行)处理应用进程发送来的请求,既然应用进程已经创建,现在开始应用进程对象ProcessRecord的初始化操作,并且向应用进程发送进程通信请求 。

7.(ActivityThread中执行)这里的初始化操作中比较重要的一项就是bindApplication,发送BIND_APPLICATION消息给H类,调用handleBindApplication方法进行Application类的创建和初始化

8.后面就是MainActivity组件的启动过程,在正式启动Activity前也会执行makeApplication方法来查看application是否创建

总时序图

应用程序启动过程中,先实例化application来初始化全局变量,接着通过全局变量来启动Activity组件

起源:ClassLoader

类加载器,起源于JAVA,实现java类可以被动态加载进虚拟机

  • 引导类加载器(bootstrap class loader):它用来加载 Java 的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader
  • 扩展类加载器(extensions class loader):它用来加载 Java 的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。
  • 系统类加载器(system class loader):它根据 Java 应用的类路径(CLASSPATH)来加载 Java 类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。

类加载器代理模式

如上面加载器树状组织结构所示,子类加载器加载类时(查找类字节码并定义它),会代理给父类加载器(不是父类对象),父类加载器尝试加载该类,如果父类加载器不能加载,再由子类加载器加载

加载类的过程

因为代理模式的存在,启动这个类的加载过程的类加载器(子类加载器)和真正完成类加载工作的类加载器(父类加载器)不是同一个类加载器。

  • defineClass:真正完成类的加载工作,一个类的定义加载器
  • loadClass:启动类的加载过程,初始加载器

线程上下文类加载器

默认使用系统类加载器。

解决引导类加载器无法加载找到系统类加载器需要加载的类

小结

【1】Java虚拟机对于两个类判断是否相同,出了名称是否一样还要判断类的定义加载器是否一样

【2】不同类加载器加载的类是不兼容的,也就是Java虚拟机中存在一个个相互隔离的类空间。

现在:DexClassLoader源码

1.设置父类加载器

2.优化并加载dex文件到内存中(本地方法)

替换类加载器

默认类加载器是PathClassLoader

什么时候执行到application组件内?

在开始启动Activity组件前的一步,有ActivityThread内执行bindApplication方法中创建Application组件,performLaunchActivity启动Activity类中也会调用makeApplication检查是否创建applicaiton

脱壳思路

what

动态加载Dex文件,防止反编译

how

流程

  1. 创建壳Application

  2. 使用DexClassLoader加载原始Dex

  3. 开启原始Application
    1. 调用ActivityThread中LoadedApk对象的makeApplication方法来创建原始Dex文件的Application对象
    2. 替换ActivityThread中mBoundApplication的mApplication属性为原始Dex文件的Application对象
    3. 替换ActivityThread中的mInitialApplication属性为原始Dex文件的Application对象
    4. 替换ActivityThread中LoadedApk的className为原始Dex文件的Application的name
    5. 替换ActivityThread中mBoundApplication里appInfo的className为原始Dex文件的Application的name
    6. 替换ActivityThread中mProviderMap中每个provider的mContext为原始Dex文件的Application对象
    7. 调用原始Dex文件的Application对象的onCreate方法开始原始Dex文件的组件启动

实例

源码地址:https://github.com/xiongchaochao/ApkProtect/tree/master/v1.0

根据预知识Activity启动流程:application类实例化在开启MainActivity之前,所以我们将动态加载工作放在application中,也就是我们自定义一个壳application并生成壳Dex文件让系统启动,在这个壳application中去开启原始Dex文件的application

public class shell extends Application {
    .....
}

根据预知识DexClassLoader原理:类加载器可以加载dex文件到内存中,但是不能启动里面的Application、Activity等组件启动原始dex文件的执行流程。这是我们下一步的方向,开启类加载器加载的Dex文件的组件

/* 2. 加载Dex文件。
        * 通过替换ActivityThread对象内loadedapk的mClassLoader值,完成了类加载器的替换和Dex文件的加载*/
        String classActivityThread = "android.app.ActivityThread";
        String classLoadedApk = "android.app.LoadedApk";
        DexClassLoader loader;
        File nativeLib = new File(FileUtils.getParent(LIBS), "lib");

        Object activityThread = RefInvoke.invokeStaticMethod(classActivityThread, "currentActivityThread", new Class[]{}, new Object[]{});

        String packageName = this.getPackageName();//当前apk的包名

        //获取currentActivityThread的mPackages字段值,是一个包名对应loadedapk对象的map值
        Map<?,?> mPackage = (Map<?,?>)RefInvoke.getField(activityThread, classActivityThread, "mPackages");
        //获取loadedapk对象的弱引用
        WeakReference<?> wr = (WeakReference<?>) mPackage.get(packageName);

        //获取DexClassLoader类加载器,准备加载原始dex文件,并且设置父类加载器为当前主线程上下文中loadedApk中保存的mClassLoader对象
        loader = new DexClassLoader(dexPathList.toString(), mApp.getCacheDir().getAbsolutePath(), nativeLib.getAbsolutePath(), (ClassLoader) RefInvoke.getField(wr.get(), "android.app.LoadedApk", "mClassLoader"));

        //更改当前loadedapk对象的类加载器为DexClassLoader类加载器
        RefInvoke.setField(wr.get(), classLoadedApk, "mClassLoader", loader);

用原始Dex文件的application类重新实例化一个对象,并且替换掉ActivityThread和LoadedApk对象内部的applicaiton对象,这样后面开始之后Activity的时候就会调用新的application类的内部属性。

/* 2. 执行application*/

        //原始Dex文件的application地址
        String main = "com.scoreloop.games.gearedub.GearApplication";
        String applicationName = main;
        //ActivityThread.currentActivityThread().mBoundApplication.info.mApplication = null;
        Object currentActivityThread = RefInvoke.invokeStaticMethod(classActivityThread, "currentActivityThread", new Class[]{}, new Object[]{});

        Object mBoundApplication = RefInvoke.getField(currentActivityThread, classActivityThread, "mBoundApplication");

        //info字段代表的是loadedapk对象
        Object loadedApkInfo = RefInvoke.getField(mBoundApplication, classActivityThread+"$AppBindData", "info");
        //将loadedapk的mApplication属性置空,在后面makeApplication方法中用原始Dex文件的Application对象赋值
        RefInvoke.setField(loadedApkInfo, classLoadedApk, "mApplication", null);

        //移除ActivityThread对象内的mInitialApplication,后面重新赋值原始Dex文件的Application对象赋值
        Object mInitApplication = RefInvoke.getField(currentActivityThread, classActivityThread, "mInitialApplication");
        List<Application> mAllApplications = (List<Application>) RefInvoke.getField(currentActivityThread, classActivityThread, "mAllApplications");
        mAllApplications.remove(mInitApplication);

        //将原始Dex文件的application路径名分别赋值给loadedapk和ActivityThread$AppBindData的className
        //(LoadedApk) loadedApkInfo.mApplicationInfo.className = applicationName
        ((ApplicationInfo) RefInvoke.getField(loadedApkInfo, classLoadedApk, "mApplicationInfo")).className = applicationName;
        //(ActivityThread$AppBindData) mBoundApplication.appInfo.className = applicationName
        ((ApplicationInfo) RefInvoke.getField(mBoundApplication, classActivityThread+"$AppBindData", "appInfo")).className = applicationName;

        //给ActivityThread的mInitialApplication赋值makeApplication对象
        //currentActivityThread.mInitApplication = loadedApkInfo.makeApplication(false, null)
        Application makeApplication = (Application) RefInvoke.invokeMethod(loadedApkInfo, classLoadedApk, "makeApplication", new Class[]{boolean.class, Instrumentation.class}, new Object[]{false, null});
        RefInvoke.setField(currentActivityThread, classActivityThread, "mInitialApplication", makeApplication);

        //currentActivityThread.mProviderMap
        Map<?,?> mProviderMap = (Map<?,?>) RefInvoke.getField(currentActivityThread, classActivityThread, "mProviderMap");
        for (Map.Entry<?, ?> entry : mProviderMap.entrySet()) {
            Object providerClientRecord = entry.getValue();
            Object mLocalProvider = RefInvoke.getField(providerClientRecord, classActivityThread+"$ProviderClientRecord", "mLocalProvider");
            RefInvoke.setField(mLocalProvider, "android.content.ContentProvider", "mContext", makeApplication);
        }
        makeApplication.onCreate();

小结

【1】DexClassLoader 加载器加载dex文件字节码到虚拟机中,但是不能没有生命周期,只是不同类

【2】在ActivityThread内部是通过LoadedApk类来存储APK安装包所有数据

参考

【1】深入探讨 Java 类加载器 https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html

【2】从源码分析 Android dexClassLoader 加载机制原理 https://blog.csdn.net/nanzhiwen666/article/details/50515895

关键词:[‘安全技术’, ‘移动安全’]


author

旭达网络

旭达网络技术博客,曾记录各种技术问题,一贴搞定.
本文采用知识共享署名 4.0 国际许可协议进行许可。

We notice you're using an adblocker. If you like our webite please keep us running by whitelisting this site in your ad blocker. We’re serving quality, related ads only. Thank you!

I've whitelisted your website.

Not now