[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
流程
-
创建壳Application
-
使用DexClassLoader加载原始Dex
- 开启原始Application
- 调用ActivityThread中LoadedApk对象的makeApplication方法来创建原始Dex文件的Application对象
- 替换ActivityThread中mBoundApplication的mApplication属性为原始Dex文件的Application对象
- 替换ActivityThread中的mInitialApplication属性为原始Dex文件的Application对象
- 替换ActivityThread中LoadedApk的className为原始Dex文件的Application的name
- 替换ActivityThread中mBoundApplication里appInfo的className为原始Dex文件的Application的name
- 替换ActivityThread中mProviderMap中每个provider的mContext为原始Dex文件的Application对象
- 调用原始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