首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
2
2
[原创动画] [源码分析] --脱壳中, DEX 内存加载 和 磁盘路径方式加载
发表于: 2025-6-10 13:38
971
[原创动画] [源码分析] --脱壳中, DEX 内存加载 和 磁盘路径方式加载
calleng
2025-6-10 13:38
971
## 在脱壳中, 两种 DEX 内存加载 和 SD卡方式加载, 源代码分析. **InMemoryDexClassLoader 和 DexClassLoader 的流程的分析** **动画采用4K录制,建议4k显示器观看.** 分为两个部分, 为了视频的清晰, 暂时提供 ftp下载. ## 文件1, 2025-06-10_在脱壳中, 两种 DEX 内存加载 和 SD卡方式加载, 源代码分析(1).mkv ## 文件2, 2025-06-10_在脱壳中, 两种 DEX 内存加载 和 SD卡方式加载, 源代码分析(2).mkv 举例子, 有个同学问了你一个问题 , 就是老师说, 你搞懂了吗? --- 回答, 搞懂了! 然后,老师说, 你把这个题,给那个同学讲一讲. --- 于是, 思路不是太清晰, 好像懂了, 好像又没有懂. ---- 所以, 当你给别人真正 讲明白了的时候, 才开始更换角色思考. ==> 日语, 版本, 英语版本. 本篇目涉及两个,问题, 一个是课件, 一个是 通过 Android Studio 和 CLion 分别阅读 AOSP8.1 的 Java层源码和 CPP 层的源码. 过程中,带着繁琐的跳转, 没有人能够一次通过. 都是多遍通过. 在 2025 , 几乎不会没有人不用 AI , AI 什么都懂 ,但是不代表,你懂得, 所以, 只有你懂了, 才是真格. 没有严谨的 分析考究AI提供的依据 , AI 就是胡说! 使用它, 抱着最 朴素的观念, 就是证伪! (AI就是错的.) InMemoryDexClassLoader 源码分析 ------ ## ???? 调用流程总览 ```plaintext InMemoryDexClassLoader (Java) └── BaseDexClassLoader (Java) └── DexPathList (Java) └── makeInMemoryDexElements (Java) └── DexFile (Java) └── DexFile.openInMemoryDexFile (Java) └── `DexFile.DexFile_createCookieWithDirectBuffer 或 `DexFile_createCookieWithArray (Native) └── CreateSingleDexFileCookie (Native) └── DexFile::CreateDexFile (Native) └──DexFile::Open (Native) └── DexFile::OpenCommon (Native) └── DexFile::DexFile 构造函数 (Native) ``` ## 详细调用链与源码位置 ### 1. `InMemoryDexClassLoader`(Java 层) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java` - **作用**:Java 层入口,允许从 `ByteBuffer` 加载 DEX 数据。 - **调用**:构造函数中调用父类 `BaseDexClassLoader` 的构造函数。 ``` public InMemoryDexClassLoader(ByteBuffer[] dexBuffers, ClassLoader parent) ``` ### 2. `BaseDexClassLoader` 构造函数 (Java) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java` 这里调用了父类构造函数,跟进到父类BaseDexClassLoader中 - **作用**:初始化 `DexPathList`,并将其赋值给成员变量 `pathList`。 ```java public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) { // TODO We should support giving this a library search path maybe. super(parent); this.pathList = new DexPathList(this, dexFiles); } ``` ### 3. `DexPathList` 构造函数 (Java) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexPathList.java` - **作用**:解析 `dexFiles`,并调用 `makeInMemoryDexElements` 方法生成 `dexElements`。 ```java public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) { ... this.dexElements = makeInMemoryDexElements(dexFiles, suppressedExceptions); ... } ``` ### 4. `makeInMemoryDexElements` 方法 (Java) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexPathList.java` - **作用**:遍历 `dexPath` 中的文件,调用 `loadDexFile` 方法加载每个 `.dex` 文件。 ```java private static Element[] makeInMemoryDexElements(ByteBuffer[] dexFiles, List<IOException> suppressedExceptions) { Element[] elements = new Element[dexFiles.length]; int elementPos = 0; for (ByteBuffer buf : dexFiles) { try { DexFile dex = new DexFile(buf); elements[elementPos++] = new Element(dex); } catch (IOException suppressed) { System.logE("Unable to load dex file: " + buf, suppressed); suppressedExceptions.add(suppressed); } } if (elementPos != elements.length) { elements = Arrays.copyOf(elements, elementPos); } return elements; } ``` ### 5. `DexFile` 构造方法 (Java) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexFile.java` - **作用**:调用 `openInMemoryDexFile` 方法加载 `Dex` 文件到内存。 ```java DexFile(ByteBuffer buf) throws IOException { // 构造函数,接受 ByteBuffer 作为参数,可能会抛出 IOException 异常 mCookie = openInMemoryDexFile(buf); // 调用 openInMemoryDexFile 方法加载 Dex 文件到内存并返回 cookie // 这样做可以避免磁盘 I/O,提升加载速度 mInternalCookie = mCookie; // 将 mCookie 赋值给 mInternalCookie // 这样做是为了保持内部状态的一致性,方便后续操作 mFileName = null; // 初始化文件名为 null // 初始化为空,表示当前没有文件路径信息,可能是内存中的 Dex 文件 } ``` ### 6. `DexFile.openInMemoryDexFile` 方法 (Java) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexFile.java` - **作用**: 根据 ByteBuffer 类型决定使用 是direct buffer 还是 array buffer, 通过 createCookieWithDirectBuffer 方法,或者 createCookieWithArray 方法 ```java // 定义静态方法,用于打开内存中的 dex 文件,并根据 ByteBuffer 类型决定使用何种方式处理 // 通过提供不同的实现方式,支持 direct buffer 和 array buffer private static Object openInMemoryDexFile(ByteBuffer buf) throws IOException { // 判断 ByteBuffer 是否是 direct 类型的缓冲区 if (buf.isDirect()) { // 如果是 direct 缓冲区,使用直接内存创建 Cookie 对象 return createCookieWithDirectBuffer(buf, buf.position(), buf.limit()); // 如果不是 direct 类型的缓冲区 } else { // 使用数组创建 Cookie对象,通过 buf.array() 直接访问底层数组,用于非 direct 缓冲区 return createCookieWithArray(buf.array(), buf.position(), buf.limit()); } } // 声明一个Native方法,使用直接缓冲区(ByteBuffer)创建 Cookie 对象 // 使用直接缓冲区可以提高 I/O 操作的效率,避免了缓冲区的复制 private static native Object createCookieWithDirectBuffer(ByteBuffer buf, int start, int end); // 声明一个Native方法,使用字节数组(byte[])创建 Cookie 对象 // 使用字节数组更为常见,适用于大多数情况下的内存操作 private static native Object createCookieWithArray(byte[] buf, int start, int end); ``` ### 7. `DexFile. DexFile_createCookieWithDirectBuffer 或 `DexFile_createCookieWithArray 方法 (Native) - **位置**:`art/runtime/native/dalvik_system_DexFile.cc` - Native查询: control + N 检索 clion 中的符号, symbol , 或者 使用 control + shift + F , 全文检索. - Clion中,查询Native 函数, Java 文件名 + "_" + JNI函数名 "DexFile_createCookieWithDirectBuffer " - **作用**: 两种处理的分支是一样的, 只不过形态不一样. 都是走到最后一步,CreateSingleDexFileCookie( ) ```cpp // 定义了一个 JNI 方法,用于通过直接缓冲区创建 dex 文件 cookie ==================> static jobject DexFile_createCookieWithDirectBuffer(JNIEnv* env, jclass, // jclass 参数是类的引用,通常用于静态方法 jobject buffer, // 直接缓冲区对象 ,--> 直接对应 Java 第一参数 jint start, // 起始偏移量 jint end) { // 结束偏移量 // 获取直接缓冲区的地址并转换为 uint8_t 类型的指针 // 这是为了直接操作内存,避免复制数据 uint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer)); if (base_address == nullptr) { // 如果直接缓冲区的地址为空 ScopedObjectAccess soa(env); // 创建 ScopedObjectAccess 对象,确保在 JNI 环境中操作时持有对象引用 ThrowWrappedIOException("dexFileBuffer not direct"); // 抛出异常,提示缓冲区不是直接缓冲区 return 0; // 返回 0 表示创建失败 } // 分配内存映射,创建 dex 文件的内存映射对象 // 通过内存映射提高对大文件的处理效率 std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end)); if (dex_mem_map == nullptr) { // 如果内存映射失败 DCHECK(Thread::Current()->IsExceptionPending()); // 确保异常已经被抛出 return 0; // 返回 0 表示失败 } // 计算数据的长度 size_t length = static_cast<size_t>(end - start); // 将缓冲区中的数据复制到内存映射的起始位置 // 使用 memcpy 提高效率,避免逐字节处理 ==================> // ★ 此处的 dex_mem_map->Begin() 就是 dex 文件的起始地址 , length 是 dex 文件的长度 memcpy(dex_mem_map->Begin(), base_address, length); // 创建 dex 文件 cookie,并返回 =================> // ★ 核心跳转, 到最后就是一个 Memory Map. return CreateSingleDexFileCookie(env, std::move(dex_mem_map)); } // 定义了另一个 JNI 方法,通过字节数组创建 dex 文件 cookie // ===========>=// DexFile 中 createCookieWithArray 函数对应的 C++ 函数 static jobject DexFile_createCookieWithArray(JNIEnv* env, jclass, // jclass 参数是类的引用,通常用于静态方法 jbyteArray buffer, // 字节数组缓冲区 --> 直接对应java 第一参数. jint start, // 起始偏移量 jint end) { // 结束偏移量 // 分配内存映射,创建 dex 文件的内存映射对象 // 内存映射提供了对内存区域的有效访问 std::unique_ptr<MemMap> dex_mem_map(AllocateDexMemoryMap(env, start, end)); if (dex_mem_map == nullptr) { // 如果内存映射失败 DCHECK(Thread::Current()->IsExceptionPending()); // 确保异常已经被抛出 return 0; // 返回 0 表示失败 } // 获取内存映射的起始位置,并转换为 jbyte* 类型 auto destination = reinterpret_cast<jbyte*>(dex_mem_map.get()->Begin()); // 从字节数组中获取指定范围的数据并写入到内存映射中 // GetByteArrayRegion 提供了对字节数组的高效访问 env->GetByteArrayRegion(buffer, start, end - start, destination); // 创建 dex 文件 cookie,并返回 =========================> // ★ 核心跳转 ,到最后就是一个 Memory Map. return CreateSingleDexFileCookie(env, std::move(dex_mem_map)); } ``` ### 8. `CreateSingleDexFileCookie` 方法 (Native) - **位置**:`art/runtime/native/dalvik_system_DexFile.cc` - **作用**:调用 `CreateDexFile` 方法接收 `data` 参数。 ```cpp // 定义静态函数,用于创建一个单一的 Dex 文件 Cookie static jobject CreateSingleDexFileCookie(JNIEnv* env, std::unique_ptr<MemMap> data) { // 创建一个 DexFile 对象 dex_file,传入环境变量和数据,使用 std::move 传递数据 // 使用智能指针管理内存,避免手动释放 std::unique_ptr<const DexFile> dex_file(CreateDexFile(env, std::move(data))); // ★ 核心语句,接收处理的参数. if (dex_file.get() == nullptr) { // 如果创建的 DexFile 为空 DCHECK(env->ExceptionCheck()); // 检查是否抛出了异常,若有则进行调试输出 // 确保没有遗漏异常检查 return nullptr; // 如果 DexFile 创建失败,返回空指针 // 失败时返回空值,防止后续操作使用无效对象 } std::vector<std::unique_ptr<const DexFile>> dex_files; // 创建一个 DexFile 智能指针的 vector 用于保存多个 Dex 文件 dex_files.push_back(std::move(dex_file)); // 将创建的 dex_file 添加到 vector 中,使用 std::move 转移所有权 // 使用 std::move 来避免不必要的复制 return ConvertDexFilesToJavaArray(env, nullptr, dex_files); // 调用 ConvertDexFilesToJavaArray 函数将 Dex 文件转换为 Java 数组并返回 // 将 Dex 文件传递给 Java 环境以便进一步使用 } ``` ### 9. `DexFile::CreateDexFile` 方法 (Native) - **位置**:`art/runtime/native/dalvik_system_DexFile.cc` - **作用**:调用 `DexFile::Open` 方法打开 `.dex` 文件。 ```cpp static const DexFile* CreateDexFile(JNIEnv* env, std::unique_ptr<MemMap> dex_mem_map) { // 创建一个函数,用于生成一个不可修改的 DexFile 对象,接受 JNIEnv 和唯一的 MemMap std::string location = StringPrintf("Anonymous-DexFile@%p-%p", // 使用 StringPrintf 格式化字符串,创建一个表示 DexFile 的位置 dex_mem_map->Begin(), // 获取内存映射的起始地址 dex_mem_map->End()); // 获取内存映射的结束地址 // 目的是创建一个唯一的标识符,方便调试 std::string error_message; // 创建一个空的字符串用于存储错误信息 std::unique_ptr<const DexFile> dex_file(DexFile::Open(location, // 调用 DexFile 的 Open 方法打开 Dex 文件 0, // 没有额外的标志位 std::move(dex_mem_map), // 将内存映射传递给 DexFile /* verify */ true, // 启用验证 /* verify_location */ true, // 验证文件位置 &error_message)); // 传递错误信息的引用 if (dex_file == nullptr) { // 如果 DexFile 打开失败 ScopedObjectAccess soa(env); // 获取 JNIEnv 的访问权限 ThrowWrappedIOException("%s", error_message.c_str()); // 抛出带有错误信息的 IO 异常 return nullptr; // 返回空指针表示失败 } if (!dex_file->DisableWrite()) { // 如果 DexFile 无法设置为只读 ScopedObjectAccess soa(env); // 获取 JNIEnv 的访问权限 ThrowWrappedIOException("Failed to make dex file read-only"); // 抛出设置只读失败的异常 return nullptr; // 返回空指针表示失败 } return dex_file.release(); // 返回释放后的 DexFile 指针,表示成功创建了 DexFile 对象 } ``` ### 10. `DexFile::Open` 方法 (Native) - **位置**:`art/dex2oat/dex/dex_file.cc` - **作用**:调用 `OpenCommon` 方法进行通用的 `.dex` 文件打开逻辑。 ```cpp std::unique_ptr<const DexFile> DexFile::Open(const std::string& location, // 声明静态方法 Open,用于打开 Dex 文件 // 提供打开 Dex 文件的功能 uint32_t location_checksum, // 校验和,用于验证文件完整性 // 校验和可以确保文件未被篡改 std::unique_ptr<MemMap> map, // 内存映射对象,提供对文件的内存访问 // 使用内存映射提高文件访问性能 bool verify, // 是否验证文件内容 // 如果为真,增加校验来提高安全性 bool verify_checksum, // 是否验证校验和 // 如果为真,进一步验证文件的完整性 std::string* error_msg) { // 错误信息,存储打开文件时的错误信息 // 用于传递打开文件时的错误原因 ScopedTrace trace(std::string("Open dex file from mapped-memory ") + location); // 创建一个跟踪对象,用于记录调用堆栈的相关信息 // 用于调试时追踪方法调用过程 CHECK(map.get() != nullptr); // 检查 map 是否为 nullptr,如果为空抛出错误 // 保证 map 在使用前有效,防止空指针异常 if (map->Size() < sizeof(DexFile::Header)) { // 如果内存映射的文件大小小于 DexFile 头部的大小 *error_msg = StringPrintf( // 格式化并设置错误信息 "DexFile: failed to open dex file '%s' that is too short to have a header", // 错误信息内容,提示文件过小无法解析 location.c_str()); return nullptr; // 返回 nullptr,表示文件打开失败 // 文件过小无法解析,返回空指针表示失败 } // 一般都是喜欢hook这里, 是一个热点地区, 使用Frida hook 这里, 拿到数据... dump出来... std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), // 调用 OpenCommon 函数,进行文件解析 // 负责真正的文件解析 map->Size(), // 传入文件大小 location, // 传入文件路径 location_checksum, // 传入校验和 kNoOatDexFile, // 指定 OAT 文件标识 verify, // 是否验证文件 verify_checksum, // 是否验证校验和 error_msg); // 传入错误信息参数 if (dex_file != nullptr) { // 如果文件成功打开 dex_file->mem_map_ = std::move(map); // 将内存映射对象移交给 dex_file,确保文件数据的生命周期和 DexFile 一致 // 确保内存映射在 DexFile 关闭时释放 } return dex_file; // 返回打开的 DexFile 对象,如果失败则返回 nullptr // 返回打开的 DexFile 对象或空指针 } ``` ### 11. `DexFile::OpenCommon` 方法 (Native) - **位置**:`art/dex2oat/dex/dex_file.cc` - **作用**:验证 `.dex` 文件头部信息,并初始化 `DexFile` 对象。 ```cpp std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base, // 定义 OpenCommon 函数,用于打开 Dex 文件 // 该函数用于加载和验证 dex 文件 size_t size, // dex 文件的大小 // 需要指定文件的大小 const std::string& location, // dex 文件的路径 // 用于指定文件的位置 uint32_t location_checksum, // dex 文件路径的校验和 // 可能用于验证路径的完整性 const OatDexFile* oat_dex_file, // 可选的 oat 文件指针 // 用于包含 oat 文件的信息 bool verify, // 是否需要验证文件的有效性 // 控制是否执行验证 bool verify_checksum, // 是否验证校验和 // 控制是否进行校验和验证 std::string* error_msg, // 错误消息输出 // 用于返回错误信息 VerifyResult* verify_result) { // 验证结果输出 // 用于返回验证的结果 if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifyNotAttempted; // 设置验证结果为未尝试验证 // 如果没有进行验证,返回初始状态 } int pid = getpid(); // get the pid number to ceate_DEX Name char dexfilepath[100] = {0}; // get the Dex Path to know where it is sprintf(dexfilepath,"/sdcard/size_%d_pid_%d_DexFile.dex",(int)size,pid); // xunlie , To wirte the Name of DEx in SDCARD/ // int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666); if(fd>0){ int number = write(fd,base,size); // write byte stream to Disk. if(number>0){ } close(fd); } // ==================> 这里是 创建 一个 DexFile, 调用 New DexFile <======================== // 创建 DexFile 对象 // 尝试用给定参数创建 DexFile 实例 // =====================> => 第二案例, 内部分析了又调用了, new DexFile() std::unique_ptr<DexFile> dex_file(new DexFile(base, size, // dex 文件大小 location, // dex 文件路径 location_checksum, // dex 文件路径校验和 oat_dex_file)); // oat 文件指针 // ==================> 这里是 创建 一个 DexFile, 调用 New DexFile <======================== if (dex_file == nullptr) { // 如果 dex_file 创建失败 *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(), // 设置错误消息 // 提供详细的错误信息,帮助定位问题 error_msg->c_str()); // 包括错误内容 return nullptr; // 返回空指针,表示打开失败 } if (!dex_file->Init(error_msg)) { // 如果初始化 DexFile 失败 dex_file.reset(); // 重置 dex_file 指针,释放资源 return nullptr; // 返回空指针,表示初始化失败 } if (verify && !DexFileVerifier::Verify(dex_file.get(), // 如果需要验证且验证失败 dex_file->Begin(), // 获取文件开始位置 dex_file->Size(), // 获取文件大小 location.c_str(), // dex 文件路径 verify_checksum, // 是否验证校验和 error_msg)) { // 错误消息输出 if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifyFailed; // 设置验证结果为验证失败 } return nullptr; // 返回空指针,表示验证失败 } if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifySucceeded; // 设置验证结果为验证成功 } return dex_file; // 返回创建并验证成功的 dex_file } ``` ### 12. `DexFile::DexFile` 构造函数 (Native) - **位置**:`art/dex2oat/dex/dex_file.cc` - **作用**:初始化 `DexFile` 对象的各个成员,准备后续的类加载等操作。 ```cpp DexFile::DexFile(const uint8_t* base, // 构造函数的参数 base,指向 dex 文件的内存开始位置 size_t size, // 构造函数的参数 size,表示 dex 文件的大小 const std::string& location, // 构造函数的参数 location,表示文件位置 uint32_t location_checksum, // 构造函数的参数 location_checksum,表示文件位置的校验和 const OatDexFile* oat_dex_file) // 构造函数的参数 oat_dex_file,表示与此 dex 文件相关的 Oat 文件 : begin_(base), // 初始化 begin_,将 base 赋值给 begin_,表示 dex 文件的起始位置 size_(size), // 初始化 size_,将 size 赋值给 size_,表示 dex 文件的大小 location_(location), // 初始化 location_,将 location 赋值给 location_,表示文件的路径 location_checksum_(location_checksum), // 初始化 location_checksum_,将 location_checksum 赋值给 location_checksum_ header_(reinterpret_cast<const Header*>(base)), // 初始化 header_,通过 reinterpret_cast 将 base 转换为 Header 类型指针 string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)), // 初始化 string_ids_,通过 base 加上偏移量 header_->string_ids_off_ 获取 StringId 类型指针 type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)), // 初始化 type_ids_,通过 base 加上偏移量 header_->type_ids_off_ 获取 TypeId 类型指针 field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)), // 初始化 field_ids_,通过 base 加上偏移量 header_->field_ids_off_ 获取 FieldId 类型指针 method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)), // 初始化 method_ids_,通过 base 加上偏移量 header_->method_ids_off_ 获取 MethodId 类型指针 proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)), // 初始化 proto_ids_,通过 base 加上偏移量 header_->proto_ids_off_ 获取 ProtoId 类型指针 class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)), // 初始化 class_defs_,通过 base 加上偏移量 header_->class_defs_off_ 获取 ClassDef 类型指针 method_handles_(nullptr), // 初始化 method_handles_ 为 nullptr,表示尚未初始化 num_method_handles_(0), // 初始化 num_method_handles_ 为 0,表示方法句柄的数量为 0 call_site_ids_(nullptr), // 初始化 call_site_ids_ 为 nullptr,表示调用站点 ID 尚未初始化 num_call_site_ids_(0), // 初始化 num_call_site_ids_ 为 0,表示调用站点 ID 的数量为 0 oat_dex_file_(oat_dex_file) { // 初始化 oat_dex_file_,将 oat_dex_file 赋值给 oat_dex_file_ CHECK(begin_ != nullptr) << GetLocation(); // 检查 begin_ 是否为 nullptr,如果为 nullptr,则抛出异常并打印文件位置 // 这样做可以确保文件的有效性,防止后续访问出现空指针错误 CHECK_GT(size_, 0U) << GetLocation(); // 检查 size_ 是否大于 0,如果小于等于 0,则抛出异常并打印文件位置 // 确保文件大小合理,避免传入无效大小 // Check base (=header) alignment. // 检查 base(即 header)是否对齐 // Must be 4-byte aligned to avoid undefined behavior when accessing // any of the sections via a pointer. // 必须是 4 字节对齐,否则通过指针访问任何部分时可能会导致未定义行为 CHECK_ALIGNED(begin_, alignof(Header)); // 检查 begin_ 是否按 Header 类型的对齐方式对齐 // 通过对齐检查确保内存访问安全,防止由于不对齐导致的异常或性能问题 int pid = getpid(); // get the pid number to ceate_DEX Name char dexfilepath[100] = {0}; // get the Dex Path to know where it is sprintf(dexfilepath,"/sdcard/size_%d_pid_%d_DexFile.dex",(int)size,pid); // xunlie , To wirte the Name of DEx in SDCARD/ 这里是明显的自定义 代码段,自动写入dex到 sd卡. //int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666); if(fd>0){ int number = write(fd,base,size); // write byte stream to Disk. if(number>0){ } close(fd); } InitializeSectionsFromMapList(); // 调用 InitializeSectionsFromMapList 初始化各个部分 // 初始化部分结构,以确保后续可以正确访问文件的各个部分 } ``` ## ???? 总结 通过上述调用链,`InMemoryDexClassLoader` 实现了从内存中加载 DEX 文件的功能,避免了将 DEX 文件写入磁盘的需求。这一机制在插件化、热修复等场景中具有重要意义。 以下是基于 Android 8.1(AOSP 8.1)源码的 `InMemoryDexClassLoader` 加载内存中 DEX 文件的完整调用流程分析,包含关键函数、源码路径和作用说明。 ------ ## ???? DexClassLoader 调用流程总览 ```plaintext 1.DexClassLoader (Java 层) 2└── BaseDexClassLoader 3└── DexPathList 4└── Element.makeDexElements 5└── DexFile.loadDexFile 6└── DexFile.loadDex 7└── DexFile.DexFile 8└── openDexFile 9└── openDexFileNative (JNI) 10└── DexFile_openDexFileNative 11└── OatFileManager::OpenDexFilesFromOat 12└── DexFile::Open 13└── DexFile::OpenFile 14└── DexFile::OpenCommon 15└── DexFile::DexFile构造函数 ``` ------ ## 详细调用链与源码位置 ### 1. `DexClassLoader`(Java 层) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java` ```java public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, // 参数1 String optimizedDirectory, // 参数2 // 也是什么都没有做,就是直接改.... String librarySearchPath, // 参数3 ClassLoader parent // 参数4 ) { super(dexPath, null, librarySearchPath, parent); // 让自己的父亲[调用父类]去干,自己什么都不用干. } } ``` ### 2. `BaseDexClassLoader` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexPathList.java` ```java // 构造函数,接受多个参数来初始化 BaseDexClassLoader public BaseDexClassLoader(String dexPath, // 参数1 File optimizedDirectory, // 参数2 String librarySearchPath, // 参数3 ClassLoader parent) { // 参数4 super(parent); // 调用父类构造函数,传递父类加载器 // 继承父类的功能,确保父类加载器正确传递 [2w p1 course1 ] // 初始化 pathList,传递 dexPath 和 librarySearchPath 以供后续使用 // 通过 DexPathList 类来处理 dex 文件路径 this.pathList = new DexPathList(this, dexPath, // 参数1 librarySearchPath, // 参数3 null ); if (reporter != null) { // 如果 reporter 不为空 reportClassLoaderChain(); // 调用 reportClassLoaderChain 方法,报告类加载器链 // 这可以帮助调试和输出类加载器的状态 } } ``` ### 3. `DexPathList` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexPathList.java` ```java /** * 构造一个实例 * * @param definingContext 用于定义尚未解析的类的上下文 * @param dexPath dex 或资源路径元素的列表,元素之间使用 {@code File.pathSeparator} 分隔 * @param librarySearchPath 本地库目录路径元素的列表,元素之间使用 {@code File.pathSeparator} 分隔 * @param optimizedDirectory 存放优化后的 {@code .dex} 文件的目录,如果为 {@code null} 则使用默认的系统目录 */ public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory ) { if (definingContext == null) { // 如果 definingContext 为 null throw new NullPointerException("definingContext == null"); // 抛出 NullPointerException 异常,提示 definingContext 为空 // 确保 definingContext 不为空,避免空指针错误 } if (dexPath == null) { // 如果 dexPath 为 null throw new NullPointerException("dexPath == null"); // 抛出 NullPointerException 异常,提示 dexPath 为空 // 确保 dexPath 不为空,避免后续路径处理失败 } if (optimizedDirectory != null) { // 如果优化目录不为 null if (!optimizedDirectory.exists()) { // 如果目录不存在 throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); // 抛出 IllegalArgumentException,提示优化目录不存在 // 需要确保该目录存在,避免后续文件操作失败 } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { // 如果目录不可读写 throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); // 抛出 IllegalArgumentException,提示目录不可读写 // 确保目录可读写,避免后续文件操作失败 } } this.definingContext = definingContext; // 初始化 definingContext,保存 ClassLoader 供后续使用 // 用于在加载类时使用指定的 ClassLoader ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); // 创建一个列表,用于保存抑制的异常 // 用于收集抑制的异常,避免遗漏错误信息 // 保存 dexPath,用于 BaseDexClassLoader // 使用拆分后的 dexPath 和优化目录来创建 dex 元素,并处理异常 // 通过此方法加载并解析 dex 文件,可能会发生异常 this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext); // 本地库可能同时存在于系统库路径和应用库路径中,使用以下搜索顺序: // // 1. 该类加载器的应用库路径(librarySearchPath): // 1.1. 本地库目录 // 1.2. APK 文件中的库路径 // 2. VM 的系统属性中的库路径(java.library.path),即系统库路径 // // 此顺序在 Gingerbread 之前曾被反转,详见 http://b/2933456 this.nativeLibraryDirectories = splitPaths(librarySearchPath, false); // 拆分应用库路径并保存本地库目录 // 按照顺序拆分应用库路径,确保正确加载本地库 this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true); // 拆分系统库路径并保存系统本地库目录 // 获取并拆分系统的本地库路径,方便查找系统库 List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories); // 创建一个新的列表,包含所有应用本地库目录 allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories); // 将系统本地库目录添加到列表中 // 合并应用和系统的库路径,确保能够访问所有本地库 this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories); // 使用所有本地库目录创建路径元素 // 将所有本地库路径转换为可用的路径元素 if (suppressedExceptions.size() > 0) { // 如果存在抑制的异常 this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); // 将抑制的异常转换为数组并保存 // 将所有收集到的异常转换为数组形式,供后续处理 } else { // 如果没有抑制的异常 dexElementsSuppressedExceptions = null; // 设置为 null,因为没有异常 // 如果没有异常,避免浪费内存 } } ``` ### 4. `Element.makeDexElements` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexPathList.java` - ```java private static Element[] makeDexElements(List<File> files, File optimizedDirectory, List<IOException> suppressedExceptions, ClassLoader loader ) { Element[] elements = new Element[files.size()]; int elementsPos = 0; /* * Open all files and load the (direct or contained) dex files up front. */ for (File file : files) { if (file.isDirectory()) { // We support directories for looking up resources. Looking up resources in // directories is useful for running libcore tests. elements[elementsPos++] = new Element(file); } else if (file.isFile()) { String name = file.getName(); if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements); // 这里对 File 进行了一个加载........<=========== if (dex != null) { elements[elementsPos++] = new Element(dex, null); } } catch (IOException suppressed) { System.logE("Unable to load dex file: " + file, suppressed); suppressedExceptions.add(suppressed); } } else { DexFile dex = null; try { dex = loadDexFile(file, optimizedDirectory, loader, elements); // 这里对 File 进行了一个加载........<=========== } catch (IOException suppressed) { /* * IOException might get thrown "legitimately" by the DexFile constructor if * the zip file turns out to be resource-only (that is, no classes.dex file * in it). * Let dex == null and hang on to the exception to add to the tea-leaves for * when findClass returns null. */ suppressedExceptions.add(suppressed); } if (dex == null) { elements[elementsPos++] = new Element(file); } else { elements[elementsPos++] = new Element(dex, file); } } } else { System.logW("ClassLoader referenced unknown path: " + file); } } if (elementsPos != elements.length) { elements = Arrays.copyOf(elements, elementsPos); } return elements; } ``` ### 5. `DexFile.loadDexFile` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexPathList.java` - **作用**:封装 DEX 文件的加载逻辑,调用 `DexFile::Open` 进行实际加载。 ```java private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader, // 这里也是核心, 就是 Return New DEX Element[] elements) throws IOException { if (optimizedDirectory == null) { return new DexFile(file, loader, elements); // 这里也是核心, 就是 Return New DEX } else { String optimizedPath = optimizedPathFor(file, optimizedDirectory); return DexFile.loadDex(file.getPath(), // 这里 合计 5个参数 optimizedPath, 0, loader, elements); // 核心跳转点 DexFile.loadDex() 进行加载 } } ``` ### 6. `DexFile.loadDex` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexFile.java` - **作用**:根据提供的内存映射,打开并验证 DEX 文件,返回 `DexFile` 对象。 ```java static DexFile loadDex(String sourcePathName, // 这里 合计 5个参数 String outputPathName, int flags, ClassLoader loader, DexPathList.Element[] elements ) throws IOException { // 也是这里的5个参数的 loadDex /* * TODO: we may want to cache previously-opened DexFile objects. * The cache would be synchronized with close(). This would help * us avoid mapping the same DEX more than once when an app * decided to open it multiple times. In practice this may not * be a real issue. */ return new DexFile(sourcePathName, outputPathName, flags, loader, elements); } ``` ### 7. `DexFile.DexFile` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexFile.java` - **作用**:通用的 DEX 文件打开逻辑,处理头部验证、结构初始化等。 ```java private DexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements ) throws IOException { if (outputName != null) { // DexFile 在这里作了一个构造, 关键函数,source name. try { String parent = new File(outputName).getParent(); if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { throw new IllegalArgumentException("Optimized data directory " + parent + " is not owned by the current user. Shared storage cannot protect" + " your application from code injection attacks."); } } catch (ErrnoException ignored) { // assume we'll fail with a more contextual error later } } mCookie = openDexFile(sourceName, // 关键函数, 5 个参数 outputName, flags, loader, elements); // DexFile 在这里作了一个构造, 关键函数,source name. mInternalCookie = mCookie; mFileName = sourceName; //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); } ``` ### 8. `openDexFile` - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexFile.java` - **作用**:初始化 `DexFile` 对象的各个成员,准备后续的类加载等操作。 ```java private static Object openDexFile(String sourceName, // 关键函数, 5 个参数 String outputName, int flags, // 对他作了一个New , OpenDExFileNative.. ClassLoader loader, DexPathList.Element[] elements ) throws IOException { // Use absolute paths to enable the use of relative paths when testing on host. return openDexFileNative(new File(sourceName).getAbsolutePath(), // 对他作了一个New , OpenDExFileNative.. (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags, loader, elements); } ``` ### 9. `openDexFileNative` (JNI) - **位置**:`libcore/dalvik/src/main/java/dalvik/system/DexFile.java` - **作用**: `openDexFileNative` 就是一个 Native 的 JNI 函数,。 ```java private static native Object openDexFileNative(String sourceName, //参数1 String outputName, //参数2 int flags, //参数3 那么到了这里, 他的所谓的实现就看不到了. ClassLoader loader, // 参数4 DexPathList.Element[] elements // 参数5 ); // 和刚刚一样,还是看他的 Defination ``` ### 10. `DexFile_openDexFileNative` - **位置**:`art/runtime/native/dalvik_system_DexFile.cc` - **作用**:这里使用 CLion 继续查看 使用 Java文件名 + "_" + 函数名, 搜索 Symbol 就出现了结果. ```cpp // TODO(calin): clean up the unused parameters (here and in libcore). static jobject DexFile_openDexFileNative(JNIEnv* env, // 定义一个静态 JNI 方法,接收环境指针和其他参数,用于打开 dex 文件 jclass, // 类对象,未使用,保留为了符合 JNI 方法签名 jstring javaSourceName, // 参数1 --> 接收 Java 端传递的源文件名 jstring javaOutputName ATTRIBUTE_UNUSED, // 参数2 --> 输出文件名参数,未使用,标记为 ATTRIBUTE_UNUSED jint flags ATTRIBUTE_UNUSED, // 参数3 --> 标志参数,未使用,标记为 ATTRIBUTE_UNUSED jobject class_loader, // 参数4 --> 加载器对象,用于加载类 jobjectArray dex_elements // 参数5 ) { // dex 文件元素数组,用于加载 dex 文件 ScopedUtfChars sourceName(env, javaSourceName); // 创建 ScopedUtfChars 对象,将 Java 字符串转为 C++ 字符串 if (sourceName.c_str() == nullptr) { // 如果源文件名为空 return 0; // 返回 0,表示出错 } Runtime* const runtime = Runtime::Current(); // 获取当前的 Runtime 实例 ClassLinker* linker = runtime->GetClassLinker(); // 获取类链接器对象,用于后续类的加载 std::vector<std::unique_ptr<const DexFile>> dex_files; // 用于存储打开的 Dex 文件对象 std::vector<std::string> error_msgs; // 用于存储错误信息 const OatFile* oat_file = nullptr; // 初始化 oat 文件对象指针 dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), // 从 oat 文件管理器中打开 dex 文件 <========================= Important! class_loader, // 传入 class_loader 用于加载类 dex_elements, // 传入 dex 元素数组 /*out*/ &oat_file, // 输出 oat 文件 /*out*/ &error_msgs); // 输出错误信息 if (!dex_files.empty()) { // 如果成功打开了 dex 文件 jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files); // 将 dex 文件转换为 Java 数组 if (array == nullptr) { // 如果转换失败 ScopedObjectAccess soa(env); // 获取对象访问权限 for (auto& dex_file : dex_files) { // 遍历已打开的 dex 文件 if (linker->IsDexFileRegistered(soa.Self(), *dex_file)) { // 检查 dex 文件是否已注册 dex_file.release(); // 如果已注册,释放该文件 } } } return array; // 返回转换后的 Java 数组 } else { // 如果没有打开成功 ScopedObjectAccess soa(env); // 获取对象访问权限 CHECK(!error_msgs.empty()); // 确保有错误信息 // The most important message is at the end. So set up nesting by going forward, which will // wrap the existing exception as a cause for the following one. // 错误信息中最重要的在最后,逐一抛出异常并进行嵌套 auto it = error_msgs.begin(); // 创建错误信息迭代器 auto itEnd = error_msgs.end(); // 获取错误信息的结束迭代器 for ( ; it != itEnd; ++it) { // 遍历错误信息 ThrowWrappedIOException("%s", it->c_str()); // 抛出带包装的 IO 异常,包含错误信息 } return nullptr; // 返回空值,表示操作失败 } } ``` ### 11. `OatFileManager::OpenDexFilesFromOat` - **位置**:`art/runtime/oat_file_manager.cc` - **作用**: 这里会进行一个 dex To OAT 的优化。 ```cpp std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat( // 这里就要补充环境了, 还要对 OAT的环境进行优化.... const char* dex_location, jobject class_loader, jobjectArray dex_elements, const OatFile** out_oat_file, std::vector<std::string>* error_msgs) { // 二代壳,很多都可以对 OAT的流程禁止掉的.... 禁止掉了之后, OAT file 就已经找不到了... ScopedTrace trace(__FUNCTION__); // 找不到之后 , 他会回退到一个, Fall Back to running the original dexfile, if we couldn't load any dex file frome the oat file. CHECK(dex_location != nullptr); CHECK(error_msgs != nullptr); // Verify we aren't holding the mutator lock, which could starve GC if we // have to generate or relocate an oat file. Thread* const self = Thread::Current(); Locks::mutator_lock_->AssertNotHeld(self); Runtime* const runtime = Runtime::Current(); std::unique_ptr<ClassLoaderContext> context; // If the class_loader is null there's not much we can do. This happens if a dex files is loaded // directly with DexFile APIs instead of using class loaders. if (class_loader == nullptr) { LOG(WARNING) << "Opening an oat file without a class loader. " << "Are you using the deprecated DexFile APIs?"; context = nullptr; } else { context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements); } OatFileAssistant oat_file_assistant(dex_location, // 这里就是 OAT file的 一个优化流程.... kRuntimeISA, !runtime->IsAotCompiler()); // Lock the target oat location to avoid races generating and loading the // oat file. std::string error_msg; if (!oat_file_assistant.Lock(/*out*/&error_msg)) { // Don't worry too much if this fails. If it does fail, it's unlikely we // can generate an oat file anyway. VLOG(class_linker) << "OatFileAssistant::Lock: " << error_msg; } const OatFile* source_oat_file = nullptr; if (!oat_file_assistant.IsUpToDate()) { // Update the oat file on disk if we can, based on the --compiler-filter // option derived from the current runtime options. // This may fail, but that's okay. Best effort is all that matters here. // TODO(calin): b/64530081 b/66984396. Pass a null context to verify and compile // secondary dex files in isolation (and avoid to extract/verify the main apk // if it's in the class path). Note this trades correctness for performance // since the resulting slow down is unacceptable in some cases until b/64530081 // is fixed. switch (oat_file_assistant.MakeUpToDate(/*profile_changed*/ false, /*class_loader_context*/ nullptr, /*out*/ &error_msg)) { case OatFileAssistant::kUpdateFailed: LOG(WARNING) << error_msg; break; case OatFileAssistant::kUpdateNotAttempted: // Avoid spamming the logs if we decided not to attempt making the oat // file up to date. VLOG(oat) << error_msg; break; case OatFileAssistant::kUpdateSucceeded: // Nothing to do. break; } } // Get the oat file on disk. std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release()); // Prevent oat files from being loaded if no class_loader or dex_elements are provided. // This can happen when the deprecated DexFile.<init>(String) is called directly, and it // could load oat files without checking the classpath, which would be incorrect. if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { // Take the file only if it has no collisions, or we must take it because of preopting. bool accept_oat_file = !HasCollisions(oat_file.get(), context.get(), /*out*/ &error_msg); if (!accept_oat_file) { // Failed the collision check. Print warning. if (Runtime::Current()->IsDexFileFallbackEnabled()) { if (!oat_file_assistant.HasOriginalDexFiles()) { // We need to fallback but don't have original dex files. We have to // fallback to opening the existing oat file. This is potentially // unsafe so we warn about it. accept_oat_file = true; LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. " << "Allow oat file use. This is potentially dangerous."; } else { // We have to fallback and found original dex files - extract them from an APK. // Also warn about this operation because it's potentially wasteful. LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : " << dex_location; LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance."; } } else { // TODO: We should remove this. The fact that we're here implies -Xno-dex-file-fallback // was set, which means that we should never fallback. If we don't have original dex // files, we should just fail resolution as the flag intended. if (!oat_file_assistant.HasOriginalDexFiles()) { accept_oat_file = true; } LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to " " load classes for " << dex_location; } LOG(WARNING) << error_msg; } if (accept_oat_file) { VLOG(class_linker) << "Registering " << oat_file->GetLocation(); source_oat_file = RegisterOatFile(std::move(oat_file)); *out_oat_file = source_oat_file; } } std::vector<std::unique_ptr<const DexFile>> dex_files; // Load the dex files from the oat file. if (source_oat_file != nullptr) { bool added_image_space = false; if (source_oat_file->IsExecutable()) { std::unique_ptr<gc::space::ImageSpace> image_space = kEnableAppImage ? oat_file_assistant.OpenImageSpace(source_oat_file) : nullptr; if (image_space != nullptr) { ScopedObjectAccess soa(self); StackHandleScope<1> hs(self); Handle<mirror::ClassLoader> h_loader( hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader))); // Can not load app image without class loader. if (h_loader != nullptr) { std::string temp_error_msg; // Add image space has a race condition since other threads could be reading from the // spaces array. { ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseAddRemoveAppImageSpace, gc::kCollectorTypeAddRemoveAppImageSpace); ScopedSuspendAll ssa("Add image space"); runtime->GetHeap()->AddSpace(image_space.get()); } { ScopedTrace trace2(StringPrintf("Adding image space for location %s", dex_location)); added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(), h_loader, dex_elements, dex_location, /*out*/&dex_files, /*out*/&temp_error_msg); } if (added_image_space) { // Successfully added image space to heap, release the map so that it does not get // freed. image_space.release(); // Register for tracking. for (const auto& dex_file : dex_files) { dex::tracking::RegisterDexFile(dex_file.get()); } } else { LOG(INFO) << "Failed to add image file " << temp_error_msg; dex_files.clear(); { ScopedThreadSuspension sts(self, kSuspended); gc::ScopedGCCriticalSection gcs(self, gc::kGcCauseAddRemoveAppImageSpace, gc::kCollectorTypeAddRemoveAppImageSpace); ScopedSuspendAll ssa("Remove image space"); runtime->GetHeap()->RemoveSpace(image_space.get()); } // Non-fatal, don't update error_msg. } } } } if (!added_image_space) { DCHECK(dex_files.empty()); dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location); // Register for tracking. for (const auto& dex_file : dex_files) { dex::tracking::RegisterDexFile(dex_file.get()); } } if (dex_files.empty()) { error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation()); } else { // Opened dex files from an oat file, madvise them to their loaded state. for (const std::unique_ptr<const DexFile>& dex_file : dex_files) { OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad); } } } // HasOriginalDexFiles , 有了这个原始的 file , 使用 dexFileOpen来加载, 就回到了刚刚, 那个一样的逻辑中, 如果是纯一代的 壳,可能不会进入 是没有可能 进入到 进入到这个 dex To OAT, 没有进入到 DEx 2 OAT 的话, 他一定会从这个流程中,给脱出来,的, 但是二代壳普遍都是进入了, // Fall back to running out of the original dex file if we couldn't load any ==============================>找不到之后 , 他会回退到一个, Fall Back to running // dex_files from the oat file. =====================================================> // 回到原始的文件中,解释执行..... if (dex_files.empty()) { if (oat_file_assistant.HasOriginalDexFiles()) { // ==============> 所以我们回到了原始的dexfile if (Runtime::Current()->IsDexFileFallbackEnabled()) { static constexpr bool kVerifyChecksum = true; // ===================> 这个时候,使用一个 dexOpen 来加载,这样就根刚才一样,回到了刚才的那个逻辑之中.. if (!DexFile::Open( dex_location, // 参数1 dex_location, // 参数2 kVerifyChecksum, // 参数3 /*out*/ &error_msg, // 参数4 &dex_files) // 参数5 ) { LOG(WARNING) << error_msg; error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) + " because: " + error_msg); } } else { error_msgs->push_back("Fallback mode disabled, skipping dex files."); } } else { error_msgs->push_back("No original dex files found for dex location " + std::string(dex_location)); } } return dex_files; } ``` ### 12. `DexFile::Open` - **位置**:`art/runtime/dex_file.cc` - ```cpp bool DexFile::Open(const char* filename, const std::string& location, bool verify_checksum, std::string* error_msg, std::vector<std::unique_ptr<const DexFile>>* dex_files) { ScopedTrace trace(std::string("Open dex file ") + std::string(location)); DCHECK(dex_files != nullptr) << "DexFile::Open: out-param is nullptr"; uint32_t magic; File fd = OpenAndReadMagic(filename, &magic, error_msg); // 这里的脱壳还不是完整的时机================> DEmo On PPT if (fd.Fd() == -1) { DCHECK(!error_msg->empty()); return false; } if (IsZipMagic(magic)) { return DexFile::OpenZip(fd.Release(), location, verify_checksum, error_msg, dex_files); } if (IsDexMagic(magic)) { std::unique_ptr<const DexFile> dex_file(DexFile::OpenFile(fd.Release(), // ====> 那么在这个地方脱壳,就是比较的适合,所以这里的花, 就是比较好的一点...OpenFile(fd location, /* verify */ true, verify_checksum, error_msg)); if (dex_file.get() != nullptr) { dex_files->push_back(std::move(dex_file)); return true; } else { return false; } } *error_msg = StringPrintf("Expected valid zip or dex file: '%s'", filename); return false; } ``` ### 13. `DexFile::OpenFile` - **位置**:`art/runtime/dex_file.cc` - ```cpp std::unique_ptr<const DexFile> DexFile::OpenFile(int fd, // 打开文件操作,从给定的文件描述符 fd 加载数据 const std::string& location, bool verify, bool verify_checksum, std::string* error_msg) { ScopedTrace trace(std::string("Open dex file ") + std::string(location)); // 追踪打开 dex 文件的操作 CHECK(!location.empty()); // 检查文件路径是否为空 std::unique_ptr<MemMap> map; // 用于映射文件内存 { File delayed_close(fd, /* check_usage */ false); // 延迟关闭文件描述符 struct stat sbuf; // 用于存储文件状态信息 memset(&sbuf, 0, sizeof(sbuf)); // 清空 sbuf if (fstat(fd, &sbuf) == -1) { // 获取文件状态,失败则返回错误信息 *error_msg = StringPrintf("DexFile: fstat '%s' failed: %s", location.c_str(), strerror(errno)); return nullptr; } if (S_ISDIR(sbuf.st_mode)) { // 如果是目录,返回错误信息 *error_msg = StringPrintf("Attempt to mmap directory '%s'", location.c_str()); return nullptr; } size_t length = sbuf.st_size; // 获取文件大小 map.reset(MemMap::MapFile(length, PROT_READ, MAP_PRIVATE, fd, 0, /*low_4gb*/false, location.c_str(), error_msg)); // 映射文件到内存 if (map == nullptr) { // 如果内存映射失败,返回错误信息 DCHECK(!error_msg->empty()); return nullptr; } } if (map->Size() < sizeof(DexFile::Header)) { // 文件大小小于 DexFile 头部,返回错误 *error_msg = StringPrintf( "DexFile: failed to open dex file '%s' that is too short to have a header", location.c_str()); return nullptr; } const Header* dex_header = reinterpret_cast<const Header*>(map->Begin()); // 读取文件头部 std::unique_ptr<DexFile> dex_file = OpenCommon(map->Begin(), // ========================> 可以看到, 又回到到了 Opencommon上面... map->Size(), location, dex_header->checksum_, kNoOatDexFile, verify, verify_checksum, error_msg); // 打开 dex 文件 if (dex_file != nullptr) { // 如果文件打开成功,保存内存映射 dex_file->mem_map_ = std::move(map); //========================================> } return dex_file; // 返回 DexFile 对象 } ``` ### 14. `DexFile::OpenCommon` - **位置**:`art/runtime/dex_file.cc` - ```cpp std::unique_ptr<DexFile> DexFile::OpenCommon(const uint8_t* base, // 定义 OpenCommon 函数,用于打开 Dex 文件 // 该函数用于加载和验证 dex 文件 size_t size, // dex 文件的大小 // 需要指定文件的大小 const std::string& location, // dex 文件的路径 // 用于指定文件的位置 uint32_t location_checksum, // dex 文件路径的校验和 // 可能用于验证路径的完整性 const OatDexFile* oat_dex_file, // 可选的 oat 文件指针 // 用于包含 oat 文件的信息 bool verify, // 是否需要验证文件的有效性 // 控制是否执行验证 bool verify_checksum, // 是否验证校验和 // 控制是否进行校验和验证 std::string* error_msg, // 错误消息输出 // 用于返回错误信息 VerifyResult* verify_result) { // 验证结果输出 // 用于返回验证的结果 if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifyNotAttempted; // 设置验证结果为未尝试验证 // 如果没有进行验证,返回初始状态 } int pid = getpid(); // get the pid number to ceate_DEX Name char dexfilepath[100] = {0}; // get the Dex Path to know where it is sprintf(dexfilepath,"/sdcard/size_%d_pid_%d_DexFile.dex",(int)size,pid); // xunlie , To wirte the Name of DEx in SDCARD/ // int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666); if(fd>0){ int number = write(fd,base,size); // write byte stream to Disk. if(number>0){ } close(fd); } std::unique_ptr<DexFile> dex_file(new DexFile(base, // 创建 DexFile 对象 // 尝试用给定参数创建 DexFile 实例 =====================> => 第二案例, 内部分析了又调用了, new DexFile() size, // dex 文件大小 location, // dex 文件路径 location_checksum, // dex 文件路径校验和 oat_dex_file)); // oat 文件指针 if (dex_file == nullptr) { // 如果 dex_file 创建失败 *error_msg = StringPrintf("Failed to open dex file '%s' from memory: %s", location.c_str(), // 设置错误消息 // 提供详细的错误信息,帮助定位问题 error_msg->c_str()); // 包括错误内容 return nullptr; // 返回空指针,表示打开失败 } if (!dex_file->Init(error_msg)) { // 如果初始化 DexFile 失败 dex_file.reset(); // 重置 dex_file 指针,释放资源 return nullptr; // 返回空指针,表示初始化失败 } if (verify && !DexFileVerifier::Verify(dex_file.get(), // 如果需要验证且验证失败 dex_file->Begin(), // 获取文件开始位置 dex_file->Size(), // 获取文件大小 location.c_str(), // dex 文件路径 verify_checksum, // 是否验证校验和 error_msg)) { // 错误消息输出 if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifyFailed; // 设置验证结果为验证失败 } return nullptr; // 返回空指针,表示验证失败 } if (verify_result != nullptr) { // 如果 verify_result 不是空指针 *verify_result = VerifyResult::kVerifySucceeded; // 设置验证结果为验证成功 } return dex_file; // 返回创建并验证成功的 dex_file } ``` ### 15. `DexFile::DexFile`构造函数 - **位置**:`art/runtime/dex_file.cc` ```cpp DexFile::DexFile(const uint8_t* base, // 构造函数的参数 base,指向 dex 文件的内存开始位置 size_t size, // 构造函数的参数 size,表示 dex 文件的大小 const std::string& location, // 构造函数的参数 location,表示文件位置 uint32_t location_checksum, // 构造函数的参数 location_checksum,表示文件位置的校验和 const OatDexFile* oat_dex_file) // 构造函数的参数 oat_dex_file,表示与此 dex 文件相关的 Oat 文件 : begin_(base), // 初始化 begin_,将 base 赋值给 begin_,表示 dex 文件的起始位置 size_(size), // 初始化 size_,将 size 赋值给 size_,表示 dex 文件的大小 location_(location), // 初始化 location_,将 location 赋值给 location_,表示文件的路径 location_checksum_(location_checksum), // 初始化 location_checksum_,将 location_checksum 赋值给 location_checksum_ header_(reinterpret_cast<const Header*>(base)), // 初始化 header_,通过 reinterpret_cast 将 base 转换为 Header 类型指针 string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)), // 初始化 string_ids_,通过 base 加上偏移量 header_->string_ids_off_ 获取 StringId 类型指针 type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)), // 初始化 type_ids_,通过 base 加上偏移量 header_->type_ids_off_ 获取 TypeId 类型指针 field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)), // 初始化 field_ids_,通过 base 加上偏移量 header_->field_ids_off_ 获取 FieldId 类型指针 method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)), // 初始化 method_ids_,通过 base 加上偏移量 header_->method_ids_off_ 获取 MethodId 类型指针 proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)), // 初始化 proto_ids_,通过 base 加上偏移量 header_->proto_ids_off_ 获取 ProtoId 类型指针 class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)), // 初始化 class_defs_,通过 base 加上偏移量 header_->class_defs_off_ 获取 ClassDef 类型指针 method_handles_(nullptr), // 初始化 method_handles_ 为 nullptr,表示尚未初始化 num_method_handles_(0), // 初始化 num_method_handles_ 为 0,表示方法句柄的数量为 0 call_site_ids_(nullptr), // 初始化 call_site_ids_ 为 nullptr,表示调用站点 ID 尚未初始化 num_call_site_ids_(0), // 初始化 num_call_site_ids_ 为 0,表示调用站点 ID 的数量为 0 oat_dex_file_(oat_dex_file) { // 初始化 oat_dex_file_,将 oat_dex_file 赋值给 oat_dex_file_ CHECK(begin_ != nullptr) << GetLocation(); // 检查 begin_ 是否为 nullptr,如果为 nullptr,则抛出异常并打印文件位置 // 这样做可以确保文件的有效性,防止后续访问出现空指针错误 CHECK_GT(size_, 0U) << GetLocation(); // 检查 size_ 是否大于 0,如果小于等于 0,则抛出异常并打印文件位置 // 确保文件大小合理,避免传入无效大小 // Check base (=header) alignment. // 检查 base(即 header)是否对齐 // Must be 4-byte aligned to avoid undefined behavior when accessing // any of the sections via a pointer. // 必须是 4 字节对齐,否则通过指针访问任何部分时可能会导致未定义行为 CHECK_ALIGNED(begin_, alignof(Header)); // 检查 begin_ 是否按 Header 类型的对齐方式对齐 // 通过对齐检查确保内存访问安全,防止由于不对齐导致的异常或性能问题 int pid = getpid(); // get the pid number to ceate_DEX Name char dexfilepath[100] = {0}; // get the Dex Path to know where it is sprintf(dexfilepath,"/sdcard/size_%d_pid_%d_DexFile.dex",(int)size,pid); // xunlie , To wirte the Name of DEx in SDCARD/ 这里是明显的自定义 代码段,自动写入dex到 sd卡. //int fd = open(dexfilepath,0 CREAT|0 RDWR,666); // byte Stream input int fd = open(dexfilepath, O_CREAT | O_RDWR, 0666); if(fd>0){ int number = write(fd,base,size); // write byte stream to Disk. if(number>0){ } close(fd); } InitializeSectionsFromMapList(); // 调用 InitializeSectionsFromMapList 初始化各个部分 // 初始化部分结构,以确保后续可以正确访问文件的各个部分 } ``` ------ ## ???? 总结 通过上述调用链,`DexClassLoader` 实现了从 `.jar` 或 `.apk` 文件中加载 `.dex` 文件的功能,支持动态加载未安装的应用程序代码。这一机制在插件化、热修复等场景中具有重要意义。 严重参考: https://bbs.kanxue.com/thread-281969.htm [mb_edqxbbqv](https://bbs.kanxue.com/user-home-978849.htm) 2024年5月出版 https://www.kanxue.com/book-leaflet-83.htm 陈老师 <<沙箱脱壳机的核心原理>> 2021年3月出版 <mark class="encrypted">643K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6U0K9r3q4@1k6%4m8@1i4K6u0W2j5$3!0E0</mark> 课件编排,概念说明.
冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于
2025-6-11 21:48 被calleng编辑 ,原因:
#源码框架
上传的附件:
kanxue_share_02.zip
(20.92kb,25次下载)
收藏
・
2
点赞
・
2
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
X66iaM
谢谢你的细致分析,受益匪浅!
2026-3-14 23:31
安卓逆向test
非常支持你的观点!
2025-6-10 21:12
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
1
)
Red
雪 币:
308
活跃值:
(690)
能力值:
( LV2,RANK:10 )
在线值:
发帖
2
回帖
2
粉丝
12
关注
私信
Red
2
楼
大佬讲的很详细,感谢,但是链接好像没了
2026-8-4 10:01
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
calleng
48
发帖
152
回帖
10
RANK
关注
私信
他的文章
[原创][js逆向] HmacSHA512例子-08月19号-搜索key定位,xhr常规定位,校验请求
1237
[原创动画][js逆向][追踪SHA1的标准算法2026-08-17]
235
[原创动画][js逆向][一个 md5 标准案例-抠js+py调用]
308
[原创动画][js逆向][一个 md5 标准算法的案例-xhr常规跟栈]
300
[原创动画][js逆向][浮现图灵在2024年的一个 md5 标准算法的案例]
216
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
谁下载
×
huangyalei
马来
xianhuimin
constant
绿雪羚羊
Red
mb_wpitiize
mb_tewbhakr
npc0vo
haaYv
veluboii
Metzkerson
mb_qucmgawb
mb_kujsrqqv
mb_cebxltxx
mb_lthgjpwj
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部