当前位置: 首页 > java >正文

ART 下 Dex 加载流程源码分析 和 通用脱壳点

版权归作者所有,如有转发,请注明文章出处:https://cyrus-studio.github.io/blog/

DEF CON

DEF CON 是全球最大的计算机安全会议之一(极客的奥斯卡),自1993年6月起,每年在美国内华达州的拉斯维加斯举办。

官网:https://media.defcon.org/,DEF CON 黑客大会官方的媒体存档站点,提供历年 DEF CON 大会的公开演讲、幻灯片、视频、音频、代码示例和其他相关资源的免费下载。

在 DEF CON 25(2017 年)上,Check Point 的安全研究员 Slava Makkaveev 和 Avi Bashan 发表了题为《Unboxing Android: Everything You Wanted to Know About Android Packers》的演讲,深入探讨了 Android 应用程序中的加壳技术及其安全影响。

报告文件地址:

https://media.defcon.org/DEF%20CON%2025/DEF%20CON%2025%20presentations/DEF%20CON%2025%20-%20Slava-Makkaveev-and-Avi-Bashan-Unboxing-Android.pdf

对于国内加壳厂商也有分析

word/media/image1.png

DEF 的安全研究员选择的两个脱壳点:art::OpenAndReadMagic 和 DexFile::DexFile

word/media/image2.png

Unboxing Android

在 DEF CON 25 (2017) 上,Avi Bashan 和 Slava Makkaveev 提出过一种非常实用的 Android 加壳脱壳技术:

通过修改 DexFile::DexFile() 构造函数和 OpenAndReadMagic() 方法,可以在应用运行时,拦截 DEX 文件加载过程,从而拿到已经解密后的内存数据,完成脱壳。

1. DexFile::DexFile 构造函数

可以看到 DexFile::DexFile() 的构造函数参数里包含了:

  • const uint8_t* base —— DEX 在内存中的起始地址

  • size_t size —— DEX 的内存大小

DexFile::DexFile(const uint8_t* base,size_t size,const uint8_t* data_begin,size_t data_size,const std::string& location,uint32_t location_checksum,const OatDexFile* oat_dex_file,std::unique_ptr<DexFileContainer> container,bool is_compact_dex): begin_(base),size_(size),data_begin_(data_begin),data_size_(data_size),location_(location),location_checksum_(location_checksum),header_(reinterpret_cast<const Header*>(base)),string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),method_handles_(nullptr),num_method_handles_(0),call_site_ids_(nullptr),num_call_site_ids_(0),hiddenapi_class_data_(nullptr),oat_dex_file_(oat_dex_file),container_(std::move(container)),is_compact_dex_(is_compact_dex),hiddenapi_domain_(hiddenapi::Domain::kApplication) {CHECK(begin_ != nullptr) << GetLocation();CHECK_GT(size_, 0U) << GetLocation();// Check base (=header) alignment.// Must be 4-byte aligned to avoid undefined behavior when accessing// any of the sections via a pointer.CHECK_ALIGNED(begin_, alignof(Header));InitializeSectionsFromMapList();
}

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96

word/media/image3.png

插入脱壳代码示例

// 打印当前 DEX 文件的位置
LOG(WARNING) << "Dex File: Filename: " << location;// 判断这个 DEX 是不是从 APP 自身私有目录 加载的。
// 因为系统自己的 framework、boot.oat 里的 DEX 都不是加壳 DEX,只想 dump 应用自己的 DEX。
if (location.find("/data/data/") != std::string::npos) {LOG(WARNING) << "Dex File: OAT file unpacking launched";// 创建一个新的文件,比如 /data/data/包名/xxx.dex__unpacked_oat。std::ofstream dst(location + "__unpacked_oat", std::ios::binary);// 把内存里的 DEX 数据完整写入磁盘。dst.write(reinterpret_cast<const char*>(base), size);// 保存文件,完成脱壳。dst.close();
} else {LOG(WARNING) << "Dex File: OAT file unpacking not launched";
}

2. DexFile::OpenAndReadMagic()

这是辅助检查 DEX 文件头的函数。

File OpenAndReadMagic(const char* filename, uint32_t* magic, std::string* error_msg) {CHECK(magic != nullptr);File fd(filename, O_RDONLY, /* check_usage= */ false);if (fd.Fd() == -1) {*error_msg = StringPrintf("Unable to open '%s' : %s", filename, strerror(errno));return File();}if (!ReadMagicAndReset(fd.Fd(), magic, error_msg)) {StringPrintf("Error in reading magic from file %s: %s", filename, error_msg->c_str());return File();}return fd;
}

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libartbase/base/file_magic.cc;l=32

word/media/image4.png

插入脱壳代码示例

struct stat st;  // 用于获取文件大小等信息// 打印当前正在处理的文件路径,便于调试和观察加载的 DEX 来源
LOG(WARNING) << "File_magic: Filename: " << filename;// 只处理 /data/data 路径下的文件(即应用私有目录中的 dex 文件)
// 这样可以避免处理系统 DEX,提高效率和准确性
if (strstr(filename, "/data/data") != NULL) {LOG(WARNING) << "File_magic: DEX file unpacking launched";// 构造输出文件路径,加上 "__unpacked_dex" 后缀char* fn_out = new char[PATH_MAX];strcpy(fn_out, filename);strcat(fn_out, "__unpacked_dex");// 创建输出文件,设置权限:用户可读写、用户组可读、其他人可读int fd_out = open(fn_out, O_WRONLY | O_CREAT | O_EXCL,S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);// 如果获取原始 dex 文件信息成功(用于获取文件大小)if (!fstat(fd.get(), &st)) {// 使用 mmap 将整个 dex 文件映射到内存中char* addr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd.get(), 0);// 将内存中的内容写入到新文件,完成磁盘级别的 dex dumpint ret = write(fd_out, addr, st.st_size);// 可选防优化代码(保证 ret 被使用,防止编译器优化)ret += 1;// 解除映射,释放内存munmap(addr, st.st_size);}// 关闭输出文件,清理路径内存close(fd_out);delete[] fn_out;} else {// 如果不是应用私有路径下的文件,跳过处理LOG(WARNING) << "File_magic: DEX file unpacking not launched";
}

ART 下脱壳原理

ART 下常见的两个 dex 加载器:InMemoryDexClassLoader 和 DexClassLoader

InMemoryDexClassLoader 源码分析

InMemoryDexClassLoader 是 Android 8.0(API 级别 26)引入的一个类,用于动态加载内存中的 Dex。

调用示例:

// 假设 dexBytes 是你的 DEX 文件内容(可以通过解密获得)
ByteBuffer buffer = ByteBuffer.wrap(dexBytes);// 创建 InMemoryDexClassLoader
ClassLoader loader = new InMemoryDexClassLoader(buffer, ClassLoader.getSystemClassLoader());// 通过反射加载类并调用方法
Class<?> clazz = loader.loadClass("com.example.MyHiddenClass");
Method m = clazz.getDeclaredMethod("secretMethod");
m.invoke(null);

InMemoryDexClassLoader 支持加载 内存中 一个或多个 Dex。源码如下:

word/media/image5.png
https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/InMemoryDexClassLoader.java

openInMemoryDexFilesNative

Dex 加载过程如下,最终调用到 native 方法 openInMemoryDexFilesNative

 InMemoryDexClassLoader(ByteBuffer[] dexBuffers, String librarySearchPath, ClassLoader parent)└── BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent)└── DexPathList.initByteBufferDexPath(ByteBuffer[] dexFiles)└── DexFile(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements)└── DexFile.openInMemoryDexFiles(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements)└── DexFile.openInMemoryDexFilesNative(ByteBuffer[] bufs, byte[][] arrays, int[] starts, int[] ends, ClassLoader loader, DexPathList.Element[] elements)└── DexFile_openInMemoryDexFilesNative(JNIEnv* env, jclass, jobjectArray buffers, jobjectArray arrays, jintArray jstarts, jintArray jends, jobject class_loader, jobjectArray dex_elements)

https://cs.android.com/android/platform/superproject/main/+/main:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=134

DexFile_openInMemoryDexFilesNative 中 调用 OpenDexFilesFromOat 方法 加载 Dex :

static jobject DexFile_openInMemoryDexFilesNative(JNIEnv* env,jclass,jobjectArray buffers,jobjectArray arrays,jintArray jstarts,jintArray jends,jobject class_loader,jobjectArray dex_elements) {jsize buffers_length = env->GetArrayLength(buffers);CHECK_EQ(buffers_length, env->GetArrayLength(arrays));CHECK_EQ(buffers_length, env->GetArrayLength(jstarts));CHECK_EQ(buffers_length, env->GetArrayLength(jends));ScopedIntArrayAccessor starts(env, jstarts);ScopedIntArrayAccessor ends(env, jends);// Allocate memory for dex files and copy data from ByteBuffers.std::vector<MemMap> dex_mem_maps;dex_mem_maps.reserve(buffers_length);for (jsize i = 0; i < buffers_length; ++i) {jobject buffer = env->GetObjectArrayElement(buffers, i);jbyteArray array = reinterpret_cast<jbyteArray>(env->GetObjectArrayElement(arrays, i));jint start = starts.Get(i);jint end = ends.Get(i);MemMap dex_data = AllocateDexMemoryMap(env, start, end);if (!dex_data.IsValid()) {DCHECK(Thread::Current()->IsExceptionPending());return nullptr;}if (array == nullptr) {// Direct ByteBufferuint8_t* base_address = reinterpret_cast<uint8_t*>(env->GetDirectBufferAddress(buffer));if (base_address == nullptr) {ScopedObjectAccess soa(env);ThrowWrappedIOException("dexFileBuffer not direct");return nullptr;}size_t length = static_cast<size_t>(end - start);memcpy(dex_data.Begin(), base_address + start, length);} else {// ByteBuffer backed by a byte arrayjbyte* destination = reinterpret_cast<jbyte*>(dex_data.Begin());env->GetByteArrayRegion(array, start, end - start, destination);}dex_mem_maps.push_back(std::move(dex_data));}// Hand MemMaps over to OatFileManager to open the dex files and potentially// create a backing OatFile instance from an anonymous vdex.std::vector<std::string> error_msgs;const OatFile* oat_file = nullptr;std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/native/dalvik_system_DexFile.cc;l=240

OpenDexFilesFromOat

OpenDexFilesFromOat 调用 OpenDexFilesFromOat_Impl 加载 Dex

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat_Impl(std::vector<MemMap>&& dex_mem_maps,jobject class_loader,jobjectArray dex_elements,const OatFile** out_oat_file,std::vector<std::string>* error_msgs) {ScopedTrace trace(__FUNCTION__);std::string error_msg;DCHECK(error_msgs != nullptr);// [1] 提取 Dex Header,用于后续校验 checksum、生成路径等const std::vector<const DexFile::Header*> dex_headers = GetDexFileHeaders(dex_mem_maps);// [2] 生成临时匿名 dex/vdex 文件路径,获取 checksum 和路径uint32_t location_checksum;std::string dex_location;std::string vdex_path;bool has_vdex = OatFileAssistant::AnonymousDexVdexLocation(dex_headers, kRuntimeISA, &location_checksum, &dex_location, &vdex_path);// [3] 尝试打开 vdex 文件,并检查其中的 dex checksum 是否一致std::unique_ptr<VdexFile> vdex_file = nullptr;if (has_vdex && OS::FileExists(vdex_path.c_str())) {vdex_file = VdexFile::Open(vdex_path, /*writable=*/false, /*low_4gb=*/false,/*unquicken=*/false, &error_msg);if (vdex_file == nullptr) {LOG(WARNING) << "Failed to open vdex " << vdex_path << ": " << error_msg;} else if (!vdex_file->MatchesDexFileChecksums(dex_headers)) {LOG(WARNING) << "Dex checksum mismatch: " << vdex_path;vdex_file.reset(nullptr);}}// [4] 加载内存中的 dex。若存在 vdex 且校验成功,可跳过结构校验std::vector<std::unique_ptr<const DexFile>> dex_files;for (size_t i = 0; i < dex_mem_maps.size(); ++i) {static constexpr bool kVerifyChecksum = true;const ArtDexFileLoader dex_file_loader;std::unique_ptr<const DexFile> dex_file(dex_file_loader.Open(DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()),location_checksum,std::move(dex_mem_maps[i]),/*verify=*/(vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(),kVerifyChecksum,&error_msg));if (dex_file != nullptr) {dex::tracking::RegisterDexFile(dex_file.get());  // 注册用于调试追踪dex_files.push_back(std::move(dex_file));} else {error_msgs->push_back("Failed to open dex files from memory: " + error_msg);}}// [5] 若 vdex 不存在、加载失败,或 class_loader 为空,直接返回 dex_filesif (vdex_file == nullptr || class_loader == nullptr || !error_msgs->empty()) {return dex_files;}// [6] 创建 ClassLoaderContext,确保之后的 oat 加载上下文一致std::unique_ptr<ClassLoaderContext> context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements);if (context == nullptr) {LOG(ERROR) << "Could not create class loader context for " << vdex_path;return dex_files;}DCHECK(context->OpenDexFiles(kRuntimeISA, ""))<< "Context created from already opened dex files should not attempt to open again";// [7] 检查 boot class path checksum 和 class loader context 是否匹配if (!vdex_file->MatchesBootClassPathChecksums() ||!vdex_file->MatchesClassLoaderContext(*context.get())) {return dex_files;}// [8] 从 vdex 创建 OatFile 实例并注册std::unique_ptr<OatFile> oat_file(OatFile::OpenFromVdex(MakeNonOwningPointerVector(dex_files),std::move(vdex_file),dex_location));DCHECK(oat_file != nullptr);VLOG(class_linker) << "Registering " << oat_file->GetLocation();*out_oat_file = RegisterOatFile(std::move(oat_file));return dex_files;
}

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=708

在 Android 10 之前,InMemoryDexClassLoader 加载的 DEX 文件不会被编译为 OAT 文件,而是直接在解释模式下执行, 这也是它和 DexClassLoader 的区别。

从 Android 10 开始,InMemoryDexClassLoader 加载的 DEX 文件也会走 OAT 流程。

DexFile_openInMemoryDexFilesNative → DexFile::DexFile 调用路径

DexFile_openInMemoryDexFilesNative(...)                    
└── AllocateDexMemoryMap(...) 创建 dex_mem_maps
└── Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(...) └── OatFileManager::OpenDexFilesFromOat(dex_mem_maps, class_loader, dex_elements, out_oat_file, error_msgs)└── OatFileManager::OpenDexFilesFromOat_Impl(...)└── ArtDexFileLoader::Open(location, location_checksum, map, verify, verify_checksum, error_msg)└── ArtDexFileLoader::OpenCommon(base, size, ...)└── DexFileLoader::OpenCommon(base, size, ...)└── new StandardDexFile(base, location, ...): DexFile(base, location, ...)└── new CompactDexFile(base, location, ...): DexFile(base, location, ...)

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=708

OpenCommon

在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump

ArtDexFileLoader::Open(location, location_checksum, map, verify, verify_checksum, error_msg)└── ArtDexFileLoader::OpenCommon(base, size, ...)└── DexFileLoader::OpenCommon(base, size, ...)└── new StandardDexFile(base, location, ...): DexFile(base, location, ...)└── new CompactDexFile(base, location, ...): DexFile(base, location, ...)

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/dex_file_loader.cc;l=316

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/dex_file.cc;l=96

DexClassLoader 源码分析

DexClassLoader 可以加载任意路径下的 dex,或者 jar、apk、zip 文件(包含classes.dex)。

源码如下:

word/media/image6.png
https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/DexClassLoader.java

DexFile_openDexFileNative

DexClassLoader 最终是通过 JNI 调用 DexFile_openDexFileNative 来加载 Dex。

下面是从 Java 到 native 的完整调用路径(以 Android 10 为例):

DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)↓
BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent)↓
DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted)↓
DexPathList.makeDexElements(...)↓
new DexFile(file, loader, elements)↓
DexFile.openDexFile(fileName, null, 0, loader, elements)↓
DexFile.openDexFileNative(sourceFile, outputFile, flags, loader, elements)↓
DexFile_openDexFileNative(...) (native 层)

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:libcore/dalvik/src/main/java/dalvik/system/DexFile.java;l=440

DexFile_openDexFileNative 方法中调用 OpenDexFilesFromOat 方法生成 OAT 文件:

static jobject DexFile_openDexFileNative(JNIEnv* env,jclass,jstring javaSourceName,jstring javaOutputName ATTRIBUTE_UNUSED,jint flags ATTRIBUTE_UNUSED,jobject class_loader,jobjectArray dex_elements) {ScopedUtfChars sourceName(env, javaSourceName);if (sourceName.c_str() == nullptr) {return nullptr;}std::vector<std::string> error_msgs;const OatFile* oat_file = nullptr;std::vector<std::unique_ptr<const DexFile>> dex_files =Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),class_loader,dex_elements,/*out*/ &oat_file,/*out*/ &error_msgs);return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs);
}

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/native/dalvik_system_DexFile.cc

OpenDexFilesFromOat

和 InMemoryDexClassLoader 不同的是:这里传入的参数不是 MemMap,而是 const char* dex_location。

OpenDexFilesFromOat 方法源码如下:

std::vector<std::unique_ptr<const DexFile>> OatFileManager::OpenDexFilesFromOat(const char* dex_location,jobject class_loader,jobjectArray dex_elements,const OatFile** out_oat_file,std::vector<std::string>* error_msgs) {ScopedTrace trace(__FUNCTION__);CHECK(dex_location != nullptr);CHECK(error_msgs != nullptr);// 步骤 1: 确保未持有 mutator_lock,防止阻塞 GCThread* const self = Thread::Current();Locks::mutator_lock_->AssertNotHeld(self);Runtime* const runtime = Runtime::Current();// 步骤 2: 构造 ClassLoaderContext(可能为空)std::unique_ptr<ClassLoaderContext> context;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);}// 步骤 3: 构建 OatFileAssistant,用于操作 oat 和 dex 文件OatFileAssistant oat_file_assistant(dex_location,kRuntimeISA,!runtime->IsAotCompiler(),only_use_system_oat_files_);// 步骤 4: 获取磁盘上最优的 OAT 文件std::unique_ptr<const OatFile> oat_file(oat_file_assistant.GetBestOatFile().release());VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()="<< reinterpret_cast<uintptr_t>(oat_file.get())<< " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")";const OatFile* source_oat_file = nullptr;CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions;std::string error_msg;// 步骤 5: 进行 collision 检查决定是否接受这个 oat 文件if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) {check_collision_result = CheckCollision(oat_file.get(), context.get(), &error_msg);bool accept_oat_file = AcceptOatFile(check_collision_result);// 检查结果为 false,判断是否启用 fallback 并记录警告信息if (!accept_oat_file) {if (runtime->IsDexFileFallbackEnabled()) {if (!oat_file_assistant.HasOriginalDexFiles()) {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 {LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : "<< dex_location;LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance.";}} else {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;}// 步骤 6: 注册 oat 文件到 OatFileManagerif (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;// 步骤 7: 从 OAT 文件加载 dex 文件(如果成功加载了 oat)if (source_oat_file != nullptr) {bool added_image_space = false;if (source_oat_file->IsExecutable()) {ScopedTrace app_image_timing("AppImage:Loading");std::unique_ptr<gc::space::ImageSpace> image_space;if (ShouldLoadAppImage(check_collision_result,source_oat_file,context.get(),&error_msg)) {image_space = oat_file_assistant.OpenImageSpace(source_oat_file);}if (image_space != nullptr) {ScopedObjectAccess soa(self);StackHandleScope<1> hs(self);Handle<mirror::ClassLoader> h_loader(hs.NewHandle(soa.Decode<mirror::ClassLoader>(class_loader)));// 步骤 8: 尝试将 image space 添加到堆中if (h_loader != nullptr) {std::string temp_error_msg;{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,&dex_files,&temp_error_msg);}if (added_image_space) {image_space.release();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());}}}}}// 步骤 9: 如果未添加 image space,则从 oat 中手动加载 dex 文件if (!added_image_space) {DCHECK(dex_files.empty());dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);for (const auto& dex_file : dex_files) {dex::tracking::RegisterDexFile(dex_file.get());}}// 步骤 10: 检查是否 dex 文件加载失败if (dex_files.empty()) {error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation());} else {for (const std::unique_ptr<const DexFile>& dex_file : dex_files) {OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad);}}}// 步骤 11: OAT 加载失败,尝试从原始 dex 文件 fallback 加载if (dex_files.empty()) {if (oat_file_assistant.HasOriginalDexFiles()) {if (Runtime::Current()->IsDexFileFallbackEnabled()) {static constexpr bool kVerifyChecksum = true;const ArtDexFileLoader dex_file_loader;if (!dex_file_loader.Open(dex_location,dex_location,Runtime::Current()->IsVerificationEnabled(),kVerifyChecksum,&error_msg,&dex_files)) {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));}}// 步骤 12: JIT 启用时注册 dex 文件if (Runtime::Current()->GetJit() != nullptr) {ScopedObjectAccess soa(self);Runtime::Current()->GetJit()->RegisterDexFiles(dex_files, soa.Decode<mirror::ClassLoader>(class_loader));}// 最终返回 dex 文件数组return dex_files;
}

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/runtime/oat_file_manager.cc;l=447

OpenCommon

DexFile_openDexFileNative → DexFile::DexFile 调用路径

DexFile_openDexFileNative(...)  
└── OatFileManager::OpenDexFilesFromOat(dex_location, class_loader, dex_elements, out_oat_file, error_msgs)└── ArtDexFileLoader::Open(filename, location, verify, verify_checksum, error_msg, dex_files)└── art::OpenAndReadMagic(...)└── ArtDexFileLoader::OpenWithMagic(...)└── ArtDexFileLoader::OpenFile(...)└── ArtDexFileLoader::OpenCommon(base, size, ...)└── DexFileLoader::OpenCommon(base, size, ...)└── new StandardDexFile(base, location, ...): DexFile(base, location, ...)└── new CompactDexFile(base, location, ...): DexFile(base, location, ...)

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/libartbase/base/file_magic.cc?q=OpenAndReadMagic

在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump

ArtDexFileLoader::Open(filename, location, verify, verify_checksum, error_msg, dex_files)└── ArtDexFileLoader::OpenCommon(base, size, ...)└── DexFileLoader::OpenCommon(base, size, ...)└── new StandardDexFile(base, location, ...): DexFile(base, location, ...)└── new CompactDexFile(base, location, ...): DexFile(base, location, ...)

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/standard_dex_file.h;l=100

https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/libdexfile/dex/dex_file.cc;l=96

通用脱壳点

所以无论是 InMemoryDexClassLoader 还是 DexClassLoader 加载 Dex 最终都会走到以下方法:

ArtDexFileLoader::OpenCommon(base, size, ...)└── DexFileLoader::OpenCommon(base, size, ...)└── new StandardDexFile(base, location, ...): DexFile(base, location, ...)└── new CompactDexFile(base, location, ...): DexFile(base, location, ...)

在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump。

OAT 文件

Android 会在安装应用时,或首次运行时通过 dex2oat 将 .dex 文件转换为 .oat 文件。

OAT 文件是 Android Runtime(ART)生成的优化后的本地代码文件,其全称是 Optimized Android executable。

OAT 文件主要用于:

  • 加速应用启动时间

  • 减少运行时 JIT 编译压力

  • 节省运行时的电量和内存资源

一个 .oat 文件大致包含以下几个部分:

部分描述
Header文件头信息,包括版本、校验等
Dex 文件副本一个或多个原始 .dex 文件的副本
ELF 可执行体编译后的机器代码,和设备架构相关(ARM/ARM64/x86 等)
VMap Table虚拟寄存器映射表,用于调试和异常恢复
OatMethodData每个方法的元数据(偏移、编译类型等)

根据 Android 版本和架构不同,OAT 文件通常存储在以下目录:

/data/app/<package>/oat/arm64/base.odex
/system/framework/boot.oat
/apex/com.android.art/javalib/<*.oat>

找不到 OpenCommon

使用 Frida list 一下 art 中的函数

list_module_functions.js

function listAllFunctions(moduleName) {const baseAddr = Module.findBaseAddress(moduleName);if (!baseAddr) {console.error(`[-] ${moduleName} not found.`);return;}console.log(`[+] ${moduleName} base address:`, baseAddr);const symbols = Module.enumerateSymbolsSync(moduleName);let count = 0;for (let sym of symbols) {if (sym.type === 'function') {console.log(`[${count}]`, sym.address, sym.name);count++;}}console.log(`[*] Total function symbols found in ${moduleName}:`, count);
}// 列出 libart.so 的所有函数
setImmediate(function () {listAllFunctions("libart.so");
});

执行脚本

frida -H 127.0.0.1:1234  -F -l list_module_functions.js -o log.txt

发现并没有 OpenCommon(LineageOS 17.1,Android 10)

word/media/image7.png

进入 adb shell,执行下面命令得到 APP 的 pid 为16418

pidof pidof com.cyrus.example

查看该进程加载的 libart.so 在什么位置

cat /proc/16418/maps | grep libart.so

输出如下:

7a27617000-7a27744000 r--p 00000000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so
7a27744000-7a27bcf000 --xp 0012d000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so
7a27bcf000-7a27bd2000 rw-p 005b8000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so
7a27bd2000-7a27be3000 r--p 005bb000 103:1d 313                           /apex/com.android.runtime/lib64/libart.so

把 libart.so 拉取到本地

adb pull /apex/com.android.runtime/lib64/libart.so

使用 IDA 打开 libart.so,确实没有 OpenCommon 函数

word/media/image8.png

找到 OpenCommon

编写一个 Frida 脚本,遍历所有模块的符号,筛选出函数名中包含 “OpenCommon” 或 “DexFileLoader” 的符号,并打印出来(包括模块名、符号名、地址)

find_symbols.js

// Frida 脚本:查找所有模块中包含 "OpenCommon" 或 "DexFileLoader" 的函数符号
function scanModulesForKeywords(keywords) {const modules = Process.enumerateModules();keywords = keywords.map(k => k.toLowerCase());for (const module of modules) {try {const symbols = Module.enumerateSymbols(module.name);for (const symbol of symbols) {if (symbol.type === 'function') {const lowerName = symbol.name.toLowerCase();if (keywords.some(k => lowerName.includes(k))) {console.log(`[+] ${module.name} -> ${symbol.name} @ ${symbol.address}`);}}}} catch (e) {// 某些模块无法枚举,忽略}}
}setImmediate(() => {console.log("[*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ...");scanModulesForKeywords(["OpenCommon", "DexFileLoader"]);console.log("[*] Done.");
});// frida -H 127.0.0.1:1234  -F -l find_symbols.js -o log.txt

输出如下:

[*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ...
[+] linker64 -> __dl__ZN3art13DexFileLoaderD2Ev @ 0x7aadeac318
[+] linker64 -> __dl__ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aadf00370
[+] linker64 -> __dl__ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x7aadf001c0
[+] linker64 -> __dl__ZN3art13DexFileLoaderD0Ev @ 0x7aadecaef8
[+] linker64 -> __dl__ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x7aadf00860
[+] linker64 -> __dl__ZNK3art13DexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aadf00288
[+] linker64 -> __dl__ZNK3art13DexFileLoader21OpenOneDexFileFromZipERKNS_13DexZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSC_ @ 0x7aadf01128
[+] linker64 -> __dl__ZNK3art13DexFileLoader22OpenAllDexFilesFromZipERKNS_13DexZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISJ_EEEENS8_ISM_EEEE @ 0x7aadf00cb8
[+] linker64 -> __dl__ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aadf002b0
[+] linker64 -> __dl__ZNK3art13DexFileLoader7OpenAllEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISI_EEEENS7_ISL_EEEE @ 0x7aadf00918
[+] libart.so -> _ZN3art13DexFileLoader15GetBaseLocationEPKc @ 0x7a277a1c80
[+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenEPKcRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISG_EEEENS7_ISJ_EEEE @ 0x0
[+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x0
[+] libart.so -> _ZN3art13DexFileLoader23GetDexCanonicalLocationEPKc @ 0x0
[+] libart.so -> _ZN3art13DexFileLoader18IsMultiDexLocationEPKc @ 0x0
[+] libart.so -> _ZNK3art16ArtDexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x0
[+] libart.so -> _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x0
[+] libart.so -> _ZNK3art16ArtDexFileLoader7OpenZipEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x0
[+] libart.so -> _ZN3art13DexFileLoader12IsMagicValidEPKh @ 0x0
[+] libart.so -> _ZN3art13DexFileLoader22IsVersionAndMagicValidEPKh @ 0x0
[+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x0
[+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjONS_6MemMapEbbPS7_ @ 0x0
[+] libart.so -> _ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x0
[+] libdexfile.so -> _ZN3art13DexFileLoaderD2Ev @ 0x7aac641380
[+] libdexfile.so -> _ZN3art16ArtDexFileLoaderD0Ev @ 0x7aac641388
[+] libdexfile.so -> _ZN3art13DexFileLoader15GetBaseLocationEPKc @ 0x7aac649a78
[+] libdexfile.so -> _ZN3art13DexFileLoaderD0Ev @ 0x7aac641388
[+] libdexfile.so -> _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28
[+] libdexfile.so -> _ZN3art13DexFileLoader12IsMagicValidEj @ 0x7aac6494f8
[+] libdexfile.so -> _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x7aac649668
[+] libdexfile.so -> _ZN3art13DexFileLoader25GetMultiDexClassesDexNameEm @ 0x7aac649620
[+] libdexfile.so -> _ZN3art16ArtDexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS_13DexFileLoader12VerifyResultE @ 0x7aac63fc88
[+] libdexfile.so -> _ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x7aac64a118
[+] libdexfile.so -> _ZNK3art13DexFileLoader7OpenAllEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISI_EEEENS7_ISL_EEEE @ 0x7aac64a1d0
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader13OpenWithMagicEjiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac640138
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aac63f020
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader21OpenOneDexFileFromZipERKNS_10ZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPSC_PNS_22DexFileLoaderErrorCodeE @ 0x7aac640b60
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader22OpenAllDexFilesFromZipERKNS_10ZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISH_EEEENS8_ISK_EEEE @ 0x7aac6406f0
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenEPKcRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISG_EEEENS7_ISJ_EEEE @ 0x7aac640058
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aac63fae8
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjONS_6MemMapEbbPS7_ @ 0x7aac63fd10
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac6403c8
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader7OpenDexEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbbPS7_ @ 0x7aac6405e0
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader7OpenZipEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac640490
[+] libdexfile.so -> _ZNK3art16ArtDexFileLoader8OpenFileEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbbPS7_ @ 0x7aac63f608
[+] libdexfile.so -> _ZN3art13DexFileLoader12IsMagicValidEPKh @ 0x7aac649570
[+] libdexfile.so -> _ZN3art13DexFileLoader18IsMultiDexLocationEPKc @ 0x7aac649600
[+] libdexfile.so -> _ZN3art13DexFileLoader22IsVersionAndMagicValidEPKh @ 0x7aac6495a8
[+] libdexfile.so -> _ZN3art13DexFileLoader23GetDexCanonicalLocationEPKc @ 0x7aac649730
[+] libdexfile.so -> _ZNK3art13DexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aac649b40
[+] libdexfile.so -> _ZNK3art13DexFileLoader21OpenOneDexFileFromZipERKNS_13DexZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSC_ @ 0x7aac64a9e0
[+] libdexfile.so -> _ZNK3art13DexFileLoader22OpenAllDexFilesFromZipERKNS_13DexZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISJ_EEEENS8_ISM_EEEE @ 0x7aac64a570
[+] libdexfile.so -> _ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aac649b68
[+] libprofile.so -> _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x0
[*] Done.

找到 DexFileLoader::OpenCommon 原来在 libdexfile.so

[+] libdexfile.so -> _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28

所以 art/runtime/dex_file_loader.cc(DexFileLoader::OpenCommon 实现)最终被编译进 libdexfile.so,而不是 libart.so。

OpenCommon 脱壳

使用 frida hook libdexfile.so 中的 DexFileLoader::OpenCommom 函数并拿到参数 base、size 和 location,把 dex 从内存中 dump 到 /sdcard/Android/data/pkgName/dump_dex 目录下:

function getProcessName() {var openPtr = Module.getExportByName('libc.so', 'open');var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);var readPtr = Module.getExportByName("libc.so", "read");var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);var closePtr = Module.getExportByName('libc.so', 'close');var close = new NativeFunction(closePtr, 'int', ['int']);var path = Memory.allocUtf8String("/proc/self/cmdline");var fd = open(path, 0);if (fd != -1) {var buffer = Memory.alloc(0x1000);var result = read(fd, buffer, 0x1000);close(fd);result = ptr(buffer).readCString();return result;}return "-1";
}function mkdir(path) {var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']);var opendirPtr = Module.getExportByName('libc.so', 'opendir');var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']);var closedirPtr = Module.getExportByName('libc.so', 'closedir');var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']);var cPath = Memory.allocUtf8String(path);var dir = opendir(cPath);if (dir != 0) {closedir(dir);return 0;}mkdir(cPath, 755);chmod(path);
}function chmod(path) {var chmodPtr = Module.getExportByName('libc.so', 'chmod');var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);var cPath = Memory.allocUtf8String(path);chmod(cPath, 755);
}function readStdString(str) {const isTiny = (str.readU8() & 1) === 0;if (isTiny) {return str.add(1).readUtf8String();}return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}function findSymbolInLib(libname, keywordList) {const libBase = Module.findBaseAddress(libname);if (!libBase) {console.error("[-] Library not loaded:", libname);return null;}const matches = [];const symbols = Module.enumerateSymbolsSync(libname);for (const sym of symbols) {if (keywordList.every(k => sym.name.includes(k))) {matches.push(sym);}}if (matches.length === 0) {console.error("[-] No matching symbol found for keywords:", keywordList);return null;}const target = matches[0]; // 取第一个匹配的console.log("[+] Found symbol:", target.name, " @ ", target.address);return target.address;
}function dumpDexToFile(filename, base, size) {// packageNamevar processName = getProcessName();if (processName != "-1") {const dir = "/sdcard/Android/data/" + processName + "/dump_dex";const fullPath = dir + "/" + filename.replace(/\//g, "_").replace(/!/g, "_");// 创建目录mkdir(dir);// dump dexvar fd = new File(fullPath, "wb");if (fd && fd != null) {var dex_buffer = ptr(base).readByteArray(size);fd.write(dex_buffer);fd.flush();fd.close();console.log("[+] Dex dumped to", fullPath);}}
}function hookDexFileLoaderOpenCommon() {const addr = findSymbolInLib("libdexfile.so", ["DexFileLoader", "OpenCommon"]);if (!addr) return;Interceptor.attach(addr, {onEnter(args) {const base = args[0]; // const uint8_t* baseconst size = args[1].toInt32(); // size_t sizeconst location_ptr = args[4]; // const std::string& locationconst location = readStdString(location_ptr);console.log("\n[*] DexFileLoader::OpenCommon called");console.log("    base       :", base);console.log("    size       :", size);console.log("    location   :", location);// 文件名const filename = location.split("/").pop();// 魔数var magic = ptr(base).readCString();console.log("    magic      :", magic)// dex 格式校验if (magic.indexOf("dex") !== -1) {dumpDexToFile(filename, base, size)}},onLeave(retval) {}});
}setImmediate(hookDexFileLoaderOpenCommon);

列出当前设备所有进程并通过 findstr 过滤出目标进程

frida-ps -H 127.0.0.1:1234 | findstr cyrus

执行脚本开始 dump

frida -H 127.0.0.1:1234 -l dump_dex_from_open_common.js -f com.cyrus.example

输出如下:

Spawning `com.cyrus.example`...                                         
[+] Found: _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28
Spawned `com.cyrus.example`. Use %resume to let the main thread start executing!
[Remote::com.cyrus.example]-> %resume
[Remote::com.cyrus.example]-> 
================= DexFileLoader::OpenCommon =================
base: 0x79b9fe106c
size: 1602672
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk
================= DexFileLoader::OpenCommon =================
base: 0x79ba1684e0
size: 1800
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes2.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes2.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba168bec
size: 155888
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes3.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes3.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba18ece0
size: 8904
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes4.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes4.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba190fac
size: 1288
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes5.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes5.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba1914b8
size: 2656
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes6.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes6.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba191f1c
size: 11824
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes7.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes7.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba194d50
size: 8720
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes8.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes8.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba196f64
size: 9472
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes9.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes9.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba199468
size: 8904
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes10.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes10.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba19b734
size: 9504
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes11.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes11.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba19dc58
size: 1632
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes12.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes12.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba19e2bc
size: 800
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes13.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes13.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba19e5e0
size: 10328
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes14.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes14.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba1a0e3c
size: 3016
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes15.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes15.dex
================= DexFileLoader::OpenCommon =================
base: 0x79ba1a1a08
size: 1205136
location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes16.dex
magic :  cdex001
processName: com.cyrus.example
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes16.dex

可以看到 dex 已经 dump 到 sdcard 了

word/media/image9.png

使用下面的 adb pull 命令,一次性将设备上的整个 dump_dex 目录拉取到本地:

adb pull /sdcard/Android/data/com.cyrus.example/dump_dex ./dumped_dex

但是日志输出的 magic 可以看到都是 cdex001,cdex 文件是不可以直接通过 dex 反编译工具反编译的

word/media/image10.png

禁止加载 cdex

Android 9 引入 CompactDex(.cdex,magic 为 cdex001),是 DEX 的压缩优化版本,导致 dump 后无法直接反编译。

优化后的 dex/cdex 通常存放在:

/data/app/package_name/oat/arm64/base.odex
/data/app/package_name/oat/arm64/base.vdex

在 Android 9(Pie)中,APP 的 .cdex 文件 是由 dex2oat 优化生成的,通常以 odex, vdex 或直接优化后的 .art 文件形式存在。

进入 adb shell 找到 目标app 存放 oat 文件的路径并删除所有 oat 文件

wayne:/sdcard/Android/data/com.cyrus.example/dump_dex # cd /data/app
wayne:/data/app # ls
com.android.chrome-b1d3YEy1eVrwwjPOa1oq5A==       com.iflytek.inputmethod-s1r9JFv0-eKNskzHyrh_vQ==
com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==        com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==
com.cyrus.example.plugin-YsXrxPvfWYdsWHxFKjcusw== com.tencent.mm-ql7ajyK9JqKXli5pgu88nw==
com.cyrus.example.test-R06ZNyf5doqJFOcZ6EaYHQ==   com.xingin.xhs-HeYr1dfB-rU7NjxJiLiDeg==
wayne:/data/app # cd com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==
wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== # ls
base.apk lib oat
wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== # cd oat
wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat # ls
arm64
wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat # cd arm64/
wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 # ls
base.art base.odex base.vdex
wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 # rm -rf *

重新执行 frida 脚本 dump dex,从输出可以看到 dump 下来的 dex 魔数都是 dex 039 / dex 035 (标准 Dex 文件的魔数)不是 cdex001,可以直接用 jadx 去反编译了。

Spawning `com.shizhuang.duapp`...                                       
[+] Found symbol: _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE  @  0x7aac649c28
Spawned `com.shizhuang.duapp`. Use %resume to let the main thread start executing!
[Remote::com.shizhuang.duapp]-> %resume
[Remote::com.shizhuang.duapp]->
[*] DexFileLoader::OpenCommon calledbase       : 0x7a1d08e02csize       : 450032location   : /system/framework/org.apache.http.legacy.jarmagic      : dex
039
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/org.apache.http.legacy.jar[*] DexFileLoader::OpenCommon calledbase       : 0x7a1d08e02csize       : 450032location   : /system/framework/org.apache.http.legacy.jarmagic      : dex
039
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/org.apache.http.legacy.jar[*] DexFileLoader::OpenCommon calledbase       : 0x79bbd5c000size       : 8681372location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apkmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk[*] DexFileLoader::OpenCommon calledbase       : 0x79ba491000size       : 12888744location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes2.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes2.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b928e000size       : 12592256location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes3.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes3.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b86e8000size       : 12213596location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes4.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes4.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b7cc2000size       : 10637856location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes5.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes5.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b74d1000size       : 8324572location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes6.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes6.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b71b1000size       : 3273924location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes7.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes7.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b69e3000size       : 8183732location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes8.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes8.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b5e72000size       : 11994176location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes9.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes9.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b53d5000size       : 11125808location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes10.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes10.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b4815000size       : 12319700location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes11.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes11.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b3c59000size       : 12300396location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes12.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes12.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b3057000size       : 12587972location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes13.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes13.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b24d1000size       : 12081268location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes14.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes14.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b18cf000size       : 12590752location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes15.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes15.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79b179b000size       : 1260244location   : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes16.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes16.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79a39f57fcsize       : 3782924location   : /system/product/app/webview/webview.apkmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk[*] DexFileLoader::OpenCommon calledbase       : 0x7a11ec6138size       : 77880location   : /system/product/app/webview/webview.apk!classes2.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk_classes2.dex[*] DexFileLoader::OpenCommon calledbase       : 0x79a39f57fcsize       : 3782924location   : /system/product/app/webview/webview.apkmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk[*] DexFileLoader::OpenCommon calledbase       : 0x7a11ec6138size       : 77880location   : /system/product/app/webview/webview.apk!classes2.dexmagic      : dex
035
[+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk_classes2.dex

jadx 反编译 dex

使用 jadx 反编译 dex。

jadx 项目地址:https://github.com/skylot/jadx

word/media/image11.png

jadx 默认缓存目录

C:\Users\$USERNAME\AppData\Local\skylot\jadx\cache\projects

DexFile 脱壳

找到 CompactDexFile 构造函数方法符号信息如下:

[+] libdexfile.so -> _ZN3art14CompactDexFileC1EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8
[+] libdexfile.so -> _ZN3art14CompactDexFileC2EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8

hook CompactDexFile 和 StandardDexFile 的构造函数拿到 base、size 和 location 并 dump dex。

dump_dex_from_dex_file.js

function getProcessName() {var openPtr = Module.getExportByName('libc.so', 'open');var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']);var readPtr = Module.getExportByName("libc.so", "read");var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]);var closePtr = Module.getExportByName('libc.so', 'close');var close = new NativeFunction(closePtr, 'int', ['int']);var path = Memory.allocUtf8String("/proc/self/cmdline");var fd = open(path, 0);if (fd != -1) {var buffer = Memory.alloc(0x1000);var result = read(fd, buffer, 0x1000);close(fd);result = ptr(buffer).readCString();return result;}return "-1";
}function mkdir(path) {var mkdirPtr = Module.getExportByName('libc.so', 'mkdir');var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']);var opendirPtr = Module.getExportByName('libc.so', 'opendir');var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']);var closedirPtr = Module.getExportByName('libc.so', 'closedir');var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']);var cPath = Memory.allocUtf8String(path);var dir = opendir(cPath);if (dir != 0) {closedir(dir);return 0;}mkdir(cPath, 755);chmod(path);
}function chmod(path) {var chmodPtr = Module.getExportByName('libc.so', 'chmod');var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']);var cPath = Memory.allocUtf8String(path);chmod(cPath, 755);
}function readStdString(str) {const isTiny = (str.readU8() & 1) === 0;if (isTiny) {return str.add(1).readUtf8String();}return str.add(2 * Process.pointerSize).readPointer().readUtf8String();
}function findSymbolInLib(libname, keywordList) {const libBase = Module.findBaseAddress(libname);if (!libBase) {console.error("[-] Library not loaded:", libname);return null;}const matches = [];const symbols = Module.enumerateSymbolsSync(libname);for (const sym of symbols) {if (keywordList.every(k => sym.name.includes(k))) {matches.push(sym);}}if (matches.length === 0) {console.error("[-] No matching symbol found for keywords:", keywordList);return null;}const target = matches[0]; // 取第一个匹配的console.log("[+] Found symbol:", target.name, " @ ", target.address);return target.address;
}function dumpDexToFile(filename, base, size) {// packageNamevar processName = getProcessName();if (processName != "-1") {const dir = "/sdcard/Android/data/" + processName + "/dump_dex";const fullPath = dir + "/" + filename.replace(/\//g, "_").replace(/!/g, "_");// 创建目录mkdir(dir);// dump dexvar fd = new File(fullPath, "wb");if (fd && fd != null) {var dex_buffer = ptr(base).readByteArray(size);fd.write(dex_buffer);fd.flush();fd.close();console.log("[+] Dex dumped to", fullPath);}}
}function hookCompactDexFile() {const addr = findSymbolInLib("libdexfile.so", ["CompactDexFile", "C1"]);if (!addr) return;Interceptor.attach(addr, {onEnter(args) {const base = args[1];const size = args[2].toInt32();const data_base = args[3];const data_size = args[4].toInt32();const location_ptr = args[5];const location = readStdString(location_ptr);console.log("\n[*] CompactDexFile constructor called");console.log("    this       :", args[0]);console.log("    base       :", base);console.log("    size       :", size);console.log("    data_base  :", data_base);console.log("    data_size  :", data_size);console.log("    location   :", location);// 文件名const filename = location.split("/").pop();// 魔数var magic = ptr(base).readCString();console.log("    magic      :", magic)// dex 格式校验if (magic.indexOf("dex") !== -1) {dumpDexToFile(filename, base, size)}}});
}function hookStandardDexFile() {const addr = findSymbolInLib("libdexfile.so", ["StandardDexFile", "C1"]);if (!addr) return;Interceptor.attach(addr, {onEnter(args) {const base = args[1];const size = args[2].toInt32();const data_base = args[3];const data_size = args[4].toInt32();const location_ptr = args[5];const location = readStdString(location_ptr);console.log("\n[*] StandardDexFile constructor called");console.log("    this       :", args[0]);console.log("    base       :", base);console.log("    size       :", size);console.log("    data_base  :", data_base);console.log("    data_size  :", data_size);console.log("    location   :", location);// 文件名const filename = location.split("/").pop();// 魔数var magic = ptr(base).readCString();console.log("    magic      :", magic)// dex 格式校验if (magic.indexOf("dex") !== -1) {dumpDexToFile(filename, base, size)}}});
}setImmediate(function () {hookCompactDexFile()hookStandardDexFile()
});

执行脚本:

frida -H 127.0.0.1:1234 -l dump_dex_from_dex_file.js -f com.cyrus.example

输出如下:

Spawning `com.cyrus.example`...                                         
[+] Found symbol: _ZN3art14CompactDexFileC1EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE  @  0x7aac6420e8
[-] No matching symbol found for keywords: StandardDexFile,C1
Spawned `com.cyrus.example`. Use %resume to let the main thread start executing!
[Remote::com.cyrus.example]-> %resume
[Remote::com.cyrus.example]->
[*] CompactDexFile constructor calledthis       : 0x7aacac0720base       : 0x79b9fe206csize       : 1602672data_base  : 0x79ba2c8d98data_size  : 14765976location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apkmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk[*] CompactDexFile constructor calledthis       : 0x7aacac0800base       : 0x79ba1694e0size       : 1800data_base  : 0x79ba2c8d98data_size  : 14770144location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes2.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes2.dex[*] CompactDexFile constructor calledthis       : 0x7aacac08e0base       : 0x79ba169becsize       : 155888data_base  : 0x79ba2c8d98data_size  : 15120528location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes3.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes3.dex[*] CompactDexFile constructor calledthis       : 0x7aacac09c0base       : 0x79ba18fce0size       : 8904data_base  : 0x79ba2c8d98data_size  : 15155776location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes4.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes4.dex[*] CompactDexFile constructor calledthis       : 0x7aacac0aa0base       : 0x79ba191facsize       : 1288data_base  : 0x79ba2c8d98data_size  : 15158304location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes5.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes5.dex[*] CompactDexFile constructor calledthis       : 0x7aacac0b80base       : 0x79ba1924b8size       : 2656data_base  : 0x79ba2c8d98data_size  : 15165016location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes6.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes6.dex[*] CompactDexFile constructor calledthis       : 0x7aacac0c60base       : 0x79ba192f1csize       : 11824data_base  : 0x79ba2c8d98data_size  : 15211952location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes7.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes7.dex[*] CompactDexFile constructor calledthis       : 0x7aacac0d40base       : 0x79ba195d50size       : 8720data_base  : 0x79ba2c8d98data_size  : 15242288location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes8.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes8.dex[*] CompactDexFile constructor calledthis       : 0x7a175fe260base       : 0x79ba197f64size       : 9472data_base  : 0x79ba2c8d98data_size  : 15276888location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes9.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes9.dex[*] CompactDexFile constructor calledthis       : 0x7a175fe340base       : 0x79ba19a468size       : 8904data_base  : 0x79ba2c8d98data_size  : 15314648location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes10.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes10.dex[*] CompactDexFile constructor calledthis       : 0x7a175fe420base       : 0x79ba19c734size       : 9504data_base  : 0x79ba2c8d98data_size  : 15346672location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes11.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes11.dex[*] CompactDexFile constructor calledthis       : 0x7a175fe500base       : 0x79ba19ec58size       : 1632data_base  : 0x79ba2c8d98data_size  : 15349816location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes12.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes12.dex[*] CompactDexFile constructor calledthis       : 0x7a175fe5e0base       : 0x79ba19f2bcsize       : 800data_base  : 0x79ba2c8d98data_size  : 15350936location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes13.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes13.dex[*] CompactDexFile constructor calledthis       : 0x7a175fe6c0base       : 0x79ba19f5e0size       : 10328data_base  : 0x79ba2c8d98data_size  : 15401760location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes14.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes14.dex[*] CompactDexFile constructor calledthis       : 0x7a176a4fc0base       : 0x79ba1a1e3csize       : 3016data_base  : 0x79ba2c8d98data_size  : 15409648location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes15.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes15.dex[*] CompactDexFile constructor calledthis       : 0x7a176a50a0base       : 0x79ba1a2a08size       : 1205136data_base  : 0x79ba2c8d98data_size  : 22612744location   : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes16.dexmagic      : cdex001
[+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes16.dex

把 dex 文件拉取到本地:

adb pull /sdcard/Android/data/com.cyrus.example/dump_dex ./dumped_dex

使用命令行工具 compact_dex_converter 把 cdex(Compact Dex)文件转换为标准 .dex 文件。

https://github.com/anestisb/vdexExtractor#compact-dex-converter

dex2oat 脱壳

dex2oat 的流程也可以进行脱壳。

当安装 APK 时,如果需要 ahead-of-time (AOT) 编译,installd 会调用 dex2oat:

word/media/image12.png
https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:frameworks/native/cmds/installd/dexopt.cpp;l=306

进入 dex2oat.cc 的 main(),在 dex2oat::ReturnCode Setup() 方法中 将 dex 注册到 VerificationResults 时候可以拿到 dex_file 对象,这里也是一个很好的脱壳点。

verification_results_->AddDexFile(dex_file);

word/media/image13.png
https://cs.android.com/android/platform/superproject/+/android-10.0.0_r47:art/dex2oat/dex2oat.cc;l=1685

完整源码

开源地址:https://github.com/CYRUS-STUDIO/frida_dex_dump

相关文章:

  • ART环境下dex加载流程分析及frida dump dex方案

  • 拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点

http://www.xdnf.cn/news/3805.html

相关文章:

  • 【ArcGIS微课1000例】0145:如何按照自定义形状裁剪数据框?
  • 学习黑客Linux权限
  • 【中间件】brpc_基础_用户态线程中断
  • LeetCode每日一题5.4
  • 架构思维:利用全量缓存架构构建毫秒级的读服务
  • 2001-2023年 上市公司-企业广告支出数据-社科数据
  • 使用宝塔面板、青龙面板实现定时推送功能
  • 【数据结构】稀疏矩阵的快速转置
  • 单细胞测序数据分析试验设计赏析(二)
  • Android 输入控件事件使用示例
  • 信息系统监理师第二版教材模拟题第一组(含解析)
  • HTML学习笔记(7)
  • PostgreSQL 的 ANALYZE 命令
  • PostgreSQL 查看索引碎片的方法
  • 论文阅读笔记——STDArm
  • PostgreSQL 判断索引是否重建过的方法
  • 4电池_基于开关电容的均衡
  • Ubuntu 系统上广受好评的浏览器推荐
  • 蘑菇管理——AI与思维模型【94】
  • 【翻译、转载】使用 LLM 构建 MCP
  • 【五一培训】Day 3
  • 机器学习+多目标优化的算法如何设计?
  • AI跑得快,MCP来加速——模型计算平台在训练与推理中的硬核作用
  • 位图的实现和拓展
  • P1603 斯诺登密码详解
  • 【项目篇之统一内存操作】仿照RabbitMQ模拟实现消息队列
  • Android运行时ART加载类和方法的过程分析
  • Python-Django系列—视图
  • 8.2 GitHub企业级PDF报告生成实战:ReportLab高级技巧与性能优化全解析
  • BUUCTF——Fake XML cookbook