[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

点击收藏 | 0 关注 | 1
  • 动动手指,沙发就是你的了!
登录 后跟帖