{"id":28568607,"url":"https://github.com/cyrus-studio/frida_dex_dump","last_synced_at":"2025-06-10T16:30:31.612Z","repository":{"id":291422402,"uuid":"977549405","full_name":"CYRUS-STUDIO/frida_dex_dump","owner":"CYRUS-STUDIO","description":"基于 frida 的 dex 脱壳工具（A Frida-based Dex unpacking tool）","archived":false,"fork":false,"pushed_at":"2025-06-08T11:43:57.000Z","size":21,"stargazers_count":14,"open_issues_count":0,"forks_count":8,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-08T12:30:57.466Z","etag":null,"topics":["dex","dexdump","dump","frida","unpack","unpacker","unpacking"],"latest_commit_sha":null,"homepage":"https://cyrus-studio.github.io/blog/posts/art-%E4%B8%8B-dex-%E5%8A%A0%E8%BD%BD%E6%B5%81%E7%A8%8B%E6%BA%90%E7%A0%81%E5%88%86%E6%9E%90-%E5%92%8C-%E9%80%9A%E7%94%A8%E8%84%B1%E5%A3%B3%E7%82%B9/","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/CYRUS-STUDIO.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2025-05-04T13:14:04.000Z","updated_at":"2025-06-08T11:44:00.000Z","dependencies_parsed_at":"2025-05-04T15:42:02.952Z","dependency_job_id":null,"html_url":"https://github.com/CYRUS-STUDIO/frida_dex_dump","commit_stats":null,"previous_names":["cyrus-studio/frida_dex_dump"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_dex_dump","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_dex_dump/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_dex_dump/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_dex_dump/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/CYRUS-STUDIO","download_url":"https://codeload.github.com/CYRUS-STUDIO/frida_dex_dump/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/CYRUS-STUDIO%2Ffrida_dex_dump/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":259109925,"owners_count":22806623,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dex","dexdump","dump","frida","unpack","unpacker","unpacking"],"created_at":"2025-06-10T16:30:30.927Z","updated_at":"2025-06-10T16:30:31.598Z","avatar_url":"https://github.com/CYRUS-STUDIO.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003e 版权归作者所有，如有转发，请注明文章出处：\u003chttps://cyrus-studio.github.io/blog/\u003e\n\n# **DEF CON**\n\n\n\nDEF CON 是全球最大的计算机安全会议之一（极客的奥斯卡），自1993年6月起，每年在美国内华达州的拉斯维加斯举办。\n\n\n\n官网：[https://media.defcon.org/](https://media.defcon.org/)，DEF CON 黑客大会官方的媒体存档站点，提供历年 DEF CON 大会的公开演讲、幻灯片、视频、音频、代码示例和其他相关资源的免费下载。\n\n\n\n在 DEF CON 25（2017 年）上，Check Point 的安全研究员 Slava Makkaveev 和 Avi Bashan 发表了题为《Unboxing Android: Everything You Wanted to Know About Android Packers》的演讲，深入探讨了 Android 应用程序中的加壳技术及其安全影响。\n\n\n\n报告文件地址：\n\n[https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/DEF%20CON%2025%20-%20Slava-Makkaveev-and-Avi-Bashan-Unboxing-Android.pdf](https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/DEF%20CON%2025%20-%20Slava-Makkaveev-and-Avi-Bashan-Unboxing-Android.pdf)\n\n\n\n对于国内加壳厂商也有分析\n\n\n\n![word/media/image1.png](https://gitee.com/cyrus-studio/images/raw/master/85777d44ae42a2c7de18295d01712c01.png)\n\n\nDEF 的安全研究员选择的两个脱壳点：art::OpenAndReadMagic 和 DexFile::DexFile\n\n\n\n![word/media/image2.png](https://gitee.com/cyrus-studio/images/raw/master/26614f34fc11c05f376afdac726283da.png)\n\n\n# **Unboxing Android**\n\n\n\n在 DEF CON 25 (2017) 上，Avi Bashan 和 Slava Makkaveev 提出过一种非常实用的 Android 加壳脱壳技术：\n\n\n\n通过修改 DexFile::DexFile() 构造函数和 OpenAndReadMagic() 方法，可以在应用运行时，拦截 DEX 文件加载过程，从而拿到已经解密后的内存数据，完成脱壳。\n\n\n\n## **1. DexFile::DexFile 构造函数**\n\n\n\n可以看到 DexFile::DexFile() 的构造函数参数里包含了：\n\n- const uint8_t* base —— DEX 在内存中的起始地址\n\n- size_t size —— DEX 的内存大小\n\n```\nDexFile::DexFile(const uint8_t* base,\n                 size_t size,\n                 const uint8_t* data_begin,\n                 size_t data_size,\n                 const std::string\u0026 location,\n                 uint32_t location_checksum,\n                 const OatDexFile* oat_dex_file,\n                 std::unique_ptr\u003cDexFileContainer\u003e container,\n                 bool is_compact_dex)\n    : begin_(base),\n      size_(size),\n      data_begin_(data_begin),\n      data_size_(data_size),\n      location_(location),\n      location_checksum_(location_checksum),\n      header_(reinterpret_cast\u003cconst Header*\u003e(base)),\n      string_ids_(reinterpret_cast\u003cconst StringId*\u003e(base + header_-\u003estring_ids_off_)),\n      type_ids_(reinterpret_cast\u003cconst TypeId*\u003e(base + header_-\u003etype_ids_off_)),\n      field_ids_(reinterpret_cast\u003cconst FieldId*\u003e(base + header_-\u003efield_ids_off_)),\n      method_ids_(reinterpret_cast\u003cconst MethodId*\u003e(base + header_-\u003emethod_ids_off_)),\n      proto_ids_(reinterpret_cast\u003cconst ProtoId*\u003e(base + header_-\u003eproto_ids_off_)),\n      class_defs_(reinterpret_cast\u003cconst ClassDef*\u003e(base + header_-\u003eclass_defs_off_)),\n      method_handles_(nullptr),\n      num_method_handles_(0),\n      call_site_ids_(nullptr),\n      num_call_site_ids_(0),\n      hiddenapi_class_data_(nullptr),\n      oat_dex_file_(oat_dex_file),\n      container_(std::move(container)),\n      is_compact_dex_(is_compact_dex),\n      hiddenapi_domain_(hiddenapi::Domain::kApplication) {\n  CHECK(begin_ != nullptr) \u003c\u003c GetLocation();\n  CHECK_GT(size_, 0U) \u003c\u003c GetLocation();\n  // Check base (=header) alignment.\n  // Must be 4-byte aligned to avoid undefined behavior when accessing\n  // any of the sections via a pointer.\n  CHECK_ALIGNED(begin_, alignof(Header));\n\n  InitializeSectionsFromMapList();\n}\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96)\n\n\n\n\n\n![word/media/image3.png](https://gitee.com/cyrus-studio/images/raw/master/19c80e4b1d1d1e8ba82186864d060e97.png)\n\n\n插入脱壳代码示例\n\n```\n// 打印当前 DEX 文件的位置\nLOG(WARNING) \u003c\u003c \"Dex File: Filename: \" \u003c\u003c location;\n\n// 判断这个 DEX 是不是从 APP 自身私有目录 加载的。\n// 因为系统自己的 framework、boot.oat 里的 DEX 都不是加壳 DEX，只想 dump 应用自己的 DEX。\nif (location.find(\"/data/data/\") != std::string::npos) {\n    LOG(WARNING) \u003c\u003c \"Dex File: OAT file unpacking launched\";\n\n    // 创建一个新的文件，比如 /data/data/包名/xxx.dex__unpacked_oat。\n    std::ofstream dst(location + \"__unpacked_oat\", std::ios::binary);\n    // 把内存里的 DEX 数据完整写入磁盘。\n    dst.write(reinterpret_cast\u003cconst char*\u003e(base), size);\n    // 保存文件，完成脱壳。\n    dst.close();\n} else {\n    LOG(WARNING) \u003c\u003c \"Dex File: OAT file unpacking not launched\";\n}\n```\n\n\n## **2. DexFile::OpenAndReadMagic()**\n\n\n\n这是辅助检查 DEX 文件头的函数。\n\n```\nFile OpenAndReadMagic(const char* filename, uint32_t* magic, std::string* error_msg) {\n  CHECK(magic != nullptr);\n  File fd(filename, O_RDONLY, /* check_usage= */ false);\n  if (fd.Fd() == -1) {\n    *error_msg = StringPrintf(\"Unable to open '%s' : %s\", filename, strerror(errno));\n    return File();\n  }\n  if (!ReadMagicAndReset(fd.Fd(), magic, error_msg)) {\n    StringPrintf(\"Error in reading magic from file %s: %s\", filename, error_msg-\u003ec_str());\n    return File();\n  }\n  return fd;\n}\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libartbase/base/file_magic.cc;l=32](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libartbase/base/file_magic.cc;l=32)\n\n\n\n\n\n![word/media/image4.png](https://gitee.com/cyrus-studio/images/raw/master/c4f07ae80a53cb21b2ed7bb7b2ee3f58.png)\n\n\n插入脱壳代码示例\n\n```\nstruct stat st;  // 用于获取文件大小等信息\n\n// 打印当前正在处理的文件路径，便于调试和观察加载的 DEX 来源\nLOG(WARNING) \u003c\u003c \"File_magic: Filename: \" \u003c\u003c filename;\n\n// 只处理 /data/data 路径下的文件（即应用私有目录中的 dex 文件）\n// 这样可以避免处理系统 DEX，提高效率和准确性\nif (strstr(filename, \"/data/data\") != NULL) {\n  LOG(WARNING) \u003c\u003c \"File_magic: DEX file unpacking launched\";\n\n  // 构造输出文件路径，加上 \"__unpacked_dex\" 后缀\n  char* fn_out = new char[PATH_MAX];\n  strcpy(fn_out, filename);\n  strcat(fn_out, \"__unpacked_dex\");\n\n  // 创建输出文件，设置权限：用户可读写、用户组可读、其他人可读\n  int fd_out = open(fn_out, O_WRONLY | O_CREAT | O_EXCL,\n                    S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);\n\n  // 如果获取原始 dex 文件信息成功（用于获取文件大小）\n  if (!fstat(fd.get(), \u0026st)) {\n    // 使用 mmap 将整个 dex 文件映射到内存中\n    char* addr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd.get(), 0);\n\n    // 将内存中的内容写入到新文件，完成磁盘级别的 dex dump\n    int ret = write(fd_out, addr, st.st_size);\n\n    // 可选防优化代码（保证 ret 被使用，防止编译器优化）\n    ret += 1;\n\n    // 解除映射，释放内存\n    munmap(addr, st.st_size);\n  }\n\n  // 关闭输出文件，清理路径内存\n  close(fd_out);\n  delete[] fn_out;\n\n} else {\n  // 如果不是应用私有路径下的文件，跳过处理\n  LOG(WARNING) \u003c\u003c \"File_magic: DEX file unpacking not launched\";\n}\n```\n\n\n# **ART 下脱壳原理**\n\n\n\nART 下常见的两个 dex 加载器：InMemoryDexClassLoader 和 DexClassLoader\n\n\n\n## **InMemoryDexClassLoader 源码分析**\n\n\n\nInMemoryDexClassLoader 是 Android 8.0（API 级别 26）引入的一个类，用于动态加载内存中的 Dex。\n\n\n\n调用示例：\n\n```\n// 假设 dexBytes 是你的 DEX 文件内容（可以通过解密获得）\nByteBuffer buffer = ByteBuffer.wrap(dexBytes);\n\n// 创建 InMemoryDexClassLoader\nClassLoader loader = new InMemoryDexClassLoader(buffer, ClassLoader.getSystemClassLoader());\n\n// 通过反射加载类并调用方法\nClass\u003c?\u003e clazz = loader.loadClass(\"com.example.MyHiddenClass\");\nMethod m = clazz.getDeclaredMethod(\"secretMethod\");\nm.invoke(null);\n```\n\n\nInMemoryDexClassLoader 支持加载 内存中 一个或多个 Dex。源码如下：\n\n\n\n![word/media/image5.png](https://gitee.com/cyrus-studio/images/raw/master/882c2733141420f573b56a73a7166520.png)\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java)\n\n\n\n### **openInMemoryDexFilesNative**\n\n\n\nDex 加载过程如下，最终调用到 native 方法 openInMemoryDexFilesNative\n\n```\n InMemoryDexClassLoader(ByteBuffer[] dexBuffers, String librarySearchPath, ClassLoader parent)\n └── BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent)\n      └── DexPathList.initByteBufferDexPath(ByteBuffer[] dexFiles)\n           └── DexFile(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements)\n                └── DexFile.openInMemoryDexFiles(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements)\n                     └── DexFile.openInMemoryDexFilesNative(ByteBuffer[] bufs, byte[][] arrays, int[] starts, int[] ends, ClassLoader loader, DexPathList.Element[] elements)\n                         └── DexFile_openInMemoryDexFilesNative(JNIEnv* env, jclass, jobjectArray buffers, jobjectArray arrays, jintArray jstarts, jintArray jends, jobject class_loader, jobjectArray dex_elements)\n```\n[https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=134](https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=134)\n\n\n\nDexFile_openInMemoryDexFilesNative 中 调用 OpenDexFilesFromOat 方法 加载 Dex ：\n\n```\nstatic jobject DexFile_openInMemoryDexFilesNative(JNIEnv* env,\n                                                  jclass,\n                                                  jobjectArray buffers,\n                                                  jobjectArray arrays,\n                                                  jintArray jstarts,\n                                                  jintArray jends,\n                                                  jobject class_loader,\n                                                  jobjectArray dex_elements) {\n  jsize buffers_length = env-\u003eGetArrayLength(buffers);\n  CHECK_EQ(buffers_length, env-\u003eGetArrayLength(arrays));\n  CHECK_EQ(buffers_length, env-\u003eGetArrayLength(jstarts));\n  CHECK_EQ(buffers_length, env-\u003eGetArrayLength(jends));\n\n  ScopedIntArrayAccessor starts(env, jstarts);\n  ScopedIntArrayAccessor ends(env, jends);\n\n  // Allocate memory for dex files and copy data from ByteBuffers.\n  std::vector\u003cMemMap\u003e dex_mem_maps;\n  dex_mem_maps.reserve(buffers_length);\n  for (jsize i = 0; i \u003c buffers_length; ++i) {\n    jobject buffer = env-\u003eGetObjectArrayElement(buffers, i);\n    jbyteArray array = reinterpret_cast\u003cjbyteArray\u003e(env-\u003eGetObjectArrayElement(arrays, i));\n    jint start = starts.Get(i);\n    jint end = ends.Get(i);\n\n    MemMap dex_data = AllocateDexMemoryMap(env, start, end);\n    if (!dex_data.IsValid()) {\n      DCHECK(Thread::Current()-\u003eIsExceptionPending());\n      return nullptr;\n    }\n\n    if (array == nullptr) {\n      // Direct ByteBuffer\n      uint8_t* base_address = reinterpret_cast\u003cuint8_t*\u003e(env-\u003eGetDirectBufferAddress(buffer));\n      if (base_address == nullptr) {\n        ScopedObjectAccess soa(env);\n        ThrowWrappedIOException(\"dexFileBuffer not direct\");\n        return nullptr;\n      }\n      size_t length = static_cast\u003csize_t\u003e(end - start);\n      memcpy(dex_data.Begin(), base_address + start, length);\n    } else {\n      // ByteBuffer backed by a byte array\n      jbyte* destination = reinterpret_cast\u003cjbyte*\u003e(dex_data.Begin());\n      env-\u003eGetByteArrayRegion(array, start, end - start, destination);\n    }\n\n    dex_mem_maps.push_back(std::move(dex_data));\n  }\n\n  // Hand MemMaps over to OatFileManager to open the dex files and potentially\n  // create a backing OatFile instance from an anonymous vdex.\n  std::vector\u003cstd::string\u003e error_msgs;\n  const OatFile* oat_file = nullptr;\n  std::vector\u003cstd::unique_ptr\u003cconst DexFile\u003e\u003e dex_files =\n      Runtime::Current()-\u003eGetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps),\n                                                                  class_loader,\n                                                                  dex_elements,\n                                                                  /*out*/ \u0026oat_file,\n                                                                  /*out*/ \u0026error_msgs);\n  return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);\n}\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/native/dalvik_system_DexFile.cc;l=240](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/native/dalvik_system_DexFile.cc;l=240)\n\n\n\n### **OpenDexFilesFromOat**\n\n\n\nOpenDexFilesFromOat 调用 OpenDexFilesFromOat_Impl 加载 Dex\n\n```\nstd::vector\u003cstd::unique_ptr\u003cconst DexFile\u003e\u003e OatFileManager::OpenDexFilesFromOat_Impl(\n    std::vector\u003cMemMap\u003e\u0026\u0026 dex_mem_maps,\n    jobject class_loader,\n    jobjectArray dex_elements,\n    const OatFile** out_oat_file,\n    std::vector\u003cstd::string\u003e* error_msgs) {\n  ScopedTrace trace(__FUNCTION__);\n  std::string error_msg;\n  DCHECK(error_msgs != nullptr);\n\n  // [1] 提取 Dex Header，用于后续校验 checksum、生成路径等\n  const std::vector\u003cconst DexFile::Header*\u003e dex_headers = GetDexFileHeaders(dex_mem_maps);\n\n  // [2] 生成临时匿名 dex/vdex 文件路径，获取 checksum 和路径\n  uint32_t location_checksum;\n  std::string dex_location;\n  std::string vdex_path;\n  bool has_vdex = OatFileAssistant::AnonymousDexVdexLocation(\n      dex_headers, kRuntimeISA, \u0026location_checksum, \u0026dex_location, \u0026vdex_path);\n\n  // [3] 尝试打开 vdex 文件，并检查其中的 dex checksum 是否一致\n  std::unique_ptr\u003cVdexFile\u003e vdex_file = nullptr;\n  if (has_vdex \u0026\u0026 OS::FileExists(vdex_path.c_str())) {\n    vdex_file = VdexFile::Open(vdex_path, /*writable=*/false, /*low_4gb=*/false,\n                               /*unquicken=*/false, \u0026error_msg);\n    if (vdex_file == nullptr) {\n      LOG(WARNING) \u003c\u003c \"Failed to open vdex \" \u003c\u003c vdex_path \u003c\u003c \": \" \u003c\u003c error_msg;\n    } else if (!vdex_file-\u003eMatchesDexFileChecksums(dex_headers)) {\n      LOG(WARNING) \u003c\u003c \"Dex checksum mismatch: \" \u003c\u003c vdex_path;\n      vdex_file.reset(nullptr);\n    }\n  }\n\n  // [4] 加载内存中的 dex。若存在 vdex 且校验成功，可跳过结构校验\n  std::vector\u003cstd::unique_ptr\u003cconst DexFile\u003e\u003e dex_files;\n  for (size_t i = 0; i \u003c dex_mem_maps.size(); ++i) {\n    static constexpr bool kVerifyChecksum = true;\n    const ArtDexFileLoader dex_file_loader;\n    std::unique_ptr\u003cconst DexFile\u003e dex_file(dex_file_loader.Open(\n        DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()),\n        location_checksum,\n        std::move(dex_mem_maps[i]),\n        /*verify=*/(vdex_file == nullptr) \u0026\u0026 Runtime::Current()-\u003eIsVerificationEnabled(),\n        kVerifyChecksum,\n        \u0026error_msg));\n    if (dex_file != nullptr) {\n      dex::tracking::RegisterDexFile(dex_file.get());  // 注册用于调试追踪\n      dex_files.push_back(std::move(dex_file));\n    } else {\n      error_msgs-\u003epush_back(\"Failed to open dex files from memory: \" + error_msg);\n    }\n  }\n\n  // [5] 若 vdex 不存在、加载失败，或 class_loader 为空，直接返回 dex_files\n  if (vdex_file == nullptr || class_loader == nullptr || !error_msgs-\u003eempty()) {\n    return dex_files;\n  }\n\n  // [6] 创建 ClassLoaderContext，确保之后的 oat 加载上下文一致\n  std::unique_ptr\u003cClassLoaderContext\u003e context = ClassLoaderContext::CreateContextForClassLoader(\n      class_loader, dex_elements);\n  if (context == nullptr) {\n    LOG(ERROR) \u003c\u003c \"Could not create class loader context for \" \u003c\u003c vdex_path;\n    return dex_files;\n  }\n  DCHECK(context-\u003eOpenDexFiles(kRuntimeISA, \"\"))\n      \u003c\u003c \"Context created from already opened dex files should not attempt to open again\";\n\n  // [7] 检查 boot class path checksum 和 class loader context 是否匹配\n  if (!vdex_file-\u003eMatchesBootClassPathChecksums() ||\n      !vdex_file-\u003eMatchesClassLoaderContext(*context.get())) {\n    return dex_files;\n  }\n\n  // [8] 从 vdex 创建 OatFile 实例并注册\n  std::unique_ptr\u003cOatFile\u003e oat_file(OatFile::OpenFromVdex(\n      MakeNonOwningPointerVector(dex_files),\n      std::move(vdex_file),\n      dex_location));\n  DCHECK(oat_file != nullptr);\n  VLOG(class_linker) \u003c\u003c \"Registering \" \u003c\u003c oat_file-\u003eGetLocation();\n  *out_oat_file = RegisterOatFile(std::move(oat_file));\n\n  return dex_files;\n}\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=708](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=708)\n\n\n\n在 Android 10 之前，InMemoryDexClassLoader 加载的 DEX 文件不会被编译为 OAT 文件，而是直接在解释模式下执行， 这也是它和 DexClassLoader 的区别。\n\n\n\n从 Android 10 开始，InMemoryDexClassLoader 加载的 DEX 文件也会走 OAT 流程。\n\n\n\nDexFile_openInMemoryDexFilesNative → DexFile::DexFile 调用路径\n\n```\nDexFile_openInMemoryDexFilesNative(...)                    \n└── AllocateDexMemoryMap(...) 创建 dex_mem_maps\n└── Runtime::Current()-\u003eGetOatFileManager().OpenDexFilesFromOat(...) \n    └── OatFileManager::OpenDexFilesFromOat(dex_mem_maps, class_loader, dex_elements, out_oat_file, error_msgs)\n        └── OatFileManager::OpenDexFilesFromOat_Impl(...)\n            └── ArtDexFileLoader::Open(location, location_checksum, map, verify, verify_checksum, error_msg)\n                  └── ArtDexFileLoader::OpenCommon(base, size, ...)\n                        └── DexFileLoader::OpenCommon(base, size, ...)\n                            └── new StandardDexFile(base, location, ...): DexFile(base, location, ...)\n                            └── new CompactDexFile(base, location, ...): DexFile(base, location, ...)\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=708](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=708)\n\n\n\n### **OpenCommon**\n\n\n\n在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump\n\n```\nArtDexFileLoader::Open(location, location_checksum, map, verify, verify_checksum, error_msg)\n  └── ArtDexFileLoader::OpenCommon(base, size, ...)\n        └── DexFileLoader::OpenCommon(base, size, ...)\n            └── new StandardDexFile(base, location, ...): DexFile(base, location, ...)\n            └── new CompactDexFile(base, location, ...): DexFile(base, location, ...)\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/art_dex_file_loader.cc;l=184](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/art_dex_file_loader.cc;l=184)\n\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file_loader.cc;l=316](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file_loader.cc;l=316)\n\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/standard_dex_file.h;l=100](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/standard_dex_file.h;l=100)\n\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96)\n\n\n\n## **DexClassLoader 源码分析**\n\n\n\nDexClassLoader 可以加载任意路径下的 dex，或者 jar、apk、zip 文件（包含classes.dex）。\n\n\n\n源码如下：\n\n\n\n![word/media/image6.png](https://gitee.com/cyrus-studio/images/raw/master/05532442ff68769ffc74626f16ea0ad9.png)\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java)\n\n\n\n### **DexFile_openDexFileNative**\n\n\n\nDexClassLoader 最终是通过 JNI 调用 DexFile_openDexFileNative 来加载 Dex。\n\n\n\n下面是从 Java 到 native 的完整调用路径（以 Android 10 为例）：\n\n```\nDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)\n   ↓\nBaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent)\n   ↓\nDexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted)\n   ↓\nDexPathList.makeDexElements(...)\n   ↓\nnew DexFile(file, loader, elements)\n   ↓\nDexFile.openDexFile(fileName, null, 0, loader, elements)\n   ↓\nDexFile.openDexFileNative(sourceFile, outputFile, flags, loader, elements)\n   ↓\nDexFile_openDexFileNative(...) （native 层）\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=440](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=440)\n\n\n\nDexFile_openDexFileNative 方法中调用 OpenDexFilesFromOat 方法生成 OAT 文件：\n\n```\nstatic jobject DexFile_openDexFileNative(JNIEnv* env,\n                                         jclass,\n                                         jstring javaSourceName,\n                                         jstring javaOutputName ATTRIBUTE_UNUSED,\n                                         jint flags ATTRIBUTE_UNUSED,\n                                         jobject class_loader,\n                                         jobjectArray dex_elements) {\n  ScopedUtfChars sourceName(env, javaSourceName);\n  if (sourceName.c_str() == nullptr) {\n    return nullptr;\n  }\n\n  std::vector\u003cstd::string\u003e error_msgs;\n  const OatFile* oat_file = nullptr;\n  std::vector\u003cstd::unique_ptr\u003cconst DexFile\u003e\u003e dex_files =\n      Runtime::Current()-\u003eGetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),\n                                                                  class_loader,\n                                                                  dex_elements,\n                                                                  /*out*/ \u0026oat_file,\n                                                                  /*out*/ \u0026error_msgs);\n  return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);\n}\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/native/dalvik_system_DexFile.cc](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/native/dalvik_system_DexFile.cc)\n\n\n\n### **OpenDexFilesFromOat**\n\n\n\n和 InMemoryDexClassLoader 不同的是：这里传入的参数不是 MemMap，而是 const char* dex_location。\n\n\n\nOpenDexFilesFromOat 方法源码如下：\n\n```\nstd::vector\u003cstd::unique_ptr\u003cconst DexFile\u003e\u003e OatFileManager::OpenDexFilesFromOat(\n    const char* dex_location,\n    jobject class_loader,\n    jobjectArray dex_elements,\n    const OatFile** out_oat_file,\n    std::vector\u003cstd::string\u003e* error_msgs) {\n  ScopedTrace trace(__FUNCTION__);\n  CHECK(dex_location != nullptr);\n  CHECK(error_msgs != nullptr);\n\n  // 步骤 1: 确保未持有 mutator_lock，防止阻塞 GC\n  Thread* const self = Thread::Current();\n  Locks::mutator_lock_-\u003eAssertNotHeld(self);\n  Runtime* const runtime = Runtime::Current();\n\n  // 步骤 2: 构造 ClassLoaderContext（可能为空）\n  std::unique_ptr\u003cClassLoaderContext\u003e context;\n  if (class_loader == nullptr) {\n    LOG(WARNING) \u003c\u003c \"Opening an oat file without a class loader. \"\n                 \u003c\u003c \"Are you using the deprecated DexFile APIs?\";\n    context = nullptr;\n  } else {\n    context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);\n  }\n\n  // 步骤 3: 构建 OatFileAssistant，用于操作 oat 和 dex 文件\n  OatFileAssistant oat_file_assistant(dex_location,\n                                      kRuntimeISA,\n                                      !runtime-\u003eIsAotCompiler(),\n                                      only_use_system_oat_files_);\n\n  // 步骤 4: 获取磁盘上最优的 OAT 文件\n  std::unique_ptr\u003cconst OatFile\u003e oat_file(oat_file_assistant.GetBestOatFile().release());\n  VLOG(oat) \u003c\u003c \"OatFileAssistant(\" \u003c\u003c dex_location \u003c\u003c \").GetBestOatFile()=\"\n            \u003c\u003c reinterpret_cast\u003cuintptr_t\u003e(oat_file.get())\n            \u003c\u003c \" (executable=\" \u003c\u003c (oat_file != nullptr ? oat_file-\u003eIsExecutable() : false) \u003c\u003c \")\";\n\n  const OatFile* source_oat_file = nullptr;\n  CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions;\n  std::string error_msg;\n\n  // 步骤 5: 进行 collision 检查决定是否接受这个 oat 文件\n  if ((class_loader != nullptr || dex_elements != nullptr) \u0026\u0026 oat_file != nullptr) {\n    check_collision_result = CheckCollision(oat_file.get(), context.get(), \u0026error_msg);\n    bool accept_oat_file = AcceptOatFile(check_collision_result);\n\n    // 检查结果为 false，判断是否启用 fallback 并记录警告信息\n    if (!accept_oat_file) {\n      if (runtime-\u003eIsDexFileFallbackEnabled()) {\n        if (!oat_file_assistant.HasOriginalDexFiles()) {\n          accept_oat_file = true;\n          LOG(WARNING) \u003c\u003c \"Dex location \" \u003c\u003c dex_location \u003c\u003c \" does not seem to include dex file. \"\n                       \u003c\u003c \"Allow oat file use. This is potentially dangerous.\";\n        } else {\n          LOG(WARNING) \u003c\u003c \"Found duplicate classes, falling back to extracting from APK : \"\n                       \u003c\u003c dex_location;\n          LOG(WARNING) \u003c\u003c \"NOTE: This wastes RAM and hurts startup performance.\";\n        }\n      } else {\n        if (!oat_file_assistant.HasOriginalDexFiles()) {\n          accept_oat_file = true;\n        }\n        LOG(WARNING) \u003c\u003c \"Found duplicate classes, dex-file-fallback disabled, will be failing to \"\n                        \" load classes for \" \u003c\u003c dex_location;\n      }\n\n      LOG(WARNING) \u003c\u003c error_msg;\n    }\n\n    // 步骤 6: 注册 oat 文件到 OatFileManager\n    if (accept_oat_file) {\n      VLOG(class_linker) \u003c\u003c \"Registering \" \u003c\u003c oat_file-\u003eGetLocation();\n      source_oat_file = RegisterOatFile(std::move(oat_file));\n      *out_oat_file = source_oat_file;\n    }\n  }\n\n  std::vector\u003cstd::unique_ptr\u003cconst DexFile\u003e\u003e dex_files;\n\n  // 步骤 7: 从 OAT 文件加载 dex 文件（如果成功加载了 oat）\n  if (source_oat_file != nullptr) {\n    bool added_image_space = false;\n\n    if (source_oat_file-\u003eIsExecutable()) {\n      ScopedTrace app_image_timing(\"AppImage:Loading\");\n\n      std::unique_ptr\u003cgc::space::ImageSpace\u003e image_space;\n      if (ShouldLoadAppImage(check_collision_result,\n                             source_oat_file,\n                             context.get(),\n                             \u0026error_msg)) {\n        image_space = oat_file_assistant.OpenImageSpace(source_oat_file);\n      }\n\n      if (image_space != nullptr) {\n        ScopedObjectAccess soa(self);\n        StackHandleScope\u003c1\u003e hs(self);\n        Handle\u003cmirror::ClassLoader\u003e h_loader(\n            hs.NewHandle(soa.Decode\u003cmirror::ClassLoader\u003e(class_loader)));\n\n        // 步骤 8: 尝试将 image space 添加到堆中\n        if (h_loader != nullptr) {\n          std::string temp_error_msg;\n          {\n            ScopedThreadSuspension sts(self, kSuspended);\n            gc::ScopedGCCriticalSection gcs(self,\n                                            gc::kGcCauseAddRemoveAppImageSpace,\n                                            gc::kCollectorTypeAddRemoveAppImageSpace);\n            ScopedSuspendAll ssa(\"Add image space\");\n            runtime-\u003eGetHeap()-\u003eAddSpace(image_space.get());\n          }\n          {\n            ScopedTrace trace2(StringPrintf(\"Adding image space for location %s\", dex_location));\n            added_image_space = runtime-\u003eGetClassLinker()-\u003eAddImageSpace(image_space.get(),\n                                                                         h_loader,\n                                                                         dex_elements,\n                                                                         dex_location,\n                                                                         \u0026dex_files,\n                                                                         \u0026temp_error_msg);\n          }\n          if (added_image_space) {\n            image_space.release();\n            for (const auto\u0026 dex_file : dex_files) {\n              dex::tracking::RegisterDexFile(dex_file.get());\n            }\n          } else {\n            LOG(INFO) \u003c\u003c \"Failed to add image file \" \u003c\u003c temp_error_msg;\n            dex_files.clear();\n            {\n              ScopedThreadSuspension sts(self, kSuspended);\n              gc::ScopedGCCriticalSection gcs(self,\n                                              gc::kGcCauseAddRemoveAppImageSpace,\n                                              gc::kCollectorTypeAddRemoveAppImageSpace);\n              ScopedSuspendAll ssa(\"Remove image space\");\n              runtime-\u003eGetHeap()-\u003eRemoveSpace(image_space.get());\n            }\n          }\n        }\n      }\n    }\n\n    // 步骤 9: 如果未添加 image space，则从 oat 中手动加载 dex 文件\n    if (!added_image_space) {\n      DCHECK(dex_files.empty());\n      dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);\n\n      for (const auto\u0026 dex_file : dex_files) {\n        dex::tracking::RegisterDexFile(dex_file.get());\n      }\n    }\n\n    // 步骤 10: 检查是否 dex 文件加载失败\n    if (dex_files.empty()) {\n      error_msgs-\u003epush_back(\"Failed to open dex files from \" + source_oat_file-\u003eGetLocation());\n    } else {\n      for (const std::unique_ptr\u003cconst DexFile\u003e\u0026 dex_file : dex_files) {\n        OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad);\n      }\n    }\n  }\n\n  // 步骤 11: OAT 加载失败，尝试从原始 dex 文件 fallback 加载\n  if (dex_files.empty()) {\n    if (oat_file_assistant.HasOriginalDexFiles()) {\n      if (Runtime::Current()-\u003eIsDexFileFallbackEnabled()) {\n        static constexpr bool kVerifyChecksum = true;\n        const ArtDexFileLoader dex_file_loader;\n        if (!dex_file_loader.Open(dex_location,\n                                  dex_location,\n                                  Runtime::Current()-\u003eIsVerificationEnabled(),\n                                  kVerifyChecksum,\n                                  \u0026error_msg,\n                                  \u0026dex_files)) {\n          LOG(WARNING) \u003c\u003c error_msg;\n          error_msgs-\u003epush_back(\"Failed to open dex files from \" + std::string(dex_location)\n                                + \" because: \" + error_msg);\n        }\n      } else {\n        error_msgs-\u003epush_back(\"Fallback mode disabled, skipping dex files.\");\n      }\n    } else {\n      error_msgs-\u003epush_back(\"No original dex files found for dex location \"\n          + std::string(dex_location));\n    }\n  }\n\n  // 步骤 12: JIT 启用时注册 dex 文件\n  if (Runtime::Current()-\u003eGetJit() != nullptr) {\n    ScopedObjectAccess soa(self);\n    Runtime::Current()-\u003eGetJit()-\u003eRegisterDexFiles(\n        dex_files, soa.Decode\u003cmirror::ClassLoader\u003e(class_loader));\n  }\n\n  // 最终返回 dex 文件数组\n  return dex_files;\n}\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=447](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=447)\n\n\n\n### **OpenCommon**\n\n\n\nDexFile_openDexFileNative → DexFile::DexFile 调用路径\n\n```\nDexFile_openDexFileNative(...)  \n└── OatFileManager::OpenDexFilesFromOat(dex_location, class_loader, dex_elements, out_oat_file, error_msgs)\n      └── ArtDexFileLoader::Open(filename, location, verify, verify_checksum, error_msg, dex_files)\n            └── art::OpenAndReadMagic(...)\n            └── ArtDexFileLoader::OpenWithMagic(...)\n            └── ArtDexFileLoader::OpenFile(...)\n                   └── ArtDexFileLoader::OpenCommon(base, size, ...)\n                          └── DexFileLoader::OpenCommon(base, size, ...)\n                                 └── new StandardDexFile(base, location, ...): DexFile(base, location, ...)\n                                 └── new CompactDexFile(base, location, ...): DexFile(base, location, ...)\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/art_dex_file_loader.cc;l=223](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/art_dex_file_loader.cc;l=223)\n\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libartbase/base/file_magic.cc?q=OpenAndReadMagic](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libartbase/base/file_magic.cc?q=OpenAndReadMagic)\n\n\n\n在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump\n\n```\nArtDexFileLoader::Open(filename, location, verify, verify_checksum, error_msg, dex_files)\n   └── ArtDexFileLoader::OpenCommon(base, size, ...)\n          └── DexFileLoader::OpenCommon(base, size, ...)\n                 └── new StandardDexFile(base, location, ...): DexFile(base, location, ...)\n                 └── new CompactDexFile(base, location, ...): DexFile(base, location, ...)\n```\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file_loader.cc;l=316](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file_loader.cc;l=316)\n\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/standard_dex_file.h;l=100](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/standard_dex_file.h;l=100)\n\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96)\n\n\n\n# **通用脱壳点**\n\n\n\n所以无论是 InMemoryDexClassLoader 还是 DexClassLoader 加载 Dex 最终都会走到以下方法：\n\n```\nArtDexFileLoader::OpenCommon(base, size, ...)\n  └── DexFileLoader::OpenCommon(base, size, ...)\n        └── new StandardDexFile(base, location, ...): DexFile(base, location, ...)\n        └── new CompactDexFile(base, location, ...): DexFile(base, location, ...)\n```\n在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump。\n\n\n\n# **OAT 文件**\n\n\n\nAndroid 会在安装应用时，或首次运行时通过 dex2oat 将 .dex 文件转换为 .oat 文件。\n\n\n\nOAT 文件是 Android Runtime（ART）生成的优化后的本地代码文件，其全称是 Optimized Android executable。\n\n\n\nOAT 文件主要用于：\n\n- 加速应用启动时间\n\n- 减少运行时 JIT 编译压力\n\n- 节省运行时的电量和内存资源\n\n\n\n一个 .oat 文件大致包含以下几个部分：\n\n| 部分 | 描述 |\n|--- | ---|\n| Header | 文件头信息，包括版本、校验等 |\n| Dex 文件副本 | 一个或多个原始 .dex 文件的副本 |\n| ELF 可执行体 | 编译后的机器代码，和设备架构相关（ARM/ARM64/x86 等） |\n| VMap Table | 虚拟寄存器映射表，用于调试和异常恢复 |\n| OatMethodData | 每个方法的元数据（偏移、编译类型等） |\n\n\n根据 Android 版本和架构不同，OAT 文件通常存储在以下目录：\n\n```\n/data/app/\u003cpackage\u003e/oat/arm64/base.odex\n/system/framework/boot.oat\n/apex/com.android.art/javalib/\u003c*.oat\u003e\n```\n\n\n# **找不到 OpenCommon**\n\n\n\n使用 Frida list 一下 art 中的函数\n\n\n\nlist_module_functions.js\n\n```\nfunction listAllFunctions(moduleName) {\n    const baseAddr = Module.findBaseAddress(moduleName);\n    if (!baseAddr) {\n        console.error(`[-] ${moduleName} not found.`);\n        return;\n    }\n\n    console.log(`[+] ${moduleName} base address:`, baseAddr);\n\n    const symbols = Module.enumerateSymbolsSync(moduleName);\n    let count = 0;\n\n    for (let sym of symbols) {\n        if (sym.type === 'function') {\n            console.log(`[${count}]`, sym.address, sym.name);\n            count++;\n        }\n    }\n\n    console.log(`[*] Total function symbols found in ${moduleName}:`, count);\n}\n\n// 列出 libart.so 的所有函数\nsetImmediate(function () {\n    listAllFunctions(\"libart.so\");\n});\n```\n\n\n执行脚本\n\n```\nfrida -H 127.0.0.1:1234  -F -l list_module_functions.js -o log.txt\n```\n\n\n发现并没有 OpenCommon（LineageOS 17.1，Android 10）\n\n\n\n![word/media/image7.png](https://gitee.com/cyrus-studio/images/raw/master/2090c5bd30815303cb2c4f2e6fa4bdb7.png)\n\n\n进入 adb shell，执行下面命令得到 APP 的  pid 为16418\n\n```\npidof pidof com.cyrus.example\n```\n\n\n查看该进程加载的 libart.so 在什么位置\n\n```\ncat /proc/16418/maps | grep libart.so\n```\n\n\n输出如下：\n\n```\n7a27617000-7a27744000 r--p 00000000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so\n7a27744000-7a27bcf000 --xp 0012d000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so\n7a27bcf000-7a27bd2000 rw-p 005b8000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so\n7a27bd2000-7a27be3000 r--p 005bb000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so\n```\n\n\n把 libart.so 拉取到本地\n\n```\nadb pull /apex/com.android.runtime/lib64/libart.so\n```\n\n\n使用 IDA 打开 libart.so，确实没有 OpenCommon 函数\n\n\n\n![word/media/image8.png](https://gitee.com/cyrus-studio/images/raw/master/4683658ba6cf3fc6420618d87bce3b62.png)\n\n\n# **找到 OpenCommon**\n\n\n\n编写一个 Frida 脚本，遍历所有模块的符号，筛选出函数名中包含 \"OpenCommon\" 或 \"DexFileLoader\" 的符号，并打印出来（包括模块名、符号名、地址）\n\n\n\nfind_symbols.js\n\n```\n// Frida 脚本：查找所有模块中包含 \"OpenCommon\" 或 \"DexFileLoader\" 的函数符号\nfunction scanModulesForKeywords(keywords) {\n    const modules = Process.enumerateModules();\n    keywords = keywords.map(k =\u003e k.toLowerCase());\n\n    for (const module of modules) {\n        try {\n            const symbols = Module.enumerateSymbols(module.name);\n            for (const symbol of symbols) {\n                if (symbol.type === 'function') {\n                    const lowerName = symbol.name.toLowerCase();\n                    if (keywords.some(k =\u003e lowerName.includes(k))) {\n                        console.log(`[+] ${module.name} -\u003e ${symbol.name} @ ${symbol.address}`);\n                    }\n                }\n            }\n        } catch (e) {\n            // 某些模块无法枚举，忽略\n        }\n    }\n}\n\nsetImmediate(() =\u003e {\n    console.log(\"[*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ...\");\n    scanModulesForKeywords([\"OpenCommon\", \"DexFileLoader\"]);\n    console.log(\"[*] Done.\");\n});\n\n\n// frida -H 127.0.0.1:1234  -F -l find_symbols.js -o log.txt\n```\n\n\n输出如下：\n\n```\n[*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ...\n[+] linker64 -\u003e __dl__ZN3art13DexFileLoaderD2Ev @ 0x7aadeac318\n[+] linker64 -\u003e __dl__ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aadf00370\n[+] linker64 -\u003e __dl__ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x7aadf001c0\n[+] linker64 -\u003e __dl__ZN3art13DexFileLoaderD0Ev @ 0x7aadecaef8\n[+] linker64 -\u003e __dl__ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x7aadf00860\n[+] linker64 -\u003e __dl__ZNK3art13DexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aadf00288\n[+] linker64 -\u003e __dl__ZNK3art13DexFileLoader21OpenOneDexFileFromZipERKNS_13DexZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSC_ @ 0x7aadf01128\n[+] linker64 -\u003e __dl__ZNK3art13DexFileLoader22OpenAllDexFilesFromZipERKNS_13DexZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISJ_EEEENS8_ISM_EEEE @ 0x7aadf00cb8\n[+] linker64 -\u003e __dl__ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aadf002b0\n[+] linker64 -\u003e __dl__ZNK3art13DexFileLoader7OpenAllEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISI_EEEENS7_ISL_EEEE @ 0x7aadf00918\n[+] libart.so -\u003e _ZN3art13DexFileLoader15GetBaseLocationEPKc @ 0x7a277a1c80\n[+] libart.so -\u003e _ZNK3art16ArtDexFileLoader4OpenEPKcRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISG_EEEENS7_ISJ_EEEE @ 0x0\n[+] libart.so -\u003e _ZNK3art16ArtDexFileLoader4OpenEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x0\n[+] libart.so -\u003e _ZN3art13DexFileLoader23GetDexCanonicalLocationEPKc @ 0x0\n[+] libart.so -\u003e _ZN3art13DexFileLoader18IsMultiDexLocationEPKc @ 0x0\n[+] libart.so -\u003e _ZNK3art16ArtDexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x0\n[+] libart.so -\u003e _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x0\n[+] libart.so -\u003e _ZNK3art16ArtDexFileLoader7OpenZipEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x0\n[+] libart.so -\u003e _ZN3art13DexFileLoader12IsMagicValidEPKh @ 0x0\n[+] libart.so -\u003e _ZN3art13DexFileLoader22IsVersionAndMagicValidEPKh @ 0x0\n[+] libart.so -\u003e _ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x0\n[+] libart.so -\u003e _ZNK3art16ArtDexFileLoader4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjONS_6MemMapEbbPS7_ @ 0x0\n[+] libart.so -\u003e _ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x0\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoaderD2Ev @ 0x7aac641380\n[+] libdexfile.so -\u003e _ZN3art16ArtDexFileLoaderD0Ev @ 0x7aac641388\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader15GetBaseLocationEPKc @ 0x7aac649a78\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoaderD0Ev @ 0x7aac641388\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader12IsMagicValidEj @ 0x7aac6494f8\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x7aac649668\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader25GetMultiDexClassesDexNameEm @ 0x7aac649620\n[+] libdexfile.so -\u003e _ZN3art16ArtDexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS_13DexFileLoader12VerifyResultE @ 0x7aac63fc88\n[+] libdexfile.so -\u003e _ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x7aac64a118\n[+] libdexfile.so -\u003e _ZNK3art13DexFileLoader7OpenAllEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISI_EEEENS7_ISL_EEEE @ 0x7aac64a1d0\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader13OpenWithMagicEjiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac640138\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aac63f020\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader21OpenOneDexFileFromZipERKNS_10ZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPSC_PNS_22DexFileLoaderErrorCodeE @ 0x7aac640b60\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader22OpenAllDexFilesFromZipERKNS_10ZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISH_EEEENS8_ISK_EEEE @ 0x7aac6406f0\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader4OpenEPKcRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISG_EEEENS7_ISJ_EEEE @ 0x7aac640058\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aac63fae8\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjONS_6MemMapEbbPS7_ @ 0x7aac63fd10\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader4OpenEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac6403c8\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader7OpenDexEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbbPS7_ @ 0x7aac6405e0\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader7OpenZipEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac640490\n[+] libdexfile.so -\u003e _ZNK3art16ArtDexFileLoader8OpenFileEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbbPS7_ @ 0x7aac63f608\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader12IsMagicValidEPKh @ 0x7aac649570\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader18IsMultiDexLocationEPKc @ 0x7aac649600\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader22IsVersionAndMagicValidEPKh @ 0x7aac6495a8\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader23GetDexCanonicalLocationEPKc @ 0x7aac649730\n[+] libdexfile.so -\u003e _ZNK3art13DexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aac649b40\n[+] libdexfile.so -\u003e _ZNK3art13DexFileLoader21OpenOneDexFileFromZipERKNS_13DexZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSC_ @ 0x7aac64a9e0\n[+] libdexfile.so -\u003e _ZNK3art13DexFileLoader22OpenAllDexFilesFromZipERKNS_13DexZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISJ_EEEENS8_ISM_EEEE @ 0x7aac64a570\n[+] libdexfile.so -\u003e _ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aac649b68\n[+] libprofile.so -\u003e _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x0\n[*] Done.\n```\n\n\n找到 DexFileLoader::OpenCommon 原来在 libdexfile.so\n\n```\n[+] libdexfile.so -\u003e _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28\n```\n所以 art/runtime/dex_file_loader.cc（DexFileLoader::OpenCommon 实现）最终被编译进 libdexfile.so，而不是 libart.so。\n\n\n\n# **OpenCommon 脱壳**\n\n\n\n使用 frida hook libdexfile.so 中的 DexFileLoader::OpenCommom 函数并拿到参数 base、size 和 location，把 dex 从内存中 dump 到 /sdcard/Android/data/pkgName/dump_dex 目录下：\n\n```\nfunction getProcessName() {\n    var openPtr = Module.getExportByName('libc.so', 'open');\n    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);\n\n    var readPtr = Module.getExportByName(\"libc.so\", \"read\");\n    var read = new NativeFunction(readPtr, \"int\", [\"int\", \"pointer\", \"int\"]);\n\n    var closePtr = Module.getExportByName('libc.so', 'close');\n    var close = new NativeFunction(closePtr, 'int', ['int']);\n\n    var path = Memory.allocUtf8String(\"/proc/self/cmdline\");\n    var fd = open(path, 0);\n    if (fd != -1) {\n        var buffer = Memory.alloc(0x1000);\n\n        var result = read(fd, buffer, 0x1000);\n        close(fd);\n        result = ptr(buffer).readCString();\n        return result;\n    }\n\n    return \"-1\";\n}\n\n\nfunction mkdir(path) {\n    var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');\n    var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']);\n\n\n    var opendirPtr = Module.getExportByName('libc.so', 'opendir');\n    var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']);\n\n    var closedirPtr = Module.getExportByName('libc.so', 'closedir');\n    var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']);\n\n    var cPath = Memory.allocUtf8String(path);\n    var dir = opendir(cPath);\n    if (dir != 0) {\n        closedir(dir);\n        return 0;\n    }\n    mkdir(cPath, 755);\n    chmod(path);\n}\n\nfunction chmod(path) {\n    var chmodPtr = Module.getExportByName('libc.so', 'chmod');\n    var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);\n    var cPath = Memory.allocUtf8String(path);\n    chmod(cPath, 755);\n}\n\nfunction readStdString(str) {\n    const isTiny = (str.readU8() \u0026 1) === 0;\n    if (isTiny) {\n        return str.add(1).readUtf8String();\n    }\n\n    return str.add(2 * Process.pointerSize).readPointer().readUtf8String();\n}\n\nfunction findSymbolInLib(libname, keywordList) {\n    const libBase = Module.findBaseAddress(libname);\n    if (!libBase) {\n        console.error(\"[-] Library not loaded:\", libname);\n        return null;\n    }\n\n    const matches = [];\n    const symbols = Module.enumerateSymbolsSync(libname);\n    for (const sym of symbols) {\n        if (keywordList.every(k =\u003e sym.name.includes(k))) {\n            matches.push(sym);\n        }\n    }\n\n    if (matches.length === 0) {\n        console.error(\"[-] No matching symbol found for keywords:\", keywordList);\n        return null;\n    }\n\n    const target = matches[0]; // 取第一个匹配的\n    console.log(\"[+] Found symbol:\", target.name, \" @ \", target.address);\n    return target.address;\n}\n\nfunction dumpDexToFile(filename, base, size) {\n    // packageName\n    var processName = getProcessName();\n\n    if (processName != \"-1\") {\n        const dir = \"/sdcard/Android/data/\" + processName + \"/dump_dex\";\n        const fullPath = dir + \"/\" + filename.replace(/\\//g, \"_\").replace(/!/g, \"_\");\n\n        // 创建目录\n        mkdir(dir);\n\n        // dump dex\n        var fd = new File(fullPath, \"wb\");\n        if (fd \u0026\u0026 fd != null) {\n            var dex_buffer = ptr(base).readByteArray(size);\n            fd.write(dex_buffer);\n            fd.flush();\n            fd.close();\n            console.log(\"[+] Dex dumped to\", fullPath);\n        }\n    }\n}\n\n\nfunction hookDexFileLoaderOpenCommon() {\n    const addr = findSymbolInLib(\"libdexfile.so\", [\"DexFileLoader\", \"OpenCommon\"]);\n    if (!addr) return;\n\n    Interceptor.attach(addr, {\n        onEnter(args) {\n            const base = args[0]; // const uint8_t* base\n            const size = args[1].toInt32(); // size_t size\n            const location_ptr = args[4]; // const std::string\u0026 location\n            const location = readStdString(location_ptr);\n\n            console.log(\"\\n[*] DexFileLoader::OpenCommon called\");\n            console.log(\"    base       :\", base);\n            console.log(\"    size       :\", size);\n            console.log(\"    location   :\", location);\n\n            // 文件名\n            const filename = location.split(\"/\").pop();\n\n            // 魔数\n            var magic = ptr(base).readCString();\n            console.log(\"    magic      :\", magic)\n\n            // dex 格式校验\n            if (magic.indexOf(\"dex\") !== -1) {\n                dumpDexToFile(filename, base, size)\n            }\n        },\n        onLeave(retval) {}\n    });\n}\n\nsetImmediate(hookDexFileLoaderOpenCommon);\n```\n\n\n列出当前设备所有进程并通过 findstr 过滤出目标进程\n\n```\nfrida-ps -H 127.0.0.1:1234 | findstr cyrus\n```\n\n\n执行脚本开始 dump\n\n```\nfrida -H 127.0.0.1:1234 -l dump_dex_from_open_common.js -f com.cyrus.example\n```\n\n\n输出如下：\n\n```\nSpawning `com.cyrus.example`...                                         \n[+] Found: _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28\nSpawned `com.cyrus.example`. Use %resume to let the main thread start executing!\n[Remote::com.cyrus.example]-\u003e %resume\n[Remote::com.cyrus.example]-\u003e \n================= DexFileLoader::OpenCommon =================\nbase: 0x79b9fe106c\nsize: 1602672\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba1684e0\nsize: 1800\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes2.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes2.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba168bec\nsize: 155888\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes3.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes3.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba18ece0\nsize: 8904\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes4.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes4.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba190fac\nsize: 1288\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes5.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes5.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba1914b8\nsize: 2656\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes6.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes6.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba191f1c\nsize: 11824\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes7.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes7.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba194d50\nsize: 8720\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes8.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes8.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba196f64\nsize: 9472\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes9.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes9.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba199468\nsize: 8904\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes10.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes10.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba19b734\nsize: 9504\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes11.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes11.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba19dc58\nsize: 1632\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes12.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes12.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba19e2bc\nsize: 800\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes13.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes13.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba19e5e0\nsize: 10328\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes14.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes14.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba1a0e3c\nsize: 3016\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes15.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes15.dex\n================= DexFileLoader::OpenCommon =================\nbase: 0x79ba1a1a08\nsize: 1205136\nlocation: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes16.dex\nmagic :  cdex001\nprocessName: com.cyrus.example\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes16.dex\n```\n\n\n可以看到 dex 已经 dump 到 sdcard 了\n\n\n\n![word/media/image9.png](https://gitee.com/cyrus-studio/images/raw/master/55c0144ce3bb1c5eb7ddff465a5cbfd1.png)\n\n\n使用下面的 adb pull 命令，一次性将设备上的整个 dump_dex 目录拉取到本地：\n\n```\nadb pull /sdcard/Android/data/com.cyrus.example/dump_dex ./dumped_dex\n```\n\n\n但是日志输出的 magic 可以看到都是  cdex001，cdex 文件是不可以直接通过 dex 反编译工具反编译的\n\n\n\n![word/media/image10.png](https://gitee.com/cyrus-studio/images/raw/master/c61540418336f940c463dac5f9533c76.png)\n\n\n# **禁止加载 cdex**\n\n\n\nAndroid 9 引入 CompactDex（.cdex，magic 为 cdex001），是 DEX 的压缩优化版本，导致 dump 后无法直接反编译。\n\n\n\n优化后的 dex/cdex 通常存放在：\n\n```\n/data/app/package_name/oat/arm64/base.odex\n/data/app/package_name/oat/arm64/base.vdex\n```\n\n\n在 Android 9（Pie）中，APP 的 .cdex 文件 是由 dex2oat 优化生成的，通常以 odex, vdex 或直接优化后的 .art 文件形式存在。\n\n\n\n进入 adb shell 找到 目标app 存放 oat 文件的路径并删除所有 oat 文件\n\n```\nwayne:/sdcard/Android/data/com.cyrus.example/dump_dex # cd /data/app\nwayne:/data/app # ls\ncom.android.chrome-b1d3YEy1eVrwwjPOa1oq5A==       com.iflytek.inputmethod-s1r9JFv0-eKNskzHyrh_vQ==\ncom.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==        com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==\ncom.cyrus.example.plugin-YsXrxPvfWYdsWHxFKjcusw== com.tencent.mm-ql7ajyK9JqKXli5pgu88nw==\ncom.cyrus.example.test-R06ZNyf5doqJFOcZ6EaYHQ==   com.xingin.xhs-HeYr1dfB-rU7NjxJiLiDeg==\nwayne:/data/app # cd com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==\nwayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== # ls\nbase.apk lib oat\nwayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== # cd oat\nwayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat # ls\narm64\nwayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat # cd arm64/\nwayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 # ls\nbase.art base.odex base.vdex\nwayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 # rm -rf *\n```\n\n\n重新执行 frida 脚本 dump dex，从输出可以看到 dump 下来的 dex 魔数都是 dex 039 / dex 035 （标准 Dex 文件的魔数）不是 cdex001，可以直接用 jadx 去反编译了。\n\n```\nSpawning `com.shizhuang.duapp`...                                       \n[+] Found symbol: _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE  @  0x7aac649c28\nSpawned `com.shizhuang.duapp`. Use %resume to let the main thread start executing!\n[Remote::com.shizhuang.duapp]-\u003e %resume\n[Remote::com.shizhuang.duapp]-\u003e\n[*] DexFileLoader::OpenCommon called\n    base       : 0x7a1d08e02c\n    size       : 450032\n    location   : /system/framework/org.apache.http.legacy.jar\n    magic      : dex\n039\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/org.apache.http.legacy.jar\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x7a1d08e02c\n    size       : 450032\n    location   : /system/framework/org.apache.http.legacy.jar\n    magic      : dex\n039\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/org.apache.http.legacy.jar\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79bbd5c000\n    size       : 8681372\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79ba491000\n    size       : 12888744\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes2.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes2.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b928e000\n    size       : 12592256\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes3.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes3.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b86e8000\n    size       : 12213596\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes4.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes4.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b7cc2000\n    size       : 10637856\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes5.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes5.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b74d1000\n    size       : 8324572\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes6.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes6.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b71b1000\n    size       : 3273924\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes7.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes7.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b69e3000\n    size       : 8183732\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes8.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes8.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b5e72000\n    size       : 11994176\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes9.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes9.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b53d5000\n    size       : 11125808\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes10.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes10.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b4815000\n    size       : 12319700\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes11.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes11.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b3c59000\n    size       : 12300396\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes12.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes12.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b3057000\n    size       : 12587972\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes13.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes13.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b24d1000\n    size       : 12081268\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes14.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes14.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b18cf000\n    size       : 12590752\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes15.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes15.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79b179b000\n    size       : 1260244\n    location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes16.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes16.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79a39f57fc\n    size       : 3782924\n    location   : /system/product/app/webview/webview.apk\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x7a11ec6138\n    size       : 77880\n    location   : /system/product/app/webview/webview.apk!classes2.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk_classes2.dex\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x79a39f57fc\n    size       : 3782924\n    location   : /system/product/app/webview/webview.apk\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk\n\n[*] DexFileLoader::OpenCommon called\n    base       : 0x7a11ec6138\n    size       : 77880\n    location   : /system/product/app/webview/webview.apk!classes2.dex\n    magic      : dex\n035\n[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk_classes2.dex\n```\n\n\n# **jadx 反编译 dex**\n\n\n\n使用 jadx 反编译 dex。\n\n\n\njadx 项目地址：[https://github.com/skylot/jadx](https://github.com/skylot/jadx)\n\n\n\n\n\n![word/media/image11.png](https://gitee.com/cyrus-studio/images/raw/master/4816e881ee8000e5b1919e2bff08a6a0.png)\n\n\njadx 默认缓存目录\n\n```\nC:\\Users\\$USERNAME\\AppData\\Local\\skylot\\jadx\\cache\\projects\n```\n\n\n# **DexFile 脱壳**\n\n\n\n找到 CompactDexFile 构造函数方法符号信息如下：\n\n```\n[+] libdexfile.so -\u003e _ZN3art14CompactDexFileC1EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8\n[+] libdexfile.so -\u003e _ZN3art14CompactDexFileC2EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8\n```\n\n\nhook CompactDexFile 和  StandardDexFile 的构造函数拿到 base、size 和 location 并 dump dex。\n\n\n\ndump_dex_from_dex_file.js\n\n```\nfunction getProcessName() {\n    var openPtr = Module.getExportByName('libc.so', 'open');\n    var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);\n\n    var readPtr = Module.getExportByName(\"libc.so\", \"read\");\n    var read = new NativeFunction(readPtr, \"int\", [\"int\", \"pointer\", \"int\"]);\n\n    var closePtr = Module.getExportByName('libc.so', 'close');\n    var close = new NativeFunction(closePtr, 'int', ['int']);\n\n    var path = Memory.allocUtf8String(\"/proc/self/cmdline\");\n    var fd = open(path, 0);\n    if (fd != -1) {\n        var buffer = Memory.alloc(0x1000);\n\n        var result = read(fd, buffer, 0x1000);\n        close(fd);\n        result = ptr(buffer).readCString();\n        return result;\n    }\n\n    return \"-1\";\n}\n\n\nfunction mkdir(path) {\n    var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');\n    var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']);\n\n\n    var opendirPtr = Module.getExportByName('libc.so', 'opendir');\n    var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']);\n\n    var closedirPtr = Module.getExportByName('libc.so', 'closedir');\n    var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']);\n\n    var cPath = Memory.allocUtf8String(path);\n    var dir = opendir(cPath);\n    if (dir != 0) {\n        closedir(dir);\n        return 0;\n    }\n    mkdir(cPath, 755);\n    chmod(path);\n}\n\nfunction chmod(path) {\n    var chmodPtr = Module.getExportByName('libc.so', 'chmod');\n    var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);\n    var cPath = Memory.allocUtf8String(path);\n    chmod(cPath, 755);\n}\n\nfunction readStdString(str) {\n    const isTiny = (str.readU8() \u0026 1) === 0;\n    if (isTiny) {\n        return str.add(1).readUtf8String();\n    }\n\n    return str.add(2 * Process.pointerSize).readPointer().readUtf8String();\n}\n\nfunction findSymbolInLib(libname, keywordList) {\n    const libBase = Module.findBaseAddress(libname);\n    if (!libBase) {\n        console.error(\"[-] Library not loaded:\", libname);\n        return null;\n    }\n\n    const matches = [];\n    const symbols = Module.enumerateSymbolsSync(libname);\n    for (const sym of symbols) {\n        if (keywordList.every(k =\u003e sym.name.includes(k))) {\n            matches.push(sym);\n        }\n    }\n\n    if (matches.length === 0) {\n        console.error(\"[-] No matching symbol found for keywords:\", keywordList);\n        return null;\n    }\n\n    const target = matches[0]; // 取第一个匹配的\n    console.log(\"[+] Found symbol:\", target.name, \" @ \", target.address);\n    return target.address;\n}\n\nfunction dumpDexToFile(filename, base, size) {\n    // packageName\n    var processName = getProcessName();\n\n    if (processName != \"-1\") {\n        const dir = \"/sdcard/Android/data/\" + processName + \"/dump_dex\";\n        const fullPath = dir + \"/\" + filename.replace(/\\//g, \"_\").replace(/!/g, \"_\");\n\n        // 创建目录\n        mkdir(dir);\n\n        // dump dex\n        var fd = new File(fullPath, \"wb\");\n        if (fd \u0026\u0026 fd != null) {\n            var dex_buffer = ptr(base).readByteArray(size);\n            fd.write(dex_buffer);\n            fd.flush();\n            fd.close();\n            console.log(\"[+] Dex dumped to\", fullPath);\n        }\n    }\n}\n\nfunction hookCompactDexFile() {\n    const addr = findSymbolInLib(\"libdexfile.so\", [\"CompactDexFile\", \"C1\"]);\n    if (!addr) return;\n\n    Interceptor.attach(addr, {\n        onEnter(args) {\n            const base = args[1];\n            const size = args[2].toInt32();\n            const data_base = args[3];\n            const data_size = args[4].toInt32();\n            const location_ptr = args[5];\n            const location = readStdString(location_ptr);\n\n            console.log(\"\\n[*] CompactDexFile constructor called\");\n            console.log(\"    this       :\", args[0]);\n            console.log(\"    base       :\", base);\n            console.log(\"    size       :\", size);\n            console.log(\"    data_base  :\", data_base);\n            console.log(\"    data_size  :\", data_size);\n            console.log(\"    location   :\", location);\n\n            // 文件名\n            const filename = location.split(\"/\").pop();\n\n            // 魔数\n            var magic = ptr(base).readCString();\n            console.log(\"    magic      :\", magic)\n\n            // dex 格式校验\n            if (magic.indexOf(\"dex\") !== -1) {\n                dumpDexToFile(filename, base, size)\n            }\n        }\n    });\n}\n\nfunction hookStandardDexFile() {\n    const addr = findSymbolInLib(\"libdexfile.so\", [\"StandardDexFile\", \"C1\"]);\n    if (!addr) return;\n\n    Interceptor.attach(addr, {\n        onEnter(args) {\n            const base = args[1];\n            const size = args[2].toInt32();\n            const data_base = args[3];\n            const data_size = args[4].toInt32();\n            const location_ptr = args[5];\n            const location = readStdString(location_ptr);\n\n            console.log(\"\\n[*] StandardDexFile constructor called\");\n            console.log(\"    this       :\", args[0]);\n            console.log(\"    base       :\", base);\n            console.log(\"    size       :\", size);\n            console.log(\"    data_base  :\", data_base);\n            console.log(\"    data_size  :\", data_size);\n            console.log(\"    location   :\", location);\n\n            // 文件名\n            const filename = location.split(\"/\").pop();\n\n            // 魔数\n            var magic = ptr(base).readCString();\n            console.log(\"    magic      :\", magic)\n\n            // dex 格式校验\n            if (magic.indexOf(\"dex\") !== -1) {\n                dumpDexToFile(filename, base, size)\n            }\n        }\n    });\n}\n\n\nsetImmediate(function () {\n    hookCompactDexFile()\n    hookStandardDexFile()\n});\n```\n\n\n执行脚本：\n\n```\nfrida -H 127.0.0.1:1234 -l dump_dex_from_dex_file.js -f com.cyrus.example\n```\n\n\n输出如下：\n\n```\nSpawning `com.cyrus.example`...                                         \n[+] Found symbol: _ZN3art14CompactDexFileC1EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE  @  0x7aac6420e8\n[-] No matching symbol found for keywords: StandardDexFile,C1\nSpawned `com.cyrus.example`. Use %resume to let the main thread start executing!\n[Remote::com.cyrus.example]-\u003e %resume\n[Remote::com.cyrus.example]-\u003e\n[*] CompactDexFile constructor called\n    this       : 0x7aacac0720\n    base       : 0x79b9fe206c\n    size       : 1602672\n    data_base  : 0x79ba2c8d98\n    data_size  : 14765976\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac0800\n    base       : 0x79ba1694e0\n    size       : 1800\n    data_base  : 0x79ba2c8d98\n    data_size  : 14770144\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes2.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes2.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac08e0\n    base       : 0x79ba169bec\n    size       : 155888\n    data_base  : 0x79ba2c8d98\n    data_size  : 15120528\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes3.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes3.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac09c0\n    base       : 0x79ba18fce0\n    size       : 8904\n    data_base  : 0x79ba2c8d98\n    data_size  : 15155776\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes4.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes4.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac0aa0\n    base       : 0x79ba191fac\n    size       : 1288\n    data_base  : 0x79ba2c8d98\n    data_size  : 15158304\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes5.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes5.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac0b80\n    base       : 0x79ba1924b8\n    size       : 2656\n    data_base  : 0x79ba2c8d98\n    data_size  : 15165016\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes6.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes6.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac0c60\n    base       : 0x79ba192f1c\n    size       : 11824\n    data_base  : 0x79ba2c8d98\n    data_size  : 15211952\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes7.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes7.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7aacac0d40\n    base       : 0x79ba195d50\n    size       : 8720\n    data_base  : 0x79ba2c8d98\n    data_size  : 15242288\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes8.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes8.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a175fe260\n    base       : 0x79ba197f64\n    size       : 9472\n    data_base  : 0x79ba2c8d98\n    data_size  : 15276888\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes9.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes9.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a175fe340\n    base       : 0x79ba19a468\n    size       : 8904\n    data_base  : 0x79ba2c8d98\n    data_size  : 15314648\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes10.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes10.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a175fe420\n    base       : 0x79ba19c734\n    size       : 9504\n    data_base  : 0x79ba2c8d98\n    data_size  : 15346672\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes11.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes11.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a175fe500\n    base       : 0x79ba19ec58\n    size       : 1632\n    data_base  : 0x79ba2c8d98\n    data_size  : 15349816\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes12.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes12.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a175fe5e0\n    base       : 0x79ba19f2bc\n    size       : 800\n    data_base  : 0x79ba2c8d98\n    data_size  : 15350936\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes13.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes13.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a175fe6c0\n    base       : 0x79ba19f5e0\n    size       : 10328\n    data_base  : 0x79ba2c8d98\n    data_size  : 15401760\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes14.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes14.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a176a4fc0\n    base       : 0x79ba1a1e3c\n    size       : 3016\n    data_base  : 0x79ba2c8d98\n    data_size  : 15409648\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes15.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes15.dex\n\n[*] CompactDexFile constructor called\n    this       : 0x7a176a50a0\n    base       : 0x79ba1a2a08\n    size       : 1205136\n    data_base  : 0x79ba2c8d98\n    data_size  : 22612744\n    location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes16.dex\n    magic      : cdex001\n[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes16.dex\n```\n\n\n把 dex 文件拉取到本地：\n\n```\nadb pull /sdcard/Android/data/com.cyrus.example/dump_dex ./dumped_dex\n```\n\n\n使用命令行工具 compact_dex_converter 把 cdex（Compact Dex）文件转换为标准 .dex 文件。\n\n[https://github.com/anestisb/vdexExtractor#compact-dex-converter](https://github.com/anestisb/vdexExtractor#compact-dex-converter)\n\n\n\n# **dex2oat 脱壳**\n\n\n\ndex2oat 的流程也可以进行脱壳。\n\n\n\n当安装 APK 时，如果需要 ahead-of-time (AOT) 编译，installd 会调用 dex2oat：\n\n\n\n![word/media/image12.png](https://gitee.com/cyrus-studio/images/raw/master/608956255740c4fefcd613ee1b52900c.png)\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:frameworks/native/cmds/installd/dexopt.cpp;l=306](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:frameworks/native/cmds/installd/dexopt.cpp;l=306)\n\n\n\n进入 dex2oat.cc 的 main()，在 dex2oat::ReturnCode Setup() 方法中 将 dex 注册到 VerificationResults 时候可以拿到 dex_file 对象，这里也是一个很好的脱壳点。\n\n```\nverification_results_-\u003eAddDexFile(dex_file);\n```\n\n\n![word/media/image13.png](https://gitee.com/cyrus-studio/images/raw/master/4a384bd1e48d829f9c6064b9d1299864.png)\n[https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/dex2oat/dex2oat.cc;l=1685](https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/dex2oat/dex2oat.cc;l=1685)\n\n\n\n# **完整源码**\n\n\n\n开源地址：[https://github.com/CYRUS-STUDIO/frida_dex_dump](https://github.com/CYRUS-STUDIO/frida_dex_dump)\n\n\n\n相关文章：\n\n- _[ART环境下dex加载流程分析及frida dump dex方案](https://bbs.kanxue.com/thread-277771.htm)_\n\n- _[拨云见日：安卓APP脱壳的本质以及如何快速发现ART下的脱壳点](https://bbs.kanxue.com/thread-254555.htm)_\n\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrus-studio%2Ffrida_dex_dump","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcyrus-studio%2Ffrida_dex_dump","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcyrus-studio%2Ffrida_dex_dump/lists"}