文章目录

  • 一、 命中 ActivityThread 中 installProvider 方法的分支三
    • 1、 原理分析
    • 2、 代码实现
  • 二、 在 ContextImpl 的 createPackageContext 方法执行前进行 Application 替换
    • 1、 原理分析
    • 2、 代码实现
  • 三、 完整代码示例
  • 四、日志分析
  • 五、源码资源

前两篇分析 ContentProvider 中的 Application 的博客 :

  • 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application )
  • 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application 二 )

ContentProvider 中替换 Application 的总结 :

  • ① 分支选择 : 首先要命中 ActivityThread 中 installProvider 方法的分支三 ;
  • ② Application 替换 : 然后要在 ContextImpl 的 createPackageContext 方法执行前进行一次 Application 替换 ;

一、 命中 ActivityThread 中 installProvider 方法的分支三


1、 原理分析

ActivityThread 中的 installProvider 方法中的三个分支如下 , 在上面的分析中 , 如果要使得分支一 context.getPackageName().equals(ai.packageName) 与分支二 mInitialApplication.getPackageName().equals(ai.packageName) , 都无法命中 , 就需要 Application 的 getPackageName 方法获取的包名不等于在 AndroidManifest.xml 中的包名 ai.packageName , 这里重写 ProxyApplication 的 getPackageName 方法 , 使该方法返回值为 “” 字符串 , 这样就无法命中前两个分支 , 只能进入 else 分支 ;

public final class ActivityThread {private ContentProviderHolder installProvider(Context context,ContentProviderHolder holder, ProviderInfo info,boolean noisy, boolean noReleaseNeeded, boolean stable) {// 该上下文对象很重要 Context c = null;ApplicationInfo ai = info.applicationInfo;// 该 context 是 ProxyApplication , 代理 Application if (context.getPackageName().equals(ai.packageName)) {// 在应用中配置的代理 Application 包名与真实 Application 包名都是相等的// 该分支是命中的 c = context;} else if (mInitialApplication != null &&mInitialApplication.getPackageName().equals(ai.packageName)) {// 该分支中 mInitialApplication 就是 Context context 参数 , 肯定不为空 // 该分支无法命中 c = mInitialApplication;} else {// 上述两个分支都无法命中 , 才进入该分支 // 需要将代理 Application 的包名 与 真实应用的包名设置成不同的// 此时上面两个分支都无法命中 try {c = context.createPackageContext(ai.packageName,Context.CONTEXT_INCLUDE_CODE);} catch (PackageManager.NameNotFoundException e) {// Ignore}}return retHolder;}}

参考路径 : frameworks/base/core/java/android/app/ActivityThread.java

2、 代码实现

代码示例 : 暂时省略其余代码 ;

public class ProxyApplication extends Application {@Overridepublic String getPackageName() {if(TextUtils.isEmpty(app_name)){// 如果 AndroidManifest.xml 中配置的 Application 全类名为空// 那么 不做任何操作}else{// 如果 AndroidManifest.xml 中配置的 Application 全类名不为空// 为了使 ActivityThread 的 installProvider 方法// 无法命中如下两个分支// 分支一 : context.getPackageName().equals(ai.packageName)// 分支二 : mInitialApplication.getPackageName().equals(ai.packageName)// 设置该方法返回值为空 , 上述两个分支就无法命中return "";}return super.getPackageName();}}

二、 在 ContextImpl 的 createPackageContext 方法执行前进行 Application 替换


1、 原理分析

分支三中调用了 , context 的 createPackageContext(ai.packageName, Context.CONTEXT_INCLUDE_CODE) 方法 , 该方法在 ContextImpl 中定义 ;

在 ContextImpl 中的 createPackageContext 方法 , 调用了 createPackageContextAsUser 方法 , 调用了如下代码 , 创建 Context 上下文 ,

ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,flags, null);

上述代码中创建 ContextImpl 时 , 使用的 mMainThread , pi , 都没有替换过 Application , 因此分支三创建的 ContentProvider 对应的 Application 也是代理 Application , 替换前的 Application 对象 ;

class ContextImpl extends Context {// 在该方法中调用了 createPackageContextAsUser 方法创建上下文@Overridepublic Context createPackageContext(String packageName, int flags)throws NameNotFoundException {return createPackageContextAsUser(packageName, flags,mUser != null ? mUser : Process.myUserHandle());}@Overridepublic Context createPackageContextAsUser(String packageName, int flags, UserHandle user)throws NameNotFoundException {if (packageName.equals("system") || packageName.equals("android")) {// The system resources are loaded in every application, so we can safely copy// the context without reloading Resources.return new ContextImpl(this, mMainThread, mPackageInfo, null, mActivityToken, user,flags, null);}// 注意该 LoadedApk 对象LoadedApk pi = mMainThread.getPackageInfo(packageName, mResources.getCompatibilityInfo(),flags | CONTEXT_REGISTER_PACKAGE, user.getIdentifier());if (pi != null) {// 创建新的 ContextImpl // 此时还没有替换 Application ContextImpl c = new ContextImpl(this, mMainThread, pi, null, mActivityToken, user,flags, null);final int displayId = mDisplay != null? mDisplay.getDisplayId() : Display.DEFAULT_DISPLAY;c.setResources(createResources(mActivityToken, pi, null, displayId, null,getDisplayAdjustments(displayId).getCompatibilityInfo()));if (c.mResources != null) {return c;}}// Should be a better exception.throw new PackageManager.NameNotFoundException("Application package " + packageName + " not found");}}

源码路径 : frameworks/base/core/java/android/app/ContextImpl.java

ContextImpl 中的 public Context createPackageContext(String packageName, int flags) 方法是公开方法 , 重写该方法 , 在重写的 createPackageContext 方法中 , 先进行一次 Application 替换 , 然后继续执行 super.createPackageContext 方法的后续操作 , 这样创建的 ContentProvider 中的上下文就是用户自定义的 MyApplication , 不再是 ProxyApplication ;

只有在创建 ContentProvider 时才调用到该 createPackageContext 方法 , 如果没有调用到该方法 , 说明该应用中没有配置 ContentProvider ;

2、 代码实现

代码实现 : 在 代理 Application 的中重写 public Context createPackageContext(String packageName, int flags) 方法 , 先替换 Application , 然后再继续向后执行 ;

这里建议 Application 替换操作 , 只执行一次 , 使用 Application delegate 是否为空 , 作为替换操作是否执行的标志 ;

public class ProxyApplication extends Application {@Overridepublic void onCreate() {super.onCreate();// 如果之前没有替换过 , 执行 Application 替换操作// 说明没有调用到 createPackageContext 方法// 该 createPackageContext 方法只有在创建 ContentProvider 时才调用到// 如果没有调用到 , 说明 AndroidManifest.xml 中没有配置 ContentProvider// 此时需要在此处进行 Application 替换if (delegate == null){applicationExchange();}}@Overridepublic Context createPackageContext(String packageName, int flags)throws PackageManager.NameNotFoundException {if(TextUtils.isEmpty(app_name)){// 如果 AndroidManifest.xml 中配置的 Application 全类名为空// 说明没有进行 dex 加密操作 , 返回父类方法执行即可return super.createPackageContext(packageName, flags);}else{// 只有在创建 ContentProvider 时才调用到该 createPackageContext 方法 ,// 如果没有调用到该方法 , 说明该应用中没有配置 ContentProvider ;// 该方法不一定会调用到// 先进行 Application 替换applicationExchange();// Application 替换完成之后 , 再继续向下执行创建 ContentProviderreturn delegate;}}/*** 调用 applicationExchange 替换 Application* 该成员就是替换后的 Application*/private Application delegate;/*** Application 替换主方法*/private void applicationExchange(){}
}

三、 完整代码示例


完整代码示例 :

package kim.hsl.multipledex;import android.app.Application;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.text.TextUtils;
import android.util.Log;import java.io.File;
import java.io.IOException;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;public class ProxyApplication extends Application {public static final String TAG = "ProxyApplication";/*** 应用真实的 Application 全类名*/String app_name;/*** DEX 解密之后的目录名称*/String app_version;/*** 在 Application 在 ActivityThread 中被创建之后,* 第一个调用的方法是 attachBaseContext 函数.* 该函数是 Application 中最先执行的函数.*/@Overrideprotected void attachBaseContext(Context base) {super.attachBaseContext(base);try {Log.i(TAG, "attachBaseContext");/*在该 Application 中主要进行两个操作 :1 . 解密并加载多个 DEX 文件2 . 将真实的 Application 替换成应用的主 Application*//*I . 解密与加载多 DEX 文件先进行解密, 然后再加载解密之后的 DEX 文件1. 先获取当前的 APK 文件2. 然后解压该 APK 文件*/// 获取当前的 APK 文件, 下面的 getApplicationInfo().sourceDir 就是本应用 APK 安装文件的全路径File apkFile = new File(getApplicationInfo().sourceDir);// 获取在 app Module 下的 AndroidManifest.xml 中配置的元数据,// 应用真实的 Application 全类名// 解密后的 dex 文件存放目录ApplicationInfo applicationInfo = null;applicationInfo = getPackageManager().getApplicationInfo(getPackageName(),PackageManager.GET_META_DATA);Bundle metaData = applicationInfo.metaData;if (metaData != null) {// 检查是否存在 app_name 元数据if (metaData.containsKey("app_name")) {app_name = metaData.getString("app_name").toString();}// 检查是否存在 app_version 元数据if (metaData.containsKey("app_version")) {app_version = metaData.getString("app_version").toString();}}// 创建用户的私有目录 , 将 apk 文件解压到该目录中File privateDir = getDir(app_name + "_" + app_version, MODE_PRIVATE);Log.i(TAG, "attachBaseContext 创建用户的私有目录 : " + privateDir.getAbsolutePath());// 在上述目录下创建 app 目录// 创建该目录的目的是存放解压后的 apk 文件的File appDir = new File(privateDir, "app");// app 中存放的是解压后的所有的 apk 文件// app 下创建 dexDir 目录 , 将所有的 dex 目录移动到该 dexDir 目录中// dexDir 目录存放应用的所有 dex 文件// 这些 dex 文件都需要进行解密File dexDir = new File(appDir, "dexDir");// 遍历解压后的 apk 文件 , 将需要加载的 dex 放入如下集合中ArrayList<File> dexFiles = new ArrayList<File>();// 如果该 dexDir 不存在 , 或者该目录为空 , 并进行 MD5 文件校验if (!dexDir.exists() || dexDir.list().length == 0) {// 将 apk 中的文件解压到了 appDir 目录ZipUtils.unZipApk(apkFile, appDir);// 获取 appDir 目录下的所有文件File[] files = appDir.listFiles();Log.i(TAG, "attachBaseContext appDir 目录路径 : " + appDir.getAbsolutePath());Log.i(TAG, "attachBaseContext appDir 目录内容 : " + files);// 遍历文件名称集合for (int i = 0; i < files.length; i++) {File file = files[i];Log.i(TAG, "attachBaseContext 遍历 " + i + " . " + file);// 如果文件后缀是 .dex , 并且不是 主 dex 文件 classes.dex// 符合上述两个条件的 dex 文件放入到 dexDir 中if (file.getName().endsWith(".dex") &&!TextUtils.equals(file.getName(), "classes.dex")) {// 筛选出来的 dex 文件都是需要解密的// 解密需要使用 OpenSSL 进行解密// 获取该文件的二进制 Byte 数据// 这些 Byte 数组就是加密后的 dex 数据byte[] bytes = OpenSSL.getBytes(file);// 解密该二进制数据, 并替换原来的加密 dex, 直接覆盖原来的文件即可OpenSSL.decrypt(bytes, file.getAbsolutePath());// 将解密完毕的 dex 文件放在需要加载的 dex 集合中dexFiles.add(file);// 拷贝到 dexDir 中Log.i(TAG, "attachBaseContext 解密完成 被解密文件是 : " + file);}// 判定是否是需要解密的 dex 文件}// 遍历 apk 解压后的文件} else {// 已经解密完成, 此时不需要解密, 直接获取 dexDir 中的文件即可for (File file : dexDir.listFiles()) {dexFiles.add(file);}}Log.i(TAG, "attachBaseContext 解密完成 dexFiles : " + dexFiles);for (int i = 0; i < dexFiles.size(); i++) {Log.i(TAG, i + " . " + dexFiles.get(i).getAbsolutePath());}// 截止到此处 , 已经拿到了解密完毕 , 需要加载的 dex 文件// 加载自己解密的 dex 文件loadDex(dexFiles, privateDir);Log.i(TAG, "attachBaseContext 完成");} catch (PackageManager.NameNotFoundException e) {e.printStackTrace();} catch (Exception e) {e.printStackTrace();}}/*** 加载 dex 文件集合* 这些 dex 文件已经解密* 参考博客 : https://hanshuliang.blog.csdn.net/article/details/109608605* <p>* 创建自己的 Element[] dexElements 数组* ( libcore/dalvik/src/main/java/dalvik/system/DexPathList.java )* 然后将 系统加载的 Element[] dexElements 数组 与 我们自己的 Element[] dexElements 数组进行合并操作*/void loadDex(ArrayList<File> dexFiles, File optimizedDirectory)throwsIllegalAccessException,InvocationTargetException,NoSuchFieldException,NoSuchMethodException {Log.i(TAG, "loadDex");/*需要执行的步骤1 . 获得系统 DexPathList 中的 Element[] dexElements 数组( libcore/dalvik/src/main/java/dalvik/system/DexPathList.java )2 . 在本应用中创建 Element[] dexElements 数组 , 用于存放解密后的 dex 文件3 . 将 系统加载的 Element[] dexElements 数组与 我们自己的 Element[] dexElements 数组进行合并操作4 . 替换 ClassLoader 加载过程中的 Element[] dexElements 数组 ( 封装在 DexPathList 中 )*//*1 . 获得系统 DexPathList 中的 Element[] dexElements 数组第一阶段 : 在 Context 中调用 getClassLoader() 方法 , 可以拿到 PathClassLoader ;第二阶段 : 从 PathClassLoader 父类 BaseDexClassLoader 中找到 DexPathList ;第三阶段 : 获取封装在 DexPathList 类中的 Element[] dexElements 数组 ;上述的 DexPathList 对象 是 BaseDexClassLoader 的私有成员Element[] dexElements 数组 也是 DexPathList 的私有成员因此只能使用反射获取 Element[] dexElements 数组*/// 阶段一二 : 调用 getClassLoader() 方法可以获取 PathClassLoader 对象// 从 PathClassLoader 对象中获取 private final DexPathList pathList 成员Field pathListField = ReflexUtils.reflexField(getClassLoader(), "pathList");// 获取 classLoader 对象对应的 DexPathList pathList 成员Object pathList = pathListField.get(getClassLoader());//阶段三 : 获取封装在 DexPathList 类中的 Element[] dexElements 数组Field dexElementsField = ReflexUtils.reflexField(pathList, "dexElements");// 获取 pathList 对象对应的 Element[] dexElements 数组成员Object[] dexElements = (Object[]) dexElementsField.get(pathList);/*2 . 在本应用中创建 Element[] dexElements 数组 , 用于存放解密后的 dex 文件不同的 Android 版本中 , 创建 Element[] dexElements 数组的方法不同 , 这里需要做兼容*/Method makeDexElements;Object[] addElements = null;if (Build.VERSION.SDK_INT <=Build.VERSION_CODES.M) { // 5.0, 5.1  makeDexElements// 反射 5.0, 5.1, 6.0 版本的 DexPathList 中的 makeDexElements 方法makeDexElements = ReflexUtils.reflexMethod(pathList, "makeDexElements",ArrayList.class, File.class, ArrayList.class);ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,optimizedDirectory,suppressedExceptions);} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {   // 7.0 以上版本 makePathElements// 反射 7.0 以上版本的 DexPathList 中的 makeDexElements 方法makeDexElements = ReflexUtils.reflexMethod(pathList, "makePathElements",List.class, File.class, List.class);ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();addElements = (Object[]) makeDexElements.invoke(pathList, dexFiles,optimizedDirectory,suppressedExceptions);}/*3 . 将 系统加载的 Element[] dexElements 数组与 我们自己的 Element[] dexElements 数组进行合并操作首先创建数组 , 数组类型与 dexElements 数组类型相同将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.size将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.size*/Object[] newElements = (Object[]) Array.newInstance(dexElements.getClass().getComponentType(),dexElements.length + addElements.length);// 将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.sizeSystem.arraycopy(dexElements, 0, newElements, 0, dexElements.length);// 将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.sizeSystem.arraycopy(addElements, 0, newElements, dexElements.length, addElements.length);/*4 . 替换 ClassLoader 加载过程中的 Element[] dexElements 数组 ( 封装在 DexPathList 中 )*/dexElementsField.set(pathList, newElements);Log.i(TAG, "loadDex 完成");}@Overridepublic void onCreate() {super.onCreate();// 如果之前没有替换过 , 执行 Application 替换操作// 说明没有调用到 createPackageContext 方法// 该 createPackageContext 方法只有在创建 ContentProvider 时才调用到// 如果没有调用到 , 说明 AndroidManifest.xml 中没有配置 ContentProvider// 此时需要在此处进行 Application 替换if (delegate == null){applicationExchange();}}@Overridepublic String getPackageName() {if(TextUtils.isEmpty(app_name)){// 如果 AndroidManifest.xml 中配置的 Application 全类名为空// 那么 不做任何操作}else{// 如果 AndroidManifest.xml 中配置的 Application 全类名不为空// 为了使 ActivityThread 的 installProvider 方法// 无法命中如下两个分支// 分支一 : context.getPackageName().equals(ai.packageName)// 分支二 : mInitialApplication.getPackageName().equals(ai.packageName)// 设置该方法返回值为空 , 上述两个分支就无法命中return "";}return super.getPackageName();}@Overridepublic Context createPackageContext(String packageName, int flags)throws PackageManager.NameNotFoundException {if(TextUtils.isEmpty(app_name)){// 如果 AndroidManifest.xml 中配置的 Application 全类名为空// 说明没有进行 dex 加密操作 , 返回父类方法执行即可return super.createPackageContext(packageName, flags);}else{// 只有在创建 ContentProvider 时才调用到该 createPackageContext 方法 ,// 如果没有调用到该方法 , 说明该应用中没有配置 ContentProvider ;// 该方法不一定会调用到// 先进行 Application 替换applicationExchange();// Application 替换完成之后 , 再继续向下执行创建 ContentProviderreturn delegate;}}/*** 调用 applicationExchange 替换 Application* 该成员就是替换后的 Application*/private Application delegate;/*** Application 替换主方法*/private void applicationExchange(){try {/*在此处进行 Application 替换*/// 先判断是否有配置 Application ,// 那么在 Manifest.xml 中的 meta-data 元数据 app_name 不为空// 如果开发者没有自定义 Application , 没有配置元数据 , 直接退出if (TextUtils.isEmpty(app_name)) {return;}// 获取上下文对象 , 保存下来 , 之后要使用Context baseContext = getBaseContext();// 通过反射获取 Application , 系统也是进行的反射操作Class<?> delegateClass = Class.forName(app_name);// 创建用户真实配置的 Applicationdelegate = (Application) delegateClass.newInstance();// 调用 Application 的 attach 函数// 该函数无法直接调用 , 也需要通过反射调用// 这里先通过反射获取 Application 的 attach 函数Method attach = Application.class.getDeclaredMethod("attach", Context.class);// attach 方法是私有的 , 设置 attach 方法允许访问attach.setAccessible(true);// 获取上下文对象 ,// 该 Context 是通过调用 Application 的 attachBaseContext 方法传入的 ContextImpl// 将该上下文对象传入 Application 的 attach 方法中attach.invoke(delegate, baseContext);/*参考 : https://hanshuliang.blog.csdn.net/article/details/111569017 博客查询应该替换哪些对象中的哪些成员截止到此处, Application 创建完毕 , 下面开始逐个替换下面的 Application① ContextImpl 的 private Context mOuterContext成员是 kim.hsl.multipledex.ProxyApplication 对象 ;② ActivityThread 中的 ArrayList<Application> mAllApplications集合中添加了 kim.hsl.multipledex.ProxyApplication 对象 ;③ LoadedApk 中的 mApplication 成员是 kim.hsl.multipledex.ProxyApplication 对象 ;④ ActivityThread 中的 Application mInitialApplication成员是 kim.hsl.multipledex.ProxyApplication 对象 ;*/// I . 替换 ① ContextImpl 的 private Context mOuterContext//  成员是 kim.hsl.multipledex.ProxyApplication 对象Class<?> contextImplClass = Class.forName("android.app.ContextImpl");// 获取 ContextImpl 中的 mOuterContext 成员Field mOuterContextField = contextImplClass.getDeclaredField("mOuterContext");// mOuterContext 成员是私有的 , 设置可访问性mOuterContextField.setAccessible(true);// ContextImpl 就是应用的 Context , 直接通过 getBaseContext() 获取即可mOuterContextField.set(baseContext, delegate);// II . 替换 ④ ActivityThread 中的 Application mInitialApplication//                    成员是 kim.hsl.multipledex.ProxyApplication 对象 ;Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");// 获取 ActivityThread 中的 mInitialApplication 成员Field mInitialApplicationField =activityThreadClass.getDeclaredField("mInitialApplication");// mInitialApplication 成员是私有的 , 设置可访问性mInitialApplicationField.setAccessible(true);// 从 ContextImpl 对象中获取其 ActivityThread mMainThread 成员变量Field mMainThreadField = contextImplClass.getDeclaredField("mMainThread");mMainThreadField.setAccessible(true);// ContextImpl 就是本应用的上下文对象 , 调用 getBaseContext 方法获得Object mMainThread = mMainThreadField.get(baseContext);// ContextImpl 就是应用的 Context , 直接通过 getBaseContext() 获取即可mInitialApplicationField.set(mMainThread, delegate);// III . 替换 ② ActivityThread 中的 ArrayList<Application> mAllApplications//                    集合中添加了 kim.hsl.multipledex.ProxyApplication 对象 ;// 获取 ActivityThread 中的 mAllApplications 成员Field mAllApplicationsField =activityThreadClass.getDeclaredField("mAllApplications");// mAllApplications 成员是私有的 , 设置可访问性mAllApplicationsField.setAccessible(true);// 获取 ActivityThread 中的 ArrayList<Application> mAllApplications 队列ArrayList<Application> mAllApplications =(ArrayList<Application>) mAllApplicationsField.get(mMainThread);// 将真实的 Application 添加到上述队列中mAllApplications.add(delegate);// IV . 替换 ③ LoadedApk 中的 mApplication//          成员是 kim.hsl.multipledex.ProxyApplication 对象// 1. 先获取 LoadedApk 对象// LoadedApk 是 ContextImpl 中的 LoadedApk mPackageInfo 成员变量// 从 ContextImpl 对象中获取其 LoadedApk mPackageInfo 成员变量Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo");mPackageInfoField.setAccessible(true);// ContextImpl 就是本应用的上下文对象 , 调用 getBaseContext 方法获得Object mPackageInfo = mPackageInfoField.get(baseContext);// 2. 获取 LoadedApk 对象中的 mApplication 成员Class<?> loadedApkClass = Class.forName("android.app.LoadedApk");// 获取 ActivityThread 中的 mInitialApplication 成员Field mApplicationField =loadedApkClass.getDeclaredField("mApplication");// LoadedApk 中的 mApplication 成员是私有的 , 设置可访问性mApplicationField.setAccessible(true);// 3. 将 Application 设置给 LoadedApk 中的 mApplication 成员mApplicationField.set(mPackageInfo, delegate);// V . 下一步操作替换替换 ApplicationInfo 中的 className , 该操作不是必须的 , 不替换也不会报错// 在应用中可能需要操作获取应用的相关信息 , 如果希望获取准确的信息 , 需要替换 ApplicationInfo// ApplicationInfo 在 LoadedApk 中Field mApplicationInfoField = loadedApkClass.getDeclaredField("mApplicationInfo");// 设置该字段可访问mApplicationInfoField.setAccessible(true);// mPackageInfo 就是 LoadedApk 对象// mApplicationInfo 就是从 LoadedApk 对象中获得的 mApplicationInfo 字段ApplicationInfo mApplicationInfo = (ApplicationInfo) mApplicationInfoField.get(mPackageInfo);// 设置 ApplicationInfo 中的 className 字段值mApplicationInfo.className = app_name;// 再次调用 onCreate 方法delegate.onCreate();} catch (ClassNotFoundException e) {e.printStackTrace();} catch (IllegalAccessException e) {e.printStackTrace();} catch (InstantiationException e) {e.printStackTrace();} catch (NoSuchMethodException e) {e.printStackTrace();} catch (InvocationTargetException e) {e.printStackTrace();} catch (NoSuchFieldException exception) {exception.printStackTrace();}}
}

四、日志分析


修改后再次分析 ContentProvider 中的 Application 是替换后的 MyApplication ;

2021-04-05 19:31:27.928 15336-15336/kim.hsl.dex I/octopus.MyApplication: Application : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:27.929 15336-15336/kim.hsl.dex I/octopus.MyApplication: ApplicationContext : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:27.929 15336-15336/kim.hsl.dex I/octopus.MyApplication: ApplicationInfo.className : kim.hsl.dex.MyApplication
2021-04-05 19:31:27.929 15336-15336/kim.hsl.dex I/octopus.MyProvider: Application : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:27.929 15336-15336/kim.hsl.dex I/octopus.MyProvider: ApplicationContext : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:27.929 15336-15336/kim.hsl.dex I/octopus.MyProvider: ApplicationInfo.className : kim.hsl.dex.MyApplication
2021-04-05 19:31:28.030 15336-15336/kim.hsl.dex I/octopus.MainActivity: Application : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:28.030 15336-15336/kim.hsl.dex I/octopus.MainActivity: ApplicationContext : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:28.030 15336-15336/kim.hsl.dex I/octopus.MainActivity: ApplicationInfo.className : kim.hsl.dex.MyApplication
2021-04-05 19:31:28.033 15336-15336/kim.hsl.dex E/octopus.MyProvider: MyProvider delete : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:28.051 15336-15336/kim.hsl.dex I/octopus.MyService: Application : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:28.051 15336-15336/kim.hsl.dex I/octopus.MyService: ApplicationContext : kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:28.051 15336-15336/kim.hsl.dex I/octopus.MyService: ApplicationInfo.className : kim.hsl.dex.MyApplication
2021-04-05 19:31:28.052 15336-15336/kim.hsl.dex I/octopus.MyBroadCastReciver: reciver:android.app.ReceiverRestrictedContext@e207566
2021-04-05 19:31:28.052 15336-15336/kim.hsl.dex I/octopus.MyBroadCastReciver: reciver:kim.hsl.dex.MyApplication@5439ba9
2021-04-05 19:31:28.052 15336-15336/kim.hsl.dex I/octopus.MyBroadCastReciver: reciver:kim.hsl.dex.MyApplication

五、源码资源


DEX 加密源码资源 :

  • GitHub 地址 : https://github.com/han1202012/DexEncryption
  • CSDN 源码快照 : https://download.csdn.net/download/han1202012/16465693
  • 注意事项 : DexEncryption\multiple-dex-tools\src\main\java\kim\hsl\multiple_dex_tools 中的 Main.kt 中 , sdkDirectory 修改成你自己电脑上的 SDK 配置 , 需要使用其中的 build-tools 下的 签名工具 , 对齐工具 等 ;
val sdkDirectory = "Y:/001_DevelopTools/002_Android_SDK/"
  • 执行流程 : 先按照 【Android 安全】DEX 加密 ( Java 工具开发 | 加密解密算法 API | 编译代理 Application 依赖库 | 解压依赖库 aar 文件 ) 生成依赖库的 aar 文件 , 然后选择 菜单栏 -> Build -> Build Bundle(s) / APK (s) 选项 , 最后执行 DexEncryption\multiple-dex-tools\src\main\java\kim\hsl\multiple_dex_tools 中的 Main.kt 文件 ;

【Android 安全】DEX 加密 ( Application 替换 | 兼容 ContentProvider 操作 | 源码资源 )相关推荐

  1. 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application 二 )

    文章目录 一. ActivityThread 中的 installProvider 方法 ( 创建 ContentProvider 内容提供者 ) 二. installProvider 方法的第三分支 ...

  2. 【Android 安全】DEX 加密 ( Application 替换 | 分析 ContentProvider 组件中调用 getApplication() 获取的 Application )

    文章目录 一. ContentProvider 创建过程分析 二. ActivityThread 中的 H 处理 BIND_APPLICATION 消息 三. ActivityThread 中的 ha ...

  3. 【Android 安全】DEX 加密 ( Application 替换 | 分析 BroadcastReceiver 组件中调用 getApplication() 获取的 Application )

    文章目录 一. Service 中的 getApplication() 方法分析 二. ActivityThread 中的 H 处理 RECEIVER 消息 三. ActivityThread 中的 ...

  4. 【Android 安全】DEX 加密 ( Application 替换 | 替换 LoadedApk 中的 Application mApplication 成员 )

    文章目录 一. 当前 Application 替换进度 二. 替换 LoadedApk 中的 Application mApplication 成员 一. 当前 Application 替换进度 上一 ...

  5. 【Android 安全】DEX 加密 ( Application 替换 | ActivityThread 中的 mAllApplications 集合添加 Application )

    文章目录 一. 当前 Application 替换进度 二. ActivityThread 中的 mAllApplications 集合添加 Application 一. 当前 Application ...

  6. 【Android 安全】DEX 加密 ( Application 替换 | 创建用户自定义 Application | 替换 ContextImpl 对象的 mOuterContext 成员 )

    文章目录 一.创建用户自定义 Application 二.替换 ContextImpl 对象的 mOuterContext 成员 dex 解密时 , 需要将 代理 Application 替换为 真实 ...

  7. 【Android 安全】DEX 加密 ( Application 替换 | 获取 ContextImpl、ActivityThread、LoadedApk 类型对象 )

    文章目录 一.获取对象类型分析 二.替换 Application 时机 一.获取对象类型分析 在 [Android 安全]DEX 加密 ( Application 替换 | 获取 ContextImp ...

  8. 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | ActivityThread 后续分析 | Application 替换位置 )

    文章目录 一.ActivityThread 后续分析 二.ActivityThread 相关源码 三.Application 替换位置 dex 解密时 , 需要将 代理 Application 替换为 ...

  9. 【Android 安全】DEX 加密 ( Application 替换 | Android 应用启动原理 | Instrumentation 源码分析 )

    文章目录 一.Instrumentation 源码分析 二.Instrumentation 创建 Application 相关的部分源码 dex 解密时 , 需要将 代理 Application 替换 ...

最新文章

  1. java与.net比较学习系列(7) 属性
  2. 发新款电池!成本降低14%,特斯拉会继续降价吗?
  3. LeetCode Combination Sum IV(动态规划)
  4. 可编辑树Ztree的使用(包括对后台数据库的增删改查)
  5. Linux文件查找工具之find “大宝剑”--转载
  6. 又到半年总结时,IT人只想躺平!
  7. C#使用HTTP头检测网络资源是否有效
  8. unix系统编码 java_Java 正确的做字符串编码转换
  9. 【转载】网络流和最小费用流
  10. 获取 Web 设计的免费资源
  11. Angular4 存储访问路由栈信息
  12. Request获取url各种信息的方法
  13. 传高盛与德劭前合伙人组5亿美元私募基金
  14. 真正的焦虑感来自对比
  15. C# webclient UploadStringAsync如何得到变量?
  16. cmake编译时不能指定头文件路径?
  17. Chrome OS 初体验
  18. Android基础篇-DrawerLayout 实现侧滑
  19. ipad上的人体模型_我拥有哪种iPad模型?
  20. 程序的优化 文字的减法

热门文章

  1. Oracle字符集问题总结
  2. Win2000域控制器+Exchange2000迁移至Win2003域控制器+Exchange2003
  3. COJ 2192: Wells弹键盘 (dp)
  4. Python学习之路—2018/6/20
  5. beego07----web博客
  6. 现在的社会,能负债的人,一定是有本事的人
  7. 最长上升子序列(LIS)
  8. 一个经典的字母排列算法
  9. 判断、转载-【Cocos2D-X 】初窥门径(8)判断精灵点击-by小雨
  10. idea无法创建javaclass文件