文章目录
一、将系统的 dexElements 与 应用的 dexElements 合并
二、Element[] dexElements 替换操作
三、完整 dex 加载源码
参考博客 :
【Android 安全】DEX 加密 ( 常用 Android 反编译工具 | apktool | dex2jar | enjarify | jd-gui | jadx )
【Android 安全】DEX 加密 ( Proguard 简介 | Proguard 相关网址 | Proguard 混淆配置 )
【Android 安全】DEX 加密 ( Proguard 简介 | 默认 ProGuard 分析 )
【Android 安全】DEX 加密 ( Proguard keep 用法 | Proguard 默认混淆结果 | 保留类及成员混淆结果 | 保留注解以及被注解修饰的类/成员/方法 )
【Android 安全】DEX 加密 ( Proguard 混淆 | 混淆后的报错信息 | Proguard 混淆映射文件 mapping.txt )
【Android 安全】DEX 加密 ( Proguard 混淆 | 将混淆后的报错信息转为原始报错信息 | retrace.bat 命令执行目录 | 暴露更少信息 )
【Android 安全】DEX 加密 ( DEX 加密原理 | DEX 加密简介 | APK 文件分析 | DEX 分割 )
【Android 安全】DEX 加密 ( 多 DEX 加载 | 65535 方法数限制和 MultiDex 配置 | PathClassLoader 类加载源码分析 | DexPathList )
【Android 安全】DEX 加密 ( 不同 Android 版本的 DEX 加载 | Android 8.0 版本 DEX 加载分析 | Android 5.0 版本 DEX 加载分析 )
【Android 安全】DEX 加密 ( DEX 加密使用到的相关工具 | dx 工具 | zipalign 对齐工具 | apksigner 签名工具 )
【Android 安全】DEX 加密 ( 支持多 DEX 的 Android 工程结构 )
【Android 安全】DEX 加密 ( 代理 Application 开发 | multiple-dex-core 依赖库开发 | 配置元数据 | 获取 apk 文件并准备相关目录 )
【Android 安全】DEX 加密 ( 代理 Application 开发 | 解压 apk 文件 | 判定是否是第一次启动 | 递归删除文件操作 | 解压 Zip 文件操作 )
【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 反射获取系统的 Element[] dexElements )
【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 使用反射获取方法创建本应用的 dexElements | 各版本创建 dex 数组源码对比 )
在 【Android 安全】DEX 加密 ( 支持多 DEX 的 Android 工程结构 ) 博客中介绍了 DEX 加密工程的基本结构 ,
app 是主应用 , 其 Module 类型是 “Phone & Tablet Module” ,
multiple-dex-core 是 Android 依赖库 , 其作用是解密并加载多 DEX 文件 , 其 Module 类型是 “Android Library” ,
multiple-dex-tools 是 Java 依赖库 , 其类型是 “Java or Kotlin Library” , 其作用是用于生成主 DEX ( 主 DEX 的作用就是用于解密与加载多 DEX ) , 并且还要为修改后的 APK 进行签名 ;
在 【Android 安全】DEX 加密 ( 代理 Application 开发 | multiple-dex-core 依赖库开发 | 配置元数据 | 获取 apk 文件并准备相关目录 ) 博客中讲解了 multiple-dex-core 依赖库开发 , 每次启动都要解密与加载 dex 文件 , 在该博客中讲解到了 获取 apk 文件 , 并准备解压目录 ;
在 【Android 安全】DEX 加密 ( 代理 Application 开发 | 解压 apk 文件 | 判定是否是第一次启动 | 递归删除文件操作 | 解压 Zip 文件操作 ) 博客中讲解了 apk 文件解压操作 ;
在 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 反射获取系统的 Element[] dexElements )博客中讲解了 dex 文件加载第一阶段 , 获取系统中的 Element[] dexElements ;
在 【Android 安全】DEX 加密 ( 代理 Application 开发 | 加载 dex 文件 | 使用反射获取方法创建本应用的 dexElements | 各版本创建 dex 数组源码对比 ) 博客中讲解了讲解 dex 文件加载操作 第二阶段 , 创建本应用的 dex 文件数组 Element[] dexElements ;
本博客中主要讲解 剩余的两个操作 :
将 系统加载的 Element[] dexElements 数组 与 我们 自己的 Element[] dexElements 数组 进行合并操作 ;
替换 ClassLoader 加载过程中的 Element[] dexElements 数组 ( 封装在 DexPathList 中 )
一、将系统的 dexElements 与 应用的 dexElements 合并
加载 dex 文件 需要执行的步骤 :
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 中 )
之前的博客中已经讲解了前两个步骤的操作 , 开始执行第三个步骤 , 将 系统加载的 Element[] dexElements 数组 与 我们自己的 Element[] dexElements 数组进行合并操作 ;
首先 创建数组 , 数组类型与 dexElements 数组类型相同 ,
将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.size ,
将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.size ;
代码示例 :
/* 3 . 将 系统加载的 Element[] dexElements 数组 与 我们自己的 Element[] dexElements 数组进行合并操作 首先创建数组 , 数组类型与 dexElements 数组类型相同 将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.size 将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.size */ var newElements: Array<Any> = java.lang.reflect.Array.newInstance(dexElements.javaClass.componentType, dexElements.size + addElements.size) as Array<Any> // 将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.size System.arraycopy(dexElements, 0, newElements, 0, dexElements.size) // 将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.size System.arraycopy(addElements, 0, newElements, dexElements.size, addElements.size)
二、Element[] dexElements 替换操作
替换 ClassLoader 加载过程中的 Element[] dexElements 数组 ( 封装在 DexPathList 中 )
/* 4 . 替换 ClassLoader 加载过程中的 Element[] dexElements 数组 ( 封装在 DexPathList 中 ) */ dexElementsField.set(pathList, newElements)
该 Field 字段由之前反射获得 ;
//阶段三 : 获取封装在 DexPathList 类中的 Element[] dexElements 数组 var dexElementsField = reflexField(pathList, "dexElements") // 获取 pathList 对象对应的 Element[] dexElements 数组成员 var dexElements : Array<Any> = dexElementsField.get(pathList) as Array<Any>
三、完整 dex 加载源码
/** * 加载 dex 文件集合 * 这些 dex 文件已经解密 * 参考博客 : https://hanshuliang.blog.csdn.net/article/details/109608605 * * 创建自己的 Element[] dexElements 数组 * ( libcore/dalvik/src/main/java/dalvik/system/DexPathList.java ) * 然后将 系统加载的 Element[] dexElements 数组 与 我们自己的 Element[] dexElements 数组进行合并操作 * 00:17:07 */ fun loadDex(dexFiles : ArrayList<File>, optimizedDirectory : File) : Unit{ /* 需要执行的步骤 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 成员 var pathListField = reflexField(classLoader, "DexPathList"); // 获取 classLoader 对象对应的 DexPathList pathList 成员 var pathList = pathListField.get(classLoader) //阶段三 : 获取封装在 DexPathList 类中的 Element[] dexElements 数组 var dexElementsField = reflexField(pathList, "dexElements") // 获取 pathList 对象对应的 Element[] dexElements 数组成员 var dexElements : Array<Any> = dexElementsField.get(pathList) as Array<Any> /* 2 . 在本应用中创建 Element[] dexElements 数组 , 用于存放解密后的 dex 文件 不同的 Android 版本中 , 创建 Element[] dexElements 数组的方法不同 , 这里需要做兼容 */ var makeDexElements: Method var addElements : Array<Any> = Array(0, {}) if(Build.VERSION.SDK_INT <= Build.VERSION_CODES.LOLLIPOP_MR1){ // 5.0, 5.1 makeDexElements // 反射 5.0, 5.1, 6.0 版本的 DexPathList 中的 makeDexElements 方法 makeDexElements = reflexMethod(pathList, "makeDexElements", ArrayList::class.java, File::class.java, ArrayList::class.java) var suppressedExceptions: ArrayList<IOException> = ArrayList<IOException>() addElements = makeDexElements.invoke(pathList, dexFiles, optimizedDirectory, suppressedExceptions) as Array<Any> }else if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M){ // 7.0 以上版本 makePathElements // 反射 7.0 以上版本的 DexPathList 中的 makeDexElements 方法 makeDexElements = reflexMethod(pathList, "makePathElements", ArrayList::class.java, File::class.java, ArrayList::class.java) var suppressedExceptions: ArrayList<IOException> = ArrayList<IOException>() addElements = makeDexElements.invoke(pathList, dexFiles, optimizedDirectory, suppressedExceptions) as Array<Any> } /* 3 . 将 系统加载的 Element[] dexElements 数组 与 我们自己的 Element[] dexElements 数组进行合并操作 首先创建数组 , 数组类型与 dexElements 数组类型相同 将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.size 将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.size */ var newElements: Array<Any> = java.lang.reflect.Array.newInstance(dexElements.javaClass.componentType, dexElements.size + addElements.size) as Array<Any> // 将 dexElements 数组中的元素拷贝到 newElements 前半部分, 拷贝元素个数是 dexElements.size System.arraycopy(dexElements, 0, newElements, 0, dexElements.size) // 将 addElements 数组中的元素拷贝到 newElements 后半部分, 拷贝元素个数是 dexElements.size System.arraycopy(addElements, 0, newElements, dexElements.size, addElements.size) /* 4 . 替换 ClassLoader 加载过程中的 Element[] dexElements 数组 ( 封装在 DexPathList 中 ) */ dexElementsField.set(pathList, newElements) }