├── .gitignore ├── README.md ├── dump_dex_from_dex_file.js ├── dump_dex_from_open_common.js ├── find_symbols.js └── list_module_functions.js /.gitignore: -------------------------------------------------------------------------------- 1 | *.so 2 | *.id0 3 | *.id1 4 | *.id2 5 | *.nam 6 | *.til 7 | log.txt 8 | log.txt~ 9 | dumped_dex* 10 | *.dex 11 | *.odex 12 | *.vdex 13 | *.art 14 | *.apk 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | > 版权归作者所有,如有转发,请注明文章出处: 2 | 3 | # **DEF CON** 4 | 5 | 6 | 7 | DEF CON 是全球最大的计算机安全会议之一(极客的奥斯卡),自1993年6月起,每年在美国内华达州的拉斯维加斯举办。 8 | 9 | 10 | 11 | 官网:[https://media.defcon.org/](https://media.defcon.org/),DEF CON 黑客大会官方的媒体存档站点,提供历年 DEF CON 大会的公开演讲、幻灯片、视频、音频、代码示例和其他相关资源的免费下载。 12 | 13 | 14 | 15 | 在 DEF CON 25(2017 年)上,Check Point 的安全研究员 Slava Makkaveev 和 Avi Bashan 发表了题为《Unboxing Android: Everything You Wanted to Know About Android Packers》的演讲,深入探讨了 Android 应用程序中的加壳技术及其安全影响。 16 | 17 | 18 | 19 | 报告文件地址: 20 | 21 | [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) 22 | 23 | 24 | 25 | 对于国内加壳厂商也有分析 26 | 27 | 28 | 29 | ![word/media/image1.png](https://gitee.com/cyrus-studio/images/raw/master/85777d44ae42a2c7de18295d01712c01.png) 30 | 31 | 32 | DEF 的安全研究员选择的两个脱壳点:art::OpenAndReadMagic 和 DexFile::DexFile 33 | 34 | 35 | 36 | ![word/media/image2.png](https://gitee.com/cyrus-studio/images/raw/master/26614f34fc11c05f376afdac726283da.png) 37 | 38 | 39 | # **Unboxing Android** 40 | 41 | 42 | 43 | 在 DEF CON 25 (2017) 上,Avi Bashan 和 Slava Makkaveev 提出过一种非常实用的 Android 加壳脱壳技术: 44 | 45 | 46 | 47 | 通过修改 DexFile::DexFile() 构造函数和 OpenAndReadMagic() 方法,可以在应用运行时,拦截 DEX 文件加载过程,从而拿到已经解密后的内存数据,完成脱壳。 48 | 49 | 50 | 51 | ## **1. DexFile::DexFile 构造函数** 52 | 53 | 54 | 55 | 可以看到 DexFile::DexFile() 的构造函数参数里包含了: 56 | 57 | - const uint8_t* base —— DEX 在内存中的起始地址 58 | 59 | - size_t size —— DEX 的内存大小 60 | 61 | ``` 62 | DexFile::DexFile(const uint8_t* base, 63 | size_t size, 64 | const uint8_t* data_begin, 65 | size_t data_size, 66 | const std::string& location, 67 | uint32_t location_checksum, 68 | const OatDexFile* oat_dex_file, 69 | std::unique_ptr container, 70 | bool is_compact_dex) 71 | : begin_(base), 72 | size_(size), 73 | data_begin_(data_begin), 74 | data_size_(data_size), 75 | location_(location), 76 | location_checksum_(location_checksum), 77 | header_(reinterpret_cast(base)), 78 | string_ids_(reinterpret_cast(base + header_->string_ids_off_)), 79 | type_ids_(reinterpret_cast(base + header_->type_ids_off_)), 80 | field_ids_(reinterpret_cast(base + header_->field_ids_off_)), 81 | method_ids_(reinterpret_cast(base + header_->method_ids_off_)), 82 | proto_ids_(reinterpret_cast(base + header_->proto_ids_off_)), 83 | class_defs_(reinterpret_cast(base + header_->class_defs_off_)), 84 | method_handles_(nullptr), 85 | num_method_handles_(0), 86 | call_site_ids_(nullptr), 87 | num_call_site_ids_(0), 88 | hiddenapi_class_data_(nullptr), 89 | oat_dex_file_(oat_dex_file), 90 | container_(std::move(container)), 91 | is_compact_dex_(is_compact_dex), 92 | hiddenapi_domain_(hiddenapi::Domain::kApplication) { 93 | CHECK(begin_ != nullptr) << GetLocation(); 94 | CHECK_GT(size_, 0U) << GetLocation(); 95 | // Check base (=header) alignment. 96 | // Must be 4-byte aligned to avoid undefined behavior when accessing 97 | // any of the sections via a pointer. 98 | CHECK_ALIGNED(begin_, alignof(Header)); 99 | 100 | InitializeSectionsFromMapList(); 101 | } 102 | ``` 103 | [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) 104 | 105 | 106 | 107 | 108 | 109 | ![word/media/image3.png](https://gitee.com/cyrus-studio/images/raw/master/19c80e4b1d1d1e8ba82186864d060e97.png) 110 | 111 | 112 | 插入脱壳代码示例 113 | 114 | ``` 115 | // 打印当前 DEX 文件的位置 116 | LOG(WARNING) << "Dex File: Filename: " << location; 117 | 118 | // 判断这个 DEX 是不是从 APP 自身私有目录 加载的。 119 | // 因为系统自己的 framework、boot.oat 里的 DEX 都不是加壳 DEX,只想 dump 应用自己的 DEX。 120 | if (location.find("/data/data/") != std::string::npos) { 121 | LOG(WARNING) << "Dex File: OAT file unpacking launched"; 122 | 123 | // 创建一个新的文件,比如 /data/data/包名/xxx.dex__unpacked_oat。 124 | std::ofstream dst(location + "__unpacked_oat", std::ios::binary); 125 | // 把内存里的 DEX 数据完整写入磁盘。 126 | dst.write(reinterpret_cast(base), size); 127 | // 保存文件,完成脱壳。 128 | dst.close(); 129 | } else { 130 | LOG(WARNING) << "Dex File: OAT file unpacking not launched"; 131 | } 132 | ``` 133 | 134 | 135 | ## **2. DexFile::OpenAndReadMagic()** 136 | 137 | 138 | 139 | 这是辅助检查 DEX 文件头的函数。 140 | 141 | ``` 142 | File OpenAndReadMagic(const char* filename, uint32_t* magic, std::string* error_msg) { 143 | CHECK(magic != nullptr); 144 | File fd(filename, O_RDONLY, /* check_usage= */ false); 145 | if (fd.Fd() == -1) { 146 | *error_msg = StringPrintf("Unable to open '%s' : %s", filename, strerror(errno)); 147 | return File(); 148 | } 149 | if (!ReadMagicAndReset(fd.Fd(), magic, error_msg)) { 150 | StringPrintf("Error in reading magic from file %s: %s", filename, error_msg->c_str()); 151 | return File(); 152 | } 153 | return fd; 154 | } 155 | ``` 156 | [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) 157 | 158 | 159 | 160 | 161 | 162 | ![word/media/image4.png](https://gitee.com/cyrus-studio/images/raw/master/c4f07ae80a53cb21b2ed7bb7b2ee3f58.png) 163 | 164 | 165 | 插入脱壳代码示例 166 | 167 | ``` 168 | struct stat st; // 用于获取文件大小等信息 169 | 170 | // 打印当前正在处理的文件路径,便于调试和观察加载的 DEX 来源 171 | LOG(WARNING) << "File_magic: Filename: " << filename; 172 | 173 | // 只处理 /data/data 路径下的文件(即应用私有目录中的 dex 文件) 174 | // 这样可以避免处理系统 DEX,提高效率和准确性 175 | if (strstr(filename, "/data/data") != NULL) { 176 | LOG(WARNING) << "File_magic: DEX file unpacking launched"; 177 | 178 | // 构造输出文件路径,加上 "__unpacked_dex" 后缀 179 | char* fn_out = new char[PATH_MAX]; 180 | strcpy(fn_out, filename); 181 | strcat(fn_out, "__unpacked_dex"); 182 | 183 | // 创建输出文件,设置权限:用户可读写、用户组可读、其他人可读 184 | int fd_out = open(fn_out, O_WRONLY | O_CREAT | O_EXCL, 185 | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); 186 | 187 | // 如果获取原始 dex 文件信息成功(用于获取文件大小) 188 | if (!fstat(fd.get(), &st)) { 189 | // 使用 mmap 将整个 dex 文件映射到内存中 190 | char* addr = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd.get(), 0); 191 | 192 | // 将内存中的内容写入到新文件,完成磁盘级别的 dex dump 193 | int ret = write(fd_out, addr, st.st_size); 194 | 195 | // 可选防优化代码(保证 ret 被使用,防止编译器优化) 196 | ret += 1; 197 | 198 | // 解除映射,释放内存 199 | munmap(addr, st.st_size); 200 | } 201 | 202 | // 关闭输出文件,清理路径内存 203 | close(fd_out); 204 | delete[] fn_out; 205 | 206 | } else { 207 | // 如果不是应用私有路径下的文件,跳过处理 208 | LOG(WARNING) << "File_magic: DEX file unpacking not launched"; 209 | } 210 | ``` 211 | 212 | 213 | # **ART 下脱壳原理** 214 | 215 | 216 | 217 | ART 下常见的两个 dex 加载器:InMemoryDexClassLoader 和 DexClassLoader 218 | 219 | 220 | 221 | ## **InMemoryDexClassLoader 源码分析** 222 | 223 | 224 | 225 | InMemoryDexClassLoader 是 Android 8.0(API 级别 26)引入的一个类,用于动态加载内存中的 Dex。 226 | 227 | 228 | 229 | 调用示例: 230 | 231 | ``` 232 | // 假设 dexBytes 是你的 DEX 文件内容(可以通过解密获得) 233 | ByteBuffer buffer = ByteBuffer.wrap(dexBytes); 234 | 235 | // 创建 InMemoryDexClassLoader 236 | ClassLoader loader = new InMemoryDexClassLoader(buffer, ClassLoader.getSystemClassLoader()); 237 | 238 | // 通过反射加载类并调用方法 239 | Class clazz = loader.loadClass("com.example.MyHiddenClass"); 240 | Method m = clazz.getDeclaredMethod("secretMethod"); 241 | m.invoke(null); 242 | ``` 243 | 244 | 245 | InMemoryDexClassLoader 支持加载 内存中 一个或多个 Dex。源码如下: 246 | 247 | 248 | 249 | ![word/media/image5.png](https://gitee.com/cyrus-studio/images/raw/master/882c2733141420f573b56a73a7166520.png) 250 | [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) 251 | 252 | 253 | 254 | ### **openInMemoryDexFilesNative** 255 | 256 | 257 | 258 | Dex 加载过程如下,最终调用到 native 方法 openInMemoryDexFilesNative 259 | 260 | ``` 261 | InMemoryDexClassLoader(ByteBuffer[] dexBuffers, String librarySearchPath, ClassLoader parent) 262 | └── BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) 263 | └── DexPathList.initByteBufferDexPath(ByteBuffer[] dexFiles) 264 | └── DexFile(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements) 265 | └── DexFile.openInMemoryDexFiles(ByteBuffer[] bufs, ClassLoader loader, DexPathList.Element[] elements) 266 | └── DexFile.openInMemoryDexFilesNative(ByteBuffer[] bufs, byte[][] arrays, int[] starts, int[] ends, ClassLoader loader, DexPathList.Element[] elements) 267 | └── DexFile_openInMemoryDexFilesNative(JNIEnv* env, jclass, jobjectArray buffers, jobjectArray arrays, jintArray jstarts, jintArray jends, jobject class_loader, jobjectArray dex_elements) 268 | ``` 269 | [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) 270 | 271 | 272 | 273 | DexFile_openInMemoryDexFilesNative 中 调用 OpenDexFilesFromOat 方法 加载 Dex : 274 | 275 | ``` 276 | static jobject DexFile_openInMemoryDexFilesNative(JNIEnv* env, 277 | jclass, 278 | jobjectArray buffers, 279 | jobjectArray arrays, 280 | jintArray jstarts, 281 | jintArray jends, 282 | jobject class_loader, 283 | jobjectArray dex_elements) { 284 | jsize buffers_length = env->GetArrayLength(buffers); 285 | CHECK_EQ(buffers_length, env->GetArrayLength(arrays)); 286 | CHECK_EQ(buffers_length, env->GetArrayLength(jstarts)); 287 | CHECK_EQ(buffers_length, env->GetArrayLength(jends)); 288 | 289 | ScopedIntArrayAccessor starts(env, jstarts); 290 | ScopedIntArrayAccessor ends(env, jends); 291 | 292 | // Allocate memory for dex files and copy data from ByteBuffers. 293 | std::vector dex_mem_maps; 294 | dex_mem_maps.reserve(buffers_length); 295 | for (jsize i = 0; i < buffers_length; ++i) { 296 | jobject buffer = env->GetObjectArrayElement(buffers, i); 297 | jbyteArray array = reinterpret_cast(env->GetObjectArrayElement(arrays, i)); 298 | jint start = starts.Get(i); 299 | jint end = ends.Get(i); 300 | 301 | MemMap dex_data = AllocateDexMemoryMap(env, start, end); 302 | if (!dex_data.IsValid()) { 303 | DCHECK(Thread::Current()->IsExceptionPending()); 304 | return nullptr; 305 | } 306 | 307 | if (array == nullptr) { 308 | // Direct ByteBuffer 309 | uint8_t* base_address = reinterpret_cast(env->GetDirectBufferAddress(buffer)); 310 | if (base_address == nullptr) { 311 | ScopedObjectAccess soa(env); 312 | ThrowWrappedIOException("dexFileBuffer not direct"); 313 | return nullptr; 314 | } 315 | size_t length = static_cast(end - start); 316 | memcpy(dex_data.Begin(), base_address + start, length); 317 | } else { 318 | // ByteBuffer backed by a byte array 319 | jbyte* destination = reinterpret_cast(dex_data.Begin()); 320 | env->GetByteArrayRegion(array, start, end - start, destination); 321 | } 322 | 323 | dex_mem_maps.push_back(std::move(dex_data)); 324 | } 325 | 326 | // Hand MemMaps over to OatFileManager to open the dex files and potentially 327 | // create a backing OatFile instance from an anonymous vdex. 328 | std::vector error_msgs; 329 | const OatFile* oat_file = nullptr; 330 | std::vector> dex_files = 331 | Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(std::move(dex_mem_maps), 332 | class_loader, 333 | dex_elements, 334 | /*out*/ &oat_file, 335 | /*out*/ &error_msgs); 336 | return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs); 337 | } 338 | ``` 339 | [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) 340 | 341 | 342 | 343 | ### **OpenDexFilesFromOat** 344 | 345 | 346 | 347 | OpenDexFilesFromOat 调用 OpenDexFilesFromOat_Impl 加载 Dex 348 | 349 | ``` 350 | std::vector> OatFileManager::OpenDexFilesFromOat_Impl( 351 | std::vector&& dex_mem_maps, 352 | jobject class_loader, 353 | jobjectArray dex_elements, 354 | const OatFile** out_oat_file, 355 | std::vector* error_msgs) { 356 | ScopedTrace trace(__FUNCTION__); 357 | std::string error_msg; 358 | DCHECK(error_msgs != nullptr); 359 | 360 | // [1] 提取 Dex Header,用于后续校验 checksum、生成路径等 361 | const std::vector dex_headers = GetDexFileHeaders(dex_mem_maps); 362 | 363 | // [2] 生成临时匿名 dex/vdex 文件路径,获取 checksum 和路径 364 | uint32_t location_checksum; 365 | std::string dex_location; 366 | std::string vdex_path; 367 | bool has_vdex = OatFileAssistant::AnonymousDexVdexLocation( 368 | dex_headers, kRuntimeISA, &location_checksum, &dex_location, &vdex_path); 369 | 370 | // [3] 尝试打开 vdex 文件,并检查其中的 dex checksum 是否一致 371 | std::unique_ptr vdex_file = nullptr; 372 | if (has_vdex && OS::FileExists(vdex_path.c_str())) { 373 | vdex_file = VdexFile::Open(vdex_path, /*writable=*/false, /*low_4gb=*/false, 374 | /*unquicken=*/false, &error_msg); 375 | if (vdex_file == nullptr) { 376 | LOG(WARNING) << "Failed to open vdex " << vdex_path << ": " << error_msg; 377 | } else if (!vdex_file->MatchesDexFileChecksums(dex_headers)) { 378 | LOG(WARNING) << "Dex checksum mismatch: " << vdex_path; 379 | vdex_file.reset(nullptr); 380 | } 381 | } 382 | 383 | // [4] 加载内存中的 dex。若存在 vdex 且校验成功,可跳过结构校验 384 | std::vector> dex_files; 385 | for (size_t i = 0; i < dex_mem_maps.size(); ++i) { 386 | static constexpr bool kVerifyChecksum = true; 387 | const ArtDexFileLoader dex_file_loader; 388 | std::unique_ptr dex_file(dex_file_loader.Open( 389 | DexFileLoader::GetMultiDexLocation(i, dex_location.c_str()), 390 | location_checksum, 391 | std::move(dex_mem_maps[i]), 392 | /*verify=*/(vdex_file == nullptr) && Runtime::Current()->IsVerificationEnabled(), 393 | kVerifyChecksum, 394 | &error_msg)); 395 | if (dex_file != nullptr) { 396 | dex::tracking::RegisterDexFile(dex_file.get()); // 注册用于调试追踪 397 | dex_files.push_back(std::move(dex_file)); 398 | } else { 399 | error_msgs->push_back("Failed to open dex files from memory: " + error_msg); 400 | } 401 | } 402 | 403 | // [5] 若 vdex 不存在、加载失败,或 class_loader 为空,直接返回 dex_files 404 | if (vdex_file == nullptr || class_loader == nullptr || !error_msgs->empty()) { 405 | return dex_files; 406 | } 407 | 408 | // [6] 创建 ClassLoaderContext,确保之后的 oat 加载上下文一致 409 | std::unique_ptr context = ClassLoaderContext::CreateContextForClassLoader( 410 | class_loader, dex_elements); 411 | if (context == nullptr) { 412 | LOG(ERROR) << "Could not create class loader context for " << vdex_path; 413 | return dex_files; 414 | } 415 | DCHECK(context->OpenDexFiles(kRuntimeISA, "")) 416 | << "Context created from already opened dex files should not attempt to open again"; 417 | 418 | // [7] 检查 boot class path checksum 和 class loader context 是否匹配 419 | if (!vdex_file->MatchesBootClassPathChecksums() || 420 | !vdex_file->MatchesClassLoaderContext(*context.get())) { 421 | return dex_files; 422 | } 423 | 424 | // [8] 从 vdex 创建 OatFile 实例并注册 425 | std::unique_ptr oat_file(OatFile::OpenFromVdex( 426 | MakeNonOwningPointerVector(dex_files), 427 | std::move(vdex_file), 428 | dex_location)); 429 | DCHECK(oat_file != nullptr); 430 | VLOG(class_linker) << "Registering " << oat_file->GetLocation(); 431 | *out_oat_file = RegisterOatFile(std::move(oat_file)); 432 | 433 | return dex_files; 434 | } 435 | ``` 436 | [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) 437 | 438 | 439 | 440 | 在 Android 10 之前,InMemoryDexClassLoader 加载的 DEX 文件不会被编译为 OAT 文件,而是直接在解释模式下执行, 这也是它和 DexClassLoader 的区别。 441 | 442 | 443 | 444 | 从 Android 10 开始,InMemoryDexClassLoader 加载的 DEX 文件也会走 OAT 流程。 445 | 446 | 447 | 448 | DexFile_openInMemoryDexFilesNative → DexFile::DexFile 调用路径 449 | 450 | ``` 451 | DexFile_openInMemoryDexFilesNative(...) 452 | └── AllocateDexMemoryMap(...) 创建 dex_mem_maps 453 | └── Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(...) 454 | └── OatFileManager::OpenDexFilesFromOat(dex_mem_maps, class_loader, dex_elements, out_oat_file, error_msgs) 455 | └── OatFileManager::OpenDexFilesFromOat_Impl(...) 456 | └── ArtDexFileLoader::Open(location, location_checksum, map, verify, verify_checksum, error_msg) 457 | └── ArtDexFileLoader::OpenCommon(base, size, ...) 458 | └── DexFileLoader::OpenCommon(base, size, ...) 459 | └── new StandardDexFile(base, location, ...): DexFile(base, location, ...) 460 | └── new CompactDexFile(base, location, ...): DexFile(base, location, ...) 461 | ``` 462 | [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) 463 | 464 | 465 | 466 | ### **OpenCommon** 467 | 468 | 469 | 470 | 在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump 471 | 472 | ``` 473 | ArtDexFileLoader::Open(location, location_checksum, map, verify, verify_checksum, error_msg) 474 | └── ArtDexFileLoader::OpenCommon(base, size, ...) 475 | └── DexFileLoader::OpenCommon(base, size, ...) 476 | └── new StandardDexFile(base, location, ...): DexFile(base, location, ...) 477 | └── new CompactDexFile(base, location, ...): DexFile(base, location, ...) 478 | ``` 479 | [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) 480 | 481 | [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) 482 | 483 | [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) 484 | 485 | [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) 486 | 487 | 488 | 489 | ## **DexClassLoader 源码分析** 490 | 491 | 492 | 493 | DexClassLoader 可以加载任意路径下的 dex,或者 jar、apk、zip 文件(包含classes.dex)。 494 | 495 | 496 | 497 | 源码如下: 498 | 499 | 500 | 501 | ![word/media/image6.png](https://gitee.com/cyrus-studio/images/raw/master/05532442ff68769ffc74626f16ea0ad9.png) 502 | [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) 503 | 504 | 505 | 506 | ### **DexFile_openDexFileNative** 507 | 508 | 509 | 510 | DexClassLoader 最终是通过 JNI 调用 DexFile_openDexFileNative 来加载 Dex。 511 | 512 | 513 | 514 | 下面是从 Java 到 native 的完整调用路径(以 Android 10 为例): 515 | 516 | ``` 517 | DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) 518 | ↓ 519 | BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent) 520 | ↓ 521 | DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory, boolean isTrusted) 522 | ↓ 523 | DexPathList.makeDexElements(...) 524 | ↓ 525 | new DexFile(file, loader, elements) 526 | ↓ 527 | DexFile.openDexFile(fileName, null, 0, loader, elements) 528 | ↓ 529 | DexFile.openDexFileNative(sourceFile, outputFile, flags, loader, elements) 530 | ↓ 531 | DexFile_openDexFileNative(...) (native 层) 532 | ``` 533 | [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) 534 | 535 | 536 | 537 | DexFile_openDexFileNative 方法中调用 OpenDexFilesFromOat 方法生成 OAT 文件: 538 | 539 | ``` 540 | static jobject DexFile_openDexFileNative(JNIEnv* env, 541 | jclass, 542 | jstring javaSourceName, 543 | jstring javaOutputName ATTRIBUTE_UNUSED, 544 | jint flags ATTRIBUTE_UNUSED, 545 | jobject class_loader, 546 | jobjectArray dex_elements) { 547 | ScopedUtfChars sourceName(env, javaSourceName); 548 | if (sourceName.c_str() == nullptr) { 549 | return nullptr; 550 | } 551 | 552 | std::vector error_msgs; 553 | const OatFile* oat_file = nullptr; 554 | std::vector> dex_files = 555 | Runtime::Current()->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(), 556 | class_loader, 557 | dex_elements, 558 | /*out*/ &oat_file, 559 | /*out*/ &error_msgs); 560 | return CreateCookieFromOatFileManagerResult(env, dex_files, oat_file, error_msgs); 561 | } 562 | ``` 563 | [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) 564 | 565 | 566 | 567 | ### **OpenDexFilesFromOat** 568 | 569 | 570 | 571 | 和 InMemoryDexClassLoader 不同的是:这里传入的参数不是 MemMap,而是 const char* dex_location。 572 | 573 | 574 | 575 | OpenDexFilesFromOat 方法源码如下: 576 | 577 | ``` 578 | std::vector> OatFileManager::OpenDexFilesFromOat( 579 | const char* dex_location, 580 | jobject class_loader, 581 | jobjectArray dex_elements, 582 | const OatFile** out_oat_file, 583 | std::vector* error_msgs) { 584 | ScopedTrace trace(__FUNCTION__); 585 | CHECK(dex_location != nullptr); 586 | CHECK(error_msgs != nullptr); 587 | 588 | // 步骤 1: 确保未持有 mutator_lock,防止阻塞 GC 589 | Thread* const self = Thread::Current(); 590 | Locks::mutator_lock_->AssertNotHeld(self); 591 | Runtime* const runtime = Runtime::Current(); 592 | 593 | // 步骤 2: 构造 ClassLoaderContext(可能为空) 594 | std::unique_ptr context; 595 | if (class_loader == nullptr) { 596 | LOG(WARNING) << "Opening an oat file without a class loader. " 597 | << "Are you using the deprecated DexFile APIs?"; 598 | context = nullptr; 599 | } else { 600 | context = ClassLoaderContext::CreateContextForClassLoader(class_loader, dex_elements); 601 | } 602 | 603 | // 步骤 3: 构建 OatFileAssistant,用于操作 oat 和 dex 文件 604 | OatFileAssistant oat_file_assistant(dex_location, 605 | kRuntimeISA, 606 | !runtime->IsAotCompiler(), 607 | only_use_system_oat_files_); 608 | 609 | // 步骤 4: 获取磁盘上最优的 OAT 文件 610 | std::unique_ptr oat_file(oat_file_assistant.GetBestOatFile().release()); 611 | VLOG(oat) << "OatFileAssistant(" << dex_location << ").GetBestOatFile()=" 612 | << reinterpret_cast(oat_file.get()) 613 | << " (executable=" << (oat_file != nullptr ? oat_file->IsExecutable() : false) << ")"; 614 | 615 | const OatFile* source_oat_file = nullptr; 616 | CheckCollisionResult check_collision_result = CheckCollisionResult::kPerformedHasCollisions; 617 | std::string error_msg; 618 | 619 | // 步骤 5: 进行 collision 检查决定是否接受这个 oat 文件 620 | if ((class_loader != nullptr || dex_elements != nullptr) && oat_file != nullptr) { 621 | check_collision_result = CheckCollision(oat_file.get(), context.get(), &error_msg); 622 | bool accept_oat_file = AcceptOatFile(check_collision_result); 623 | 624 | // 检查结果为 false,判断是否启用 fallback 并记录警告信息 625 | if (!accept_oat_file) { 626 | if (runtime->IsDexFileFallbackEnabled()) { 627 | if (!oat_file_assistant.HasOriginalDexFiles()) { 628 | accept_oat_file = true; 629 | LOG(WARNING) << "Dex location " << dex_location << " does not seem to include dex file. " 630 | << "Allow oat file use. This is potentially dangerous."; 631 | } else { 632 | LOG(WARNING) << "Found duplicate classes, falling back to extracting from APK : " 633 | << dex_location; 634 | LOG(WARNING) << "NOTE: This wastes RAM and hurts startup performance."; 635 | } 636 | } else { 637 | if (!oat_file_assistant.HasOriginalDexFiles()) { 638 | accept_oat_file = true; 639 | } 640 | LOG(WARNING) << "Found duplicate classes, dex-file-fallback disabled, will be failing to " 641 | " load classes for " << dex_location; 642 | } 643 | 644 | LOG(WARNING) << error_msg; 645 | } 646 | 647 | // 步骤 6: 注册 oat 文件到 OatFileManager 648 | if (accept_oat_file) { 649 | VLOG(class_linker) << "Registering " << oat_file->GetLocation(); 650 | source_oat_file = RegisterOatFile(std::move(oat_file)); 651 | *out_oat_file = source_oat_file; 652 | } 653 | } 654 | 655 | std::vector> dex_files; 656 | 657 | // 步骤 7: 从 OAT 文件加载 dex 文件(如果成功加载了 oat) 658 | if (source_oat_file != nullptr) { 659 | bool added_image_space = false; 660 | 661 | if (source_oat_file->IsExecutable()) { 662 | ScopedTrace app_image_timing("AppImage:Loading"); 663 | 664 | std::unique_ptr image_space; 665 | if (ShouldLoadAppImage(check_collision_result, 666 | source_oat_file, 667 | context.get(), 668 | &error_msg)) { 669 | image_space = oat_file_assistant.OpenImageSpace(source_oat_file); 670 | } 671 | 672 | if (image_space != nullptr) { 673 | ScopedObjectAccess soa(self); 674 | StackHandleScope<1> hs(self); 675 | Handle h_loader( 676 | hs.NewHandle(soa.Decode(class_loader))); 677 | 678 | // 步骤 8: 尝试将 image space 添加到堆中 679 | if (h_loader != nullptr) { 680 | std::string temp_error_msg; 681 | { 682 | ScopedThreadSuspension sts(self, kSuspended); 683 | gc::ScopedGCCriticalSection gcs(self, 684 | gc::kGcCauseAddRemoveAppImageSpace, 685 | gc::kCollectorTypeAddRemoveAppImageSpace); 686 | ScopedSuspendAll ssa("Add image space"); 687 | runtime->GetHeap()->AddSpace(image_space.get()); 688 | } 689 | { 690 | ScopedTrace trace2(StringPrintf("Adding image space for location %s", dex_location)); 691 | added_image_space = runtime->GetClassLinker()->AddImageSpace(image_space.get(), 692 | h_loader, 693 | dex_elements, 694 | dex_location, 695 | &dex_files, 696 | &temp_error_msg); 697 | } 698 | if (added_image_space) { 699 | image_space.release(); 700 | for (const auto& dex_file : dex_files) { 701 | dex::tracking::RegisterDexFile(dex_file.get()); 702 | } 703 | } else { 704 | LOG(INFO) << "Failed to add image file " << temp_error_msg; 705 | dex_files.clear(); 706 | { 707 | ScopedThreadSuspension sts(self, kSuspended); 708 | gc::ScopedGCCriticalSection gcs(self, 709 | gc::kGcCauseAddRemoveAppImageSpace, 710 | gc::kCollectorTypeAddRemoveAppImageSpace); 711 | ScopedSuspendAll ssa("Remove image space"); 712 | runtime->GetHeap()->RemoveSpace(image_space.get()); 713 | } 714 | } 715 | } 716 | } 717 | } 718 | 719 | // 步骤 9: 如果未添加 image space,则从 oat 中手动加载 dex 文件 720 | if (!added_image_space) { 721 | DCHECK(dex_files.empty()); 722 | dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location); 723 | 724 | for (const auto& dex_file : dex_files) { 725 | dex::tracking::RegisterDexFile(dex_file.get()); 726 | } 727 | } 728 | 729 | // 步骤 10: 检查是否 dex 文件加载失败 730 | if (dex_files.empty()) { 731 | error_msgs->push_back("Failed to open dex files from " + source_oat_file->GetLocation()); 732 | } else { 733 | for (const std::unique_ptr& dex_file : dex_files) { 734 | OatDexFile::MadviseDexFile(*dex_file, MadviseState::kMadviseStateAtLoad); 735 | } 736 | } 737 | } 738 | 739 | // 步骤 11: OAT 加载失败,尝试从原始 dex 文件 fallback 加载 740 | if (dex_files.empty()) { 741 | if (oat_file_assistant.HasOriginalDexFiles()) { 742 | if (Runtime::Current()->IsDexFileFallbackEnabled()) { 743 | static constexpr bool kVerifyChecksum = true; 744 | const ArtDexFileLoader dex_file_loader; 745 | if (!dex_file_loader.Open(dex_location, 746 | dex_location, 747 | Runtime::Current()->IsVerificationEnabled(), 748 | kVerifyChecksum, 749 | &error_msg, 750 | &dex_files)) { 751 | LOG(WARNING) << error_msg; 752 | error_msgs->push_back("Failed to open dex files from " + std::string(dex_location) 753 | + " because: " + error_msg); 754 | } 755 | } else { 756 | error_msgs->push_back("Fallback mode disabled, skipping dex files."); 757 | } 758 | } else { 759 | error_msgs->push_back("No original dex files found for dex location " 760 | + std::string(dex_location)); 761 | } 762 | } 763 | 764 | // 步骤 12: JIT 启用时注册 dex 文件 765 | if (Runtime::Current()->GetJit() != nullptr) { 766 | ScopedObjectAccess soa(self); 767 | Runtime::Current()->GetJit()->RegisterDexFiles( 768 | dex_files, soa.Decode(class_loader)); 769 | } 770 | 771 | // 最终返回 dex 文件数组 772 | return dex_files; 773 | } 774 | ``` 775 | [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) 776 | 777 | 778 | 779 | ### **OpenCommon** 780 | 781 | 782 | 783 | DexFile_openDexFileNative → DexFile::DexFile 调用路径 784 | 785 | ``` 786 | DexFile_openDexFileNative(...) 787 | └── OatFileManager::OpenDexFilesFromOat(dex_location, class_loader, dex_elements, out_oat_file, error_msgs) 788 | └── ArtDexFileLoader::Open(filename, location, verify, verify_checksum, error_msg, dex_files) 789 | └── art::OpenAndReadMagic(...) 790 | └── ArtDexFileLoader::OpenWithMagic(...) 791 | └── ArtDexFileLoader::OpenFile(...) 792 | └── ArtDexFileLoader::OpenCommon(base, size, ...) 793 | └── DexFileLoader::OpenCommon(base, size, ...) 794 | └── new StandardDexFile(base, location, ...): DexFile(base, location, ...) 795 | └── new CompactDexFile(base, location, ...): DexFile(base, location, ...) 796 | ``` 797 | [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) 798 | 799 | [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) 800 | 801 | 802 | 803 | 在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump 804 | 805 | ``` 806 | ArtDexFileLoader::Open(filename, location, verify, verify_checksum, error_msg, dex_files) 807 | └── ArtDexFileLoader::OpenCommon(base, size, ...) 808 | └── DexFileLoader::OpenCommon(base, size, ...) 809 | └── new StandardDexFile(base, location, ...): DexFile(base, location, ...) 810 | └── new CompactDexFile(base, location, ...): DexFile(base, location, ...) 811 | ``` 812 | [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) 813 | 814 | [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) 815 | 816 | [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) 817 | 818 | 819 | 820 | # **通用脱壳点** 821 | 822 | 823 | 824 | 所以无论是 InMemoryDexClassLoader 还是 DexClassLoader 加载 Dex 最终都会走到以下方法: 825 | 826 | ``` 827 | ArtDexFileLoader::OpenCommon(base, size, ...) 828 | └── DexFileLoader::OpenCommon(base, size, ...) 829 | └── new StandardDexFile(base, location, ...): DexFile(base, location, ...) 830 | └── new CompactDexFile(base, location, ...): DexFile(base, location, ...) 831 | ``` 832 | 在这些关键 api 当中我们都可以拿到 dex 的起始地址和 size 来进行 dump。 833 | 834 | 835 | 836 | # **OAT 文件** 837 | 838 | 839 | 840 | Android 会在安装应用时,或首次运行时通过 dex2oat 将 .dex 文件转换为 .oat 文件。 841 | 842 | 843 | 844 | OAT 文件是 Android Runtime(ART)生成的优化后的本地代码文件,其全称是 Optimized Android executable。 845 | 846 | 847 | 848 | OAT 文件主要用于: 849 | 850 | - 加速应用启动时间 851 | 852 | - 减少运行时 JIT 编译压力 853 | 854 | - 节省运行时的电量和内存资源 855 | 856 | 857 | 858 | 一个 .oat 文件大致包含以下几个部分: 859 | 860 | | 部分 | 描述 | 861 | |--- | ---| 862 | | Header | 文件头信息,包括版本、校验等 | 863 | | Dex 文件副本 | 一个或多个原始 .dex 文件的副本 | 864 | | ELF 可执行体 | 编译后的机器代码,和设备架构相关(ARM/ARM64/x86 等) | 865 | | VMap Table | 虚拟寄存器映射表,用于调试和异常恢复 | 866 | | OatMethodData | 每个方法的元数据(偏移、编译类型等) | 867 | 868 | 869 | 根据 Android 版本和架构不同,OAT 文件通常存储在以下目录: 870 | 871 | ``` 872 | /data/app//oat/arm64/base.odex 873 | /system/framework/boot.oat 874 | /apex/com.android.art/javalib/<*.oat> 875 | ``` 876 | 877 | 878 | # **找不到 OpenCommon** 879 | 880 | 881 | 882 | 使用 Frida list 一下 art 中的函数 883 | 884 | 885 | 886 | list_module_functions.js 887 | 888 | ``` 889 | function listAllFunctions(moduleName) { 890 | const baseAddr = Module.findBaseAddress(moduleName); 891 | if (!baseAddr) { 892 | console.error(`[-] ${moduleName} not found.`); 893 | return; 894 | } 895 | 896 | console.log(`[+] ${moduleName} base address:`, baseAddr); 897 | 898 | const symbols = Module.enumerateSymbolsSync(moduleName); 899 | let count = 0; 900 | 901 | for (let sym of symbols) { 902 | if (sym.type === 'function') { 903 | console.log(`[${count}]`, sym.address, sym.name); 904 | count++; 905 | } 906 | } 907 | 908 | console.log(`[*] Total function symbols found in ${moduleName}:`, count); 909 | } 910 | 911 | // 列出 libart.so 的所有函数 912 | setImmediate(function () { 913 | listAllFunctions("libart.so"); 914 | }); 915 | ``` 916 | 917 | 918 | 执行脚本 919 | 920 | ``` 921 | frida -H 127.0.0.1:1234 -F -l list_module_functions.js -o log.txt 922 | ``` 923 | 924 | 925 | 发现并没有 OpenCommon(LineageOS 17.1,Android 10) 926 | 927 | 928 | 929 | ![word/media/image7.png](https://gitee.com/cyrus-studio/images/raw/master/2090c5bd30815303cb2c4f2e6fa4bdb7.png) 930 | 931 | 932 | 进入 adb shell,执行下面命令得到 APP 的 pid 为16418 933 | 934 | ``` 935 | pidof pidof com.cyrus.example 936 | ``` 937 | 938 | 939 | 查看该进程加载的 libart.so 在什么位置 940 | 941 | ``` 942 | cat /proc/16418/maps | grep libart.so 943 | ``` 944 | 945 | 946 | 输出如下: 947 | 948 | ``` 949 | 7a27617000-7a27744000 r--p 00000000 103:1d 313 /apex/com.android.runtime/lib64/libart.so 950 | 7a27744000-7a27bcf000 --xp 0012d000 103:1d 313 /apex/com.android.runtime/lib64/libart.so 951 | 7a27bcf000-7a27bd2000 rw-p 005b8000 103:1d 313 /apex/com.android.runtime/lib64/libart.so 952 | 7a27bd2000-7a27be3000 r--p 005bb000 103:1d 313 /apex/com.android.runtime/lib64/libart.so 953 | ``` 954 | 955 | 956 | 把 libart.so 拉取到本地 957 | 958 | ``` 959 | adb pull /apex/com.android.runtime/lib64/libart.so 960 | ``` 961 | 962 | 963 | 使用 IDA 打开 libart.so,确实没有 OpenCommon 函数 964 | 965 | 966 | 967 | ![word/media/image8.png](https://gitee.com/cyrus-studio/images/raw/master/4683658ba6cf3fc6420618d87bce3b62.png) 968 | 969 | 970 | # **找到 OpenCommon** 971 | 972 | 973 | 974 | 编写一个 Frida 脚本,遍历所有模块的符号,筛选出函数名中包含 "OpenCommon" 或 "DexFileLoader" 的符号,并打印出来(包括模块名、符号名、地址) 975 | 976 | 977 | 978 | find_symbols.js 979 | 980 | ``` 981 | // Frida 脚本:查找所有模块中包含 "OpenCommon" 或 "DexFileLoader" 的函数符号 982 | function scanModulesForKeywords(keywords) { 983 | const modules = Process.enumerateModules(); 984 | keywords = keywords.map(k => k.toLowerCase()); 985 | 986 | for (const module of modules) { 987 | try { 988 | const symbols = Module.enumerateSymbols(module.name); 989 | for (const symbol of symbols) { 990 | if (symbol.type === 'function') { 991 | const lowerName = symbol.name.toLowerCase(); 992 | if (keywords.some(k => lowerName.includes(k))) { 993 | console.log(`[+] ${module.name} -> ${symbol.name} @ ${symbol.address}`); 994 | } 995 | } 996 | } 997 | } catch (e) { 998 | // 某些模块无法枚举,忽略 999 | } 1000 | } 1001 | } 1002 | 1003 | setImmediate(() => { 1004 | console.log("[*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ..."); 1005 | scanModulesForKeywords(["OpenCommon", "DexFileLoader"]); 1006 | console.log("[*] Done."); 1007 | }); 1008 | 1009 | 1010 | // frida -H 127.0.0.1:1234 -F -l find_symbols.js -o log.txt 1011 | ``` 1012 | 1013 | 1014 | 输出如下: 1015 | 1016 | ``` 1017 | [*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ... 1018 | [+] linker64 -> __dl__ZN3art13DexFileLoaderD2Ev @ 0x7aadeac318 1019 | [+] linker64 -> __dl__ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aadf00370 1020 | [+] linker64 -> __dl__ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x7aadf001c0 1021 | [+] linker64 -> __dl__ZN3art13DexFileLoaderD0Ev @ 0x7aadecaef8 1022 | [+] linker64 -> __dl__ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x7aadf00860 1023 | [+] linker64 -> __dl__ZNK3art13DexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aadf00288 1024 | [+] linker64 -> __dl__ZNK3art13DexFileLoader21OpenOneDexFileFromZipERKNS_13DexZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSC_ @ 0x7aadf01128 1025 | [+] linker64 -> __dl__ZNK3art13DexFileLoader22OpenAllDexFilesFromZipERKNS_13DexZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISJ_EEEENS8_ISM_EEEE @ 0x7aadf00cb8 1026 | [+] linker64 -> __dl__ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aadf002b0 1027 | [+] linker64 -> __dl__ZNK3art13DexFileLoader7OpenAllEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISI_EEEENS7_ISL_EEEE @ 0x7aadf00918 1028 | [+] libart.so -> _ZN3art13DexFileLoader15GetBaseLocationEPKc @ 0x7a277a1c80 1029 | [+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenEPKcRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISG_EEEENS7_ISJ_EEEE @ 0x0 1030 | [+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x0 1031 | [+] libart.so -> _ZN3art13DexFileLoader23GetDexCanonicalLocationEPKc @ 0x0 1032 | [+] libart.so -> _ZN3art13DexFileLoader18IsMultiDexLocationEPKc @ 0x0 1033 | [+] libart.so -> _ZNK3art16ArtDexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x0 1034 | [+] libart.so -> _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x0 1035 | [+] libart.so -> _ZNK3art16ArtDexFileLoader7OpenZipEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x0 1036 | [+] libart.so -> _ZN3art13DexFileLoader12IsMagicValidEPKh @ 0x0 1037 | [+] libart.so -> _ZN3art13DexFileLoader22IsVersionAndMagicValidEPKh @ 0x0 1038 | [+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x0 1039 | [+] libart.so -> _ZNK3art16ArtDexFileLoader4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjONS_6MemMapEbbPS7_ @ 0x0 1040 | [+] libart.so -> _ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x0 1041 | [+] libdexfile.so -> _ZN3art13DexFileLoaderD2Ev @ 0x7aac641380 1042 | [+] libdexfile.so -> _ZN3art16ArtDexFileLoaderD0Ev @ 0x7aac641388 1043 | [+] libdexfile.so -> _ZN3art13DexFileLoader15GetBaseLocationEPKc @ 0x7aac649a78 1044 | [+] libdexfile.so -> _ZN3art13DexFileLoaderD0Ev @ 0x7aac641388 1045 | [+] libdexfile.so -> _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28 1046 | [+] libdexfile.so -> _ZN3art13DexFileLoader12IsMagicValidEj @ 0x7aac6494f8 1047 | [+] libdexfile.so -> _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x7aac649668 1048 | [+] libdexfile.so -> _ZN3art13DexFileLoader25GetMultiDexClassesDexNameEm @ 0x7aac649620 1049 | [+] libdexfile.so -> _ZN3art16ArtDexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS_13DexFileLoader12VerifyResultE @ 0x7aac63fc88 1050 | [+] libdexfile.so -> _ZNK3art13DexFileLoader19OpenWithDataSectionEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_ @ 0x7aac64a118 1051 | [+] libdexfile.so -> _ZNK3art13DexFileLoader7OpenAllEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISI_EEEENS7_ISL_EEEE @ 0x7aac64a1d0 1052 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader13OpenWithMagicEjiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac640138 1053 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aac63f020 1054 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader21OpenOneDexFileFromZipERKNS_10ZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPSC_PNS_22DexFileLoaderErrorCodeE @ 0x7aac640b60 1055 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader22OpenAllDexFilesFromZipERKNS_10ZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISH_EEEENS8_ISK_EEEE @ 0x7aac6406f0 1056 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenEPKcRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEbbPS9_PNS3_6vectorINS3_10unique_ptrIKNS_7DexFileENS3_14default_deleteISG_EEEENS7_ISJ_EEEE @ 0x7aac640058 1057 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aac63fae8 1058 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenERKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEjONS_6MemMapEbbPS7_ @ 0x7aac63fd10 1059 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader4OpenEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac6403c8 1060 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader7OpenDexEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbbPS7_ @ 0x7aac6405e0 1061 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader7OpenZipEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbPS7_PNS1_6vectorINS1_10unique_ptrIKNS_7DexFileENS1_14default_deleteISE_EEEENS5_ISH_EEEE @ 0x7aac640490 1062 | [+] libdexfile.so -> _ZNK3art16ArtDexFileLoader8OpenFileEiRKNSt3__112basic_stringIcNS1_11char_traitsIcEENS1_9allocatorIcEEEEbbbPS7_ @ 0x7aac63f608 1063 | [+] libdexfile.so -> _ZN3art13DexFileLoader12IsMagicValidEPKh @ 0x7aac649570 1064 | [+] libdexfile.so -> _ZN3art13DexFileLoader18IsMultiDexLocationEPKc @ 0x7aac649600 1065 | [+] libdexfile.so -> _ZN3art13DexFileLoader22IsVersionAndMagicValidEPKh @ 0x7aac6495a8 1066 | [+] libdexfile.so -> _ZN3art13DexFileLoader23GetDexCanonicalLocationEPKc @ 0x7aac649730 1067 | [+] libdexfile.so -> _ZNK3art13DexFileLoader20GetMultiDexChecksumsEPKcPNSt3__16vectorIjNS3_9allocatorIjEEEEPNS3_12basic_stringIcNS3_11char_traitsIcEENS5_IcEEEEiPb @ 0x7aac649b40 1068 | [+] libdexfile.so -> _ZNK3art13DexFileLoader21OpenOneDexFileFromZipERKNS_13DexZipArchiveEPKcRKNSt3__112basic_stringIcNS6_11char_traitsIcEENS6_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSC_ @ 0x7aac64a9e0 1069 | [+] libdexfile.so -> _ZNK3art13DexFileLoader22OpenAllDexFilesFromZipERKNS_13DexZipArchiveERKNSt3__112basic_stringIcNS4_11char_traitsIcEENS4_9allocatorIcEEEEbbPNS_22DexFileLoaderErrorCodeEPSA_PNS4_6vectorINS4_10unique_ptrIKNS_7DexFileENS4_14default_deleteISJ_EEEENS8_ISM_EEEE @ 0x7aac64a570 1070 | [+] libdexfile.so -> _ZNK3art13DexFileLoader4OpenEPKhmRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEE @ 0x7aac649b68 1071 | [+] libprofile.so -> _ZN3art13DexFileLoader19GetMultiDexLocationEmPKc @ 0x0 1072 | [*] Done. 1073 | ``` 1074 | 1075 | 1076 | 找到 DexFileLoader::OpenCommon 原来在 libdexfile.so 1077 | 1078 | ``` 1079 | [+] libdexfile.so -> _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28 1080 | ``` 1081 | 所以 art/runtime/dex_file_loader.cc(DexFileLoader::OpenCommon 实现)最终被编译进 libdexfile.so,而不是 libart.so。 1082 | 1083 | 1084 | 1085 | # **OpenCommon 脱壳** 1086 | 1087 | 1088 | 1089 | 使用 frida hook libdexfile.so 中的 DexFileLoader::OpenCommom 函数并拿到参数 base、size 和 location,把 dex 从内存中 dump 到 /sdcard/Android/data/pkgName/dump_dex 目录下: 1090 | 1091 | ``` 1092 | function getProcessName() { 1093 | var openPtr = Module.getExportByName('libc.so', 'open'); 1094 | var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); 1095 | 1096 | var readPtr = Module.getExportByName("libc.so", "read"); 1097 | var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); 1098 | 1099 | var closePtr = Module.getExportByName('libc.so', 'close'); 1100 | var close = new NativeFunction(closePtr, 'int', ['int']); 1101 | 1102 | var path = Memory.allocUtf8String("/proc/self/cmdline"); 1103 | var fd = open(path, 0); 1104 | if (fd != -1) { 1105 | var buffer = Memory.alloc(0x1000); 1106 | 1107 | var result = read(fd, buffer, 0x1000); 1108 | close(fd); 1109 | result = ptr(buffer).readCString(); 1110 | return result; 1111 | } 1112 | 1113 | return "-1"; 1114 | } 1115 | 1116 | 1117 | function mkdir(path) { 1118 | var mkdirPtr = Module.getExportByName('libc.so', 'mkdir'); 1119 | var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']); 1120 | 1121 | 1122 | var opendirPtr = Module.getExportByName('libc.so', 'opendir'); 1123 | var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']); 1124 | 1125 | var closedirPtr = Module.getExportByName('libc.so', 'closedir'); 1126 | var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']); 1127 | 1128 | var cPath = Memory.allocUtf8String(path); 1129 | var dir = opendir(cPath); 1130 | if (dir != 0) { 1131 | closedir(dir); 1132 | return 0; 1133 | } 1134 | mkdir(cPath, 755); 1135 | chmod(path); 1136 | } 1137 | 1138 | function chmod(path) { 1139 | var chmodPtr = Module.getExportByName('libc.so', 'chmod'); 1140 | var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']); 1141 | var cPath = Memory.allocUtf8String(path); 1142 | chmod(cPath, 755); 1143 | } 1144 | 1145 | function readStdString(str) { 1146 | const isTiny = (str.readU8() & 1) === 0; 1147 | if (isTiny) { 1148 | return str.add(1).readUtf8String(); 1149 | } 1150 | 1151 | return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); 1152 | } 1153 | 1154 | function findSymbolInLib(libname, keywordList) { 1155 | const libBase = Module.findBaseAddress(libname); 1156 | if (!libBase) { 1157 | console.error("[-] Library not loaded:", libname); 1158 | return null; 1159 | } 1160 | 1161 | const matches = []; 1162 | const symbols = Module.enumerateSymbolsSync(libname); 1163 | for (const sym of symbols) { 1164 | if (keywordList.every(k => sym.name.includes(k))) { 1165 | matches.push(sym); 1166 | } 1167 | } 1168 | 1169 | if (matches.length === 0) { 1170 | console.error("[-] No matching symbol found for keywords:", keywordList); 1171 | return null; 1172 | } 1173 | 1174 | const target = matches[0]; // 取第一个匹配的 1175 | console.log("[+] Found symbol:", target.name, " @ ", target.address); 1176 | return target.address; 1177 | } 1178 | 1179 | function dumpDexToFile(filename, base, size) { 1180 | // packageName 1181 | var processName = getProcessName(); 1182 | 1183 | if (processName != "-1") { 1184 | const dir = "/sdcard/Android/data/" + processName + "/dump_dex"; 1185 | const fullPath = dir + "/" + filename.replace(/\//g, "_").replace(/!/g, "_"); 1186 | 1187 | // 创建目录 1188 | mkdir(dir); 1189 | 1190 | // dump dex 1191 | var fd = new File(fullPath, "wb"); 1192 | if (fd && fd != null) { 1193 | var dex_buffer = ptr(base).readByteArray(size); 1194 | fd.write(dex_buffer); 1195 | fd.flush(); 1196 | fd.close(); 1197 | console.log("[+] Dex dumped to", fullPath); 1198 | } 1199 | } 1200 | } 1201 | 1202 | 1203 | function hookDexFileLoaderOpenCommon() { 1204 | const addr = findSymbolInLib("libdexfile.so", ["DexFileLoader", "OpenCommon"]); 1205 | if (!addr) return; 1206 | 1207 | Interceptor.attach(addr, { 1208 | onEnter(args) { 1209 | const base = args[0]; // const uint8_t* base 1210 | const size = args[1].toInt32(); // size_t size 1211 | const location_ptr = args[4]; // const std::string& location 1212 | const location = readStdString(location_ptr); 1213 | 1214 | console.log("\n[*] DexFileLoader::OpenCommon called"); 1215 | console.log(" base :", base); 1216 | console.log(" size :", size); 1217 | console.log(" location :", location); 1218 | 1219 | // 文件名 1220 | const filename = location.split("/").pop(); 1221 | 1222 | // 魔数 1223 | var magic = ptr(base).readCString(); 1224 | console.log(" magic :", magic) 1225 | 1226 | // dex 格式校验 1227 | if (magic.indexOf("dex") !== -1) { 1228 | dumpDexToFile(filename, base, size) 1229 | } 1230 | }, 1231 | onLeave(retval) {} 1232 | }); 1233 | } 1234 | 1235 | setImmediate(hookDexFileLoaderOpenCommon); 1236 | ``` 1237 | 1238 | 1239 | 列出当前设备所有进程并通过 findstr 过滤出目标进程 1240 | 1241 | ``` 1242 | frida-ps -H 127.0.0.1:1234 | findstr cyrus 1243 | ``` 1244 | 1245 | 1246 | 执行脚本开始 dump 1247 | 1248 | ``` 1249 | frida -H 127.0.0.1:1234 -l dump_dex_from_open_common.js -f com.cyrus.example 1250 | ``` 1251 | 1252 | 1253 | 输出如下: 1254 | 1255 | ``` 1256 | Spawning `com.cyrus.example`... 1257 | [+] Found: _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28 1258 | Spawned `com.cyrus.example`. Use %resume to let the main thread start executing! 1259 | [Remote::com.cyrus.example]-> %resume 1260 | [Remote::com.cyrus.example]-> 1261 | ================= DexFileLoader::OpenCommon ================= 1262 | base: 0x79b9fe106c 1263 | size: 1602672 1264 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk 1265 | magic : cdex001 1266 | processName: com.cyrus.example 1267 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk 1268 | ================= DexFileLoader::OpenCommon ================= 1269 | base: 0x79ba1684e0 1270 | size: 1800 1271 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes2.dex 1272 | magic : cdex001 1273 | processName: com.cyrus.example 1274 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes2.dex 1275 | ================= DexFileLoader::OpenCommon ================= 1276 | base: 0x79ba168bec 1277 | size: 155888 1278 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes3.dex 1279 | magic : cdex001 1280 | processName: com.cyrus.example 1281 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes3.dex 1282 | ================= DexFileLoader::OpenCommon ================= 1283 | base: 0x79ba18ece0 1284 | size: 8904 1285 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes4.dex 1286 | magic : cdex001 1287 | processName: com.cyrus.example 1288 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes4.dex 1289 | ================= DexFileLoader::OpenCommon ================= 1290 | base: 0x79ba190fac 1291 | size: 1288 1292 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes5.dex 1293 | magic : cdex001 1294 | processName: com.cyrus.example 1295 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes5.dex 1296 | ================= DexFileLoader::OpenCommon ================= 1297 | base: 0x79ba1914b8 1298 | size: 2656 1299 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes6.dex 1300 | magic : cdex001 1301 | processName: com.cyrus.example 1302 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes6.dex 1303 | ================= DexFileLoader::OpenCommon ================= 1304 | base: 0x79ba191f1c 1305 | size: 11824 1306 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes7.dex 1307 | magic : cdex001 1308 | processName: com.cyrus.example 1309 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes7.dex 1310 | ================= DexFileLoader::OpenCommon ================= 1311 | base: 0x79ba194d50 1312 | size: 8720 1313 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes8.dex 1314 | magic : cdex001 1315 | processName: com.cyrus.example 1316 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes8.dex 1317 | ================= DexFileLoader::OpenCommon ================= 1318 | base: 0x79ba196f64 1319 | size: 9472 1320 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes9.dex 1321 | magic : cdex001 1322 | processName: com.cyrus.example 1323 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes9.dex 1324 | ================= DexFileLoader::OpenCommon ================= 1325 | base: 0x79ba199468 1326 | size: 8904 1327 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes10.dex 1328 | magic : cdex001 1329 | processName: com.cyrus.example 1330 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes10.dex 1331 | ================= DexFileLoader::OpenCommon ================= 1332 | base: 0x79ba19b734 1333 | size: 9504 1334 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes11.dex 1335 | magic : cdex001 1336 | processName: com.cyrus.example 1337 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes11.dex 1338 | ================= DexFileLoader::OpenCommon ================= 1339 | base: 0x79ba19dc58 1340 | size: 1632 1341 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes12.dex 1342 | magic : cdex001 1343 | processName: com.cyrus.example 1344 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes12.dex 1345 | ================= DexFileLoader::OpenCommon ================= 1346 | base: 0x79ba19e2bc 1347 | size: 800 1348 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes13.dex 1349 | magic : cdex001 1350 | processName: com.cyrus.example 1351 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes13.dex 1352 | ================= DexFileLoader::OpenCommon ================= 1353 | base: 0x79ba19e5e0 1354 | size: 10328 1355 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes14.dex 1356 | magic : cdex001 1357 | processName: com.cyrus.example 1358 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes14.dex 1359 | ================= DexFileLoader::OpenCommon ================= 1360 | base: 0x79ba1a0e3c 1361 | size: 3016 1362 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes15.dex 1363 | magic : cdex001 1364 | processName: com.cyrus.example 1365 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes15.dex 1366 | ================= DexFileLoader::OpenCommon ================= 1367 | base: 0x79ba1a1a08 1368 | size: 1205136 1369 | location: /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes16.dex 1370 | magic : cdex001 1371 | processName: com.cyrus.example 1372 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes16.dex 1373 | ``` 1374 | 1375 | 1376 | 可以看到 dex 已经 dump 到 sdcard 了 1377 | 1378 | 1379 | 1380 | ![word/media/image9.png](https://gitee.com/cyrus-studio/images/raw/master/55c0144ce3bb1c5eb7ddff465a5cbfd1.png) 1381 | 1382 | 1383 | 使用下面的 adb pull 命令,一次性将设备上的整个 dump_dex 目录拉取到本地: 1384 | 1385 | ``` 1386 | adb pull /sdcard/Android/data/com.cyrus.example/dump_dex ./dumped_dex 1387 | ``` 1388 | 1389 | 1390 | 但是日志输出的 magic 可以看到都是 cdex001,cdex 文件是不可以直接通过 dex 反编译工具反编译的 1391 | 1392 | 1393 | 1394 | ![word/media/image10.png](https://gitee.com/cyrus-studio/images/raw/master/c61540418336f940c463dac5f9533c76.png) 1395 | 1396 | 1397 | # **禁止加载 cdex** 1398 | 1399 | 1400 | 1401 | Android 9 引入 CompactDex(.cdex,magic 为 cdex001),是 DEX 的压缩优化版本,导致 dump 后无法直接反编译。 1402 | 1403 | 1404 | 1405 | 优化后的 dex/cdex 通常存放在: 1406 | 1407 | ``` 1408 | /data/app/package_name/oat/arm64/base.odex 1409 | /data/app/package_name/oat/arm64/base.vdex 1410 | ``` 1411 | 1412 | 1413 | 在 Android 9(Pie)中,APP 的 .cdex 文件 是由 dex2oat 优化生成的,通常以 odex, vdex 或直接优化后的 .art 文件形式存在。 1414 | 1415 | 1416 | 1417 | 进入 adb shell 找到 目标app 存放 oat 文件的路径并删除所有 oat 文件 1418 | 1419 | ``` 1420 | wayne:/sdcard/Android/data/com.cyrus.example/dump_dex # cd /data/app 1421 | wayne:/data/app # ls 1422 | com.android.chrome-b1d3YEy1eVrwwjPOa1oq5A== com.iflytek.inputmethod-s1r9JFv0-eKNskzHyrh_vQ== 1423 | com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw== com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== 1424 | com.cyrus.example.plugin-YsXrxPvfWYdsWHxFKjcusw== com.tencent.mm-ql7ajyK9JqKXli5pgu88nw== 1425 | com.cyrus.example.test-R06ZNyf5doqJFOcZ6EaYHQ== com.xingin.xhs-HeYr1dfB-rU7NjxJiLiDeg== 1426 | wayne:/data/app # cd com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== 1427 | wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== # ls 1428 | base.apk lib oat 1429 | wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ== # cd oat 1430 | wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat # ls 1431 | arm64 1432 | wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat # cd arm64/ 1433 | wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 # ls 1434 | base.art base.odex base.vdex 1435 | wayne:/data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/oat/arm64 # rm -rf * 1436 | ``` 1437 | 1438 | 1439 | 重新执行 frida 脚本 dump dex,从输出可以看到 dump 下来的 dex 魔数都是 dex 039 / dex 035 (标准 Dex 文件的魔数)不是 cdex001,可以直接用 jadx 去反编译了。 1440 | 1441 | ``` 1442 | Spawning `com.shizhuang.duapp`... 1443 | [+] Found symbol: _ZN3art13DexFileLoader10OpenCommonEPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileEbbPS9_NS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISH_EEEEPNS0_12VerifyResultE @ 0x7aac649c28 1444 | Spawned `com.shizhuang.duapp`. Use %resume to let the main thread start executing! 1445 | [Remote::com.shizhuang.duapp]-> %resume 1446 | [Remote::com.shizhuang.duapp]-> 1447 | [*] DexFileLoader::OpenCommon called 1448 | base : 0x7a1d08e02c 1449 | size : 450032 1450 | location : /system/framework/org.apache.http.legacy.jar 1451 | magic : dex 1452 | 039 1453 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/org.apache.http.legacy.jar 1454 | 1455 | [*] DexFileLoader::OpenCommon called 1456 | base : 0x7a1d08e02c 1457 | size : 450032 1458 | location : /system/framework/org.apache.http.legacy.jar 1459 | magic : dex 1460 | 039 1461 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/org.apache.http.legacy.jar 1462 | 1463 | [*] DexFileLoader::OpenCommon called 1464 | base : 0x79bbd5c000 1465 | size : 8681372 1466 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk 1467 | magic : dex 1468 | 035 1469 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk 1470 | 1471 | [*] DexFileLoader::OpenCommon called 1472 | base : 0x79ba491000 1473 | size : 12888744 1474 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes2.dex 1475 | magic : dex 1476 | 035 1477 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes2.dex 1478 | 1479 | [*] DexFileLoader::OpenCommon called 1480 | base : 0x79b928e000 1481 | size : 12592256 1482 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes3.dex 1483 | magic : dex 1484 | 035 1485 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes3.dex 1486 | 1487 | [*] DexFileLoader::OpenCommon called 1488 | base : 0x79b86e8000 1489 | size : 12213596 1490 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes4.dex 1491 | magic : dex 1492 | 035 1493 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes4.dex 1494 | 1495 | [*] DexFileLoader::OpenCommon called 1496 | base : 0x79b7cc2000 1497 | size : 10637856 1498 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes5.dex 1499 | magic : dex 1500 | 035 1501 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes5.dex 1502 | 1503 | [*] DexFileLoader::OpenCommon called 1504 | base : 0x79b74d1000 1505 | size : 8324572 1506 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes6.dex 1507 | magic : dex 1508 | 035 1509 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes6.dex 1510 | 1511 | [*] DexFileLoader::OpenCommon called 1512 | base : 0x79b71b1000 1513 | size : 3273924 1514 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes7.dex 1515 | magic : dex 1516 | 035 1517 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes7.dex 1518 | 1519 | [*] DexFileLoader::OpenCommon called 1520 | base : 0x79b69e3000 1521 | size : 8183732 1522 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes8.dex 1523 | magic : dex 1524 | 035 1525 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes8.dex 1526 | 1527 | [*] DexFileLoader::OpenCommon called 1528 | base : 0x79b5e72000 1529 | size : 11994176 1530 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes9.dex 1531 | magic : dex 1532 | 035 1533 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes9.dex 1534 | 1535 | [*] DexFileLoader::OpenCommon called 1536 | base : 0x79b53d5000 1537 | size : 11125808 1538 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes10.dex 1539 | magic : dex 1540 | 035 1541 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes10.dex 1542 | 1543 | [*] DexFileLoader::OpenCommon called 1544 | base : 0x79b4815000 1545 | size : 12319700 1546 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes11.dex 1547 | magic : dex 1548 | 035 1549 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes11.dex 1550 | 1551 | [*] DexFileLoader::OpenCommon called 1552 | base : 0x79b3c59000 1553 | size : 12300396 1554 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes12.dex 1555 | magic : dex 1556 | 035 1557 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes12.dex 1558 | 1559 | [*] DexFileLoader::OpenCommon called 1560 | base : 0x79b3057000 1561 | size : 12587972 1562 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes13.dex 1563 | magic : dex 1564 | 035 1565 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes13.dex 1566 | 1567 | [*] DexFileLoader::OpenCommon called 1568 | base : 0x79b24d1000 1569 | size : 12081268 1570 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes14.dex 1571 | magic : dex 1572 | 035 1573 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes14.dex 1574 | 1575 | [*] DexFileLoader::OpenCommon called 1576 | base : 0x79b18cf000 1577 | size : 12590752 1578 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes15.dex 1579 | magic : dex 1580 | 035 1581 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes15.dex 1582 | 1583 | [*] DexFileLoader::OpenCommon called 1584 | base : 0x79b179b000 1585 | size : 1260244 1586 | location : /data/app/com.shizhuang.duapp-fTxemmnM8l6298xbBELksQ==/base.apk!classes16.dex 1587 | magic : dex 1588 | 035 1589 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/base.apk_classes16.dex 1590 | 1591 | [*] DexFileLoader::OpenCommon called 1592 | base : 0x79a39f57fc 1593 | size : 3782924 1594 | location : /system/product/app/webview/webview.apk 1595 | magic : dex 1596 | 035 1597 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk 1598 | 1599 | [*] DexFileLoader::OpenCommon called 1600 | base : 0x7a11ec6138 1601 | size : 77880 1602 | location : /system/product/app/webview/webview.apk!classes2.dex 1603 | magic : dex 1604 | 035 1605 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk_classes2.dex 1606 | 1607 | [*] DexFileLoader::OpenCommon called 1608 | base : 0x79a39f57fc 1609 | size : 3782924 1610 | location : /system/product/app/webview/webview.apk 1611 | magic : dex 1612 | 035 1613 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk 1614 | 1615 | [*] DexFileLoader::OpenCommon called 1616 | base : 0x7a11ec6138 1617 | size : 77880 1618 | location : /system/product/app/webview/webview.apk!classes2.dex 1619 | magic : dex 1620 | 035 1621 | [+] Dex dumped to /sdcard/Android/data/com.shizhuang.duapp/dump_dex/webview.apk_classes2.dex 1622 | ``` 1623 | 1624 | 1625 | # **jadx 反编译 dex** 1626 | 1627 | 1628 | 1629 | 使用 jadx 反编译 dex。 1630 | 1631 | 1632 | 1633 | jadx 项目地址:[https://github.com/skylot/jadx](https://github.com/skylot/jadx) 1634 | 1635 | 1636 | 1637 | 1638 | 1639 | ![word/media/image11.png](https://gitee.com/cyrus-studio/images/raw/master/4816e881ee8000e5b1919e2bff08a6a0.png) 1640 | 1641 | 1642 | jadx 默认缓存目录 1643 | 1644 | ``` 1645 | C:\Users\$USERNAME\AppData\Local\skylot\jadx\cache\projects 1646 | ``` 1647 | 1648 | 1649 | # **DexFile 脱壳** 1650 | 1651 | 1652 | 1653 | 找到 CompactDexFile 构造函数方法符号信息如下: 1654 | 1655 | ``` 1656 | [+] libdexfile.so -> _ZN3art14CompactDexFileC1EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8 1657 | [+] libdexfile.so -> _ZN3art14CompactDexFileC2EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8 1658 | ``` 1659 | 1660 | 1661 | hook CompactDexFile 和 StandardDexFile 的构造函数拿到 base、size 和 location 并 dump dex。 1662 | 1663 | 1664 | 1665 | dump_dex_from_dex_file.js 1666 | 1667 | ``` 1668 | function getProcessName() { 1669 | var openPtr = Module.getExportByName('libc.so', 'open'); 1670 | var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); 1671 | 1672 | var readPtr = Module.getExportByName("libc.so", "read"); 1673 | var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); 1674 | 1675 | var closePtr = Module.getExportByName('libc.so', 'close'); 1676 | var close = new NativeFunction(closePtr, 'int', ['int']); 1677 | 1678 | var path = Memory.allocUtf8String("/proc/self/cmdline"); 1679 | var fd = open(path, 0); 1680 | if (fd != -1) { 1681 | var buffer = Memory.alloc(0x1000); 1682 | 1683 | var result = read(fd, buffer, 0x1000); 1684 | close(fd); 1685 | result = ptr(buffer).readCString(); 1686 | return result; 1687 | } 1688 | 1689 | return "-1"; 1690 | } 1691 | 1692 | 1693 | function mkdir(path) { 1694 | var mkdirPtr = Module.getExportByName('libc.so', 'mkdir'); 1695 | var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']); 1696 | 1697 | 1698 | var opendirPtr = Module.getExportByName('libc.so', 'opendir'); 1699 | var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']); 1700 | 1701 | var closedirPtr = Module.getExportByName('libc.so', 'closedir'); 1702 | var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']); 1703 | 1704 | var cPath = Memory.allocUtf8String(path); 1705 | var dir = opendir(cPath); 1706 | if (dir != 0) { 1707 | closedir(dir); 1708 | return 0; 1709 | } 1710 | mkdir(cPath, 755); 1711 | chmod(path); 1712 | } 1713 | 1714 | function chmod(path) { 1715 | var chmodPtr = Module.getExportByName('libc.so', 'chmod'); 1716 | var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']); 1717 | var cPath = Memory.allocUtf8String(path); 1718 | chmod(cPath, 755); 1719 | } 1720 | 1721 | function readStdString(str) { 1722 | const isTiny = (str.readU8() & 1) === 0; 1723 | if (isTiny) { 1724 | return str.add(1).readUtf8String(); 1725 | } 1726 | 1727 | return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); 1728 | } 1729 | 1730 | function findSymbolInLib(libname, keywordList) { 1731 | const libBase = Module.findBaseAddress(libname); 1732 | if (!libBase) { 1733 | console.error("[-] Library not loaded:", libname); 1734 | return null; 1735 | } 1736 | 1737 | const matches = []; 1738 | const symbols = Module.enumerateSymbolsSync(libname); 1739 | for (const sym of symbols) { 1740 | if (keywordList.every(k => sym.name.includes(k))) { 1741 | matches.push(sym); 1742 | } 1743 | } 1744 | 1745 | if (matches.length === 0) { 1746 | console.error("[-] No matching symbol found for keywords:", keywordList); 1747 | return null; 1748 | } 1749 | 1750 | const target = matches[0]; // 取第一个匹配的 1751 | console.log("[+] Found symbol:", target.name, " @ ", target.address); 1752 | return target.address; 1753 | } 1754 | 1755 | function dumpDexToFile(filename, base, size) { 1756 | // packageName 1757 | var processName = getProcessName(); 1758 | 1759 | if (processName != "-1") { 1760 | const dir = "/sdcard/Android/data/" + processName + "/dump_dex"; 1761 | const fullPath = dir + "/" + filename.replace(/\//g, "_").replace(/!/g, "_"); 1762 | 1763 | // 创建目录 1764 | mkdir(dir); 1765 | 1766 | // dump dex 1767 | var fd = new File(fullPath, "wb"); 1768 | if (fd && fd != null) { 1769 | var dex_buffer = ptr(base).readByteArray(size); 1770 | fd.write(dex_buffer); 1771 | fd.flush(); 1772 | fd.close(); 1773 | console.log("[+] Dex dumped to", fullPath); 1774 | } 1775 | } 1776 | } 1777 | 1778 | function hookCompactDexFile() { 1779 | const addr = findSymbolInLib("libdexfile.so", ["CompactDexFile", "C1"]); 1780 | if (!addr) return; 1781 | 1782 | Interceptor.attach(addr, { 1783 | onEnter(args) { 1784 | const base = args[1]; 1785 | const size = args[2].toInt32(); 1786 | const data_base = args[3]; 1787 | const data_size = args[4].toInt32(); 1788 | const location_ptr = args[5]; 1789 | const location = readStdString(location_ptr); 1790 | 1791 | console.log("\n[*] CompactDexFile constructor called"); 1792 | console.log(" this :", args[0]); 1793 | console.log(" base :", base); 1794 | console.log(" size :", size); 1795 | console.log(" data_base :", data_base); 1796 | console.log(" data_size :", data_size); 1797 | console.log(" location :", location); 1798 | 1799 | // 文件名 1800 | const filename = location.split("/").pop(); 1801 | 1802 | // 魔数 1803 | var magic = ptr(base).readCString(); 1804 | console.log(" magic :", magic) 1805 | 1806 | // dex 格式校验 1807 | if (magic.indexOf("dex") !== -1) { 1808 | dumpDexToFile(filename, base, size) 1809 | } 1810 | } 1811 | }); 1812 | } 1813 | 1814 | function hookStandardDexFile() { 1815 | const addr = findSymbolInLib("libdexfile.so", ["StandardDexFile", "C1"]); 1816 | if (!addr) return; 1817 | 1818 | Interceptor.attach(addr, { 1819 | onEnter(args) { 1820 | const base = args[1]; 1821 | const size = args[2].toInt32(); 1822 | const data_base = args[3]; 1823 | const data_size = args[4].toInt32(); 1824 | const location_ptr = args[5]; 1825 | const location = readStdString(location_ptr); 1826 | 1827 | console.log("\n[*] StandardDexFile constructor called"); 1828 | console.log(" this :", args[0]); 1829 | console.log(" base :", base); 1830 | console.log(" size :", size); 1831 | console.log(" data_base :", data_base); 1832 | console.log(" data_size :", data_size); 1833 | console.log(" location :", location); 1834 | 1835 | // 文件名 1836 | const filename = location.split("/").pop(); 1837 | 1838 | // 魔数 1839 | var magic = ptr(base).readCString(); 1840 | console.log(" magic :", magic) 1841 | 1842 | // dex 格式校验 1843 | if (magic.indexOf("dex") !== -1) { 1844 | dumpDexToFile(filename, base, size) 1845 | } 1846 | } 1847 | }); 1848 | } 1849 | 1850 | 1851 | setImmediate(function () { 1852 | hookCompactDexFile() 1853 | hookStandardDexFile() 1854 | }); 1855 | ``` 1856 | 1857 | 1858 | 执行脚本: 1859 | 1860 | ``` 1861 | frida -H 127.0.0.1:1234 -l dump_dex_from_dex_file.js -f com.cyrus.example 1862 | ``` 1863 | 1864 | 1865 | 输出如下: 1866 | 1867 | ``` 1868 | Spawning `com.cyrus.example`... 1869 | [+] Found symbol: _ZN3art14CompactDexFileC1EPKhmS2_mRKNSt3__112basic_stringIcNS3_11char_traitsIcEENS3_9allocatorIcEEEEjPKNS_10OatDexFileENS3_10unique_ptrINS_16DexFileContainerENS3_14default_deleteISG_EEEE @ 0x7aac6420e8 1870 | [-] No matching symbol found for keywords: StandardDexFile,C1 1871 | Spawned `com.cyrus.example`. Use %resume to let the main thread start executing! 1872 | [Remote::com.cyrus.example]-> %resume 1873 | [Remote::com.cyrus.example]-> 1874 | [*] CompactDexFile constructor called 1875 | this : 0x7aacac0720 1876 | base : 0x79b9fe206c 1877 | size : 1602672 1878 | data_base : 0x79ba2c8d98 1879 | data_size : 14765976 1880 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk 1881 | magic : cdex001 1882 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk 1883 | 1884 | [*] CompactDexFile constructor called 1885 | this : 0x7aacac0800 1886 | base : 0x79ba1694e0 1887 | size : 1800 1888 | data_base : 0x79ba2c8d98 1889 | data_size : 14770144 1890 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes2.dex 1891 | magic : cdex001 1892 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes2.dex 1893 | 1894 | [*] CompactDexFile constructor called 1895 | this : 0x7aacac08e0 1896 | base : 0x79ba169bec 1897 | size : 155888 1898 | data_base : 0x79ba2c8d98 1899 | data_size : 15120528 1900 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes3.dex 1901 | magic : cdex001 1902 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes3.dex 1903 | 1904 | [*] CompactDexFile constructor called 1905 | this : 0x7aacac09c0 1906 | base : 0x79ba18fce0 1907 | size : 8904 1908 | data_base : 0x79ba2c8d98 1909 | data_size : 15155776 1910 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes4.dex 1911 | magic : cdex001 1912 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes4.dex 1913 | 1914 | [*] CompactDexFile constructor called 1915 | this : 0x7aacac0aa0 1916 | base : 0x79ba191fac 1917 | size : 1288 1918 | data_base : 0x79ba2c8d98 1919 | data_size : 15158304 1920 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes5.dex 1921 | magic : cdex001 1922 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes5.dex 1923 | 1924 | [*] CompactDexFile constructor called 1925 | this : 0x7aacac0b80 1926 | base : 0x79ba1924b8 1927 | size : 2656 1928 | data_base : 0x79ba2c8d98 1929 | data_size : 15165016 1930 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes6.dex 1931 | magic : cdex001 1932 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes6.dex 1933 | 1934 | [*] CompactDexFile constructor called 1935 | this : 0x7aacac0c60 1936 | base : 0x79ba192f1c 1937 | size : 11824 1938 | data_base : 0x79ba2c8d98 1939 | data_size : 15211952 1940 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes7.dex 1941 | magic : cdex001 1942 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes7.dex 1943 | 1944 | [*] CompactDexFile constructor called 1945 | this : 0x7aacac0d40 1946 | base : 0x79ba195d50 1947 | size : 8720 1948 | data_base : 0x79ba2c8d98 1949 | data_size : 15242288 1950 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes8.dex 1951 | magic : cdex001 1952 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes8.dex 1953 | 1954 | [*] CompactDexFile constructor called 1955 | this : 0x7a175fe260 1956 | base : 0x79ba197f64 1957 | size : 9472 1958 | data_base : 0x79ba2c8d98 1959 | data_size : 15276888 1960 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes9.dex 1961 | magic : cdex001 1962 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes9.dex 1963 | 1964 | [*] CompactDexFile constructor called 1965 | this : 0x7a175fe340 1966 | base : 0x79ba19a468 1967 | size : 8904 1968 | data_base : 0x79ba2c8d98 1969 | data_size : 15314648 1970 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes10.dex 1971 | magic : cdex001 1972 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes10.dex 1973 | 1974 | [*] CompactDexFile constructor called 1975 | this : 0x7a175fe420 1976 | base : 0x79ba19c734 1977 | size : 9504 1978 | data_base : 0x79ba2c8d98 1979 | data_size : 15346672 1980 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes11.dex 1981 | magic : cdex001 1982 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes11.dex 1983 | 1984 | [*] CompactDexFile constructor called 1985 | this : 0x7a175fe500 1986 | base : 0x79ba19ec58 1987 | size : 1632 1988 | data_base : 0x79ba2c8d98 1989 | data_size : 15349816 1990 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes12.dex 1991 | magic : cdex001 1992 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes12.dex 1993 | 1994 | [*] CompactDexFile constructor called 1995 | this : 0x7a175fe5e0 1996 | base : 0x79ba19f2bc 1997 | size : 800 1998 | data_base : 0x79ba2c8d98 1999 | data_size : 15350936 2000 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes13.dex 2001 | magic : cdex001 2002 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes13.dex 2003 | 2004 | [*] CompactDexFile constructor called 2005 | this : 0x7a175fe6c0 2006 | base : 0x79ba19f5e0 2007 | size : 10328 2008 | data_base : 0x79ba2c8d98 2009 | data_size : 15401760 2010 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes14.dex 2011 | magic : cdex001 2012 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes14.dex 2013 | 2014 | [*] CompactDexFile constructor called 2015 | this : 0x7a176a4fc0 2016 | base : 0x79ba1a1e3c 2017 | size : 3016 2018 | data_base : 0x79ba2c8d98 2019 | data_size : 15409648 2020 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes15.dex 2021 | magic : cdex001 2022 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes15.dex 2023 | 2024 | [*] CompactDexFile constructor called 2025 | this : 0x7a176a50a0 2026 | base : 0x79ba1a2a08 2027 | size : 1205136 2028 | data_base : 0x79ba2c8d98 2029 | data_size : 22612744 2030 | location : /data/app/com.cyrus.example-uIsySv7lFm21qMVPnPJ-pw==/base.apk!classes16.dex 2031 | magic : cdex001 2032 | [+] Dex dumped to /sdcard/Android/data/com.cyrus.example/dump_dex/base.apk_classes16.dex 2033 | ``` 2034 | 2035 | 2036 | 把 dex 文件拉取到本地: 2037 | 2038 | ``` 2039 | adb pull /sdcard/Android/data/com.cyrus.example/dump_dex ./dumped_dex 2040 | ``` 2041 | 2042 | 2043 | 使用命令行工具 compact_dex_converter 把 cdex(Compact Dex)文件转换为标准 .dex 文件。 2044 | 2045 | [https://github.com/anestisb/vdexExtractor#compact-dex-converter](https://github.com/anestisb/vdexExtractor#compact-dex-converter) 2046 | 2047 | 2048 | 2049 | # **dex2oat 脱壳** 2050 | 2051 | 2052 | 2053 | dex2oat 的流程也可以进行脱壳。 2054 | 2055 | 2056 | 2057 | 当安装 APK 时,如果需要 ahead-of-time (AOT) 编译,installd 会调用 dex2oat: 2058 | 2059 | 2060 | 2061 | ![word/media/image12.png](https://gitee.com/cyrus-studio/images/raw/master/608956255740c4fefcd613ee1b52900c.png) 2062 | [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) 2063 | 2064 | 2065 | 2066 | 进入 dex2oat.cc 的 main(),在 dex2oat::ReturnCode Setup() 方法中 将 dex 注册到 VerificationResults 时候可以拿到 dex_file 对象,这里也是一个很好的脱壳点。 2067 | 2068 | ``` 2069 | verification_results_->AddDexFile(dex_file); 2070 | ``` 2071 | 2072 | 2073 | ![word/media/image13.png](https://gitee.com/cyrus-studio/images/raw/master/4a384bd1e48d829f9c6064b9d1299864.png) 2074 | [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) 2075 | 2076 | 2077 | 2078 | # **完整源码** 2079 | 2080 | 2081 | 2082 | 开源地址:[https://github.com/CYRUS-STUDIO/frida_dex_dump](https://github.com/CYRUS-STUDIO/frida_dex_dump) 2083 | 2084 | 2085 | 2086 | 相关文章: 2087 | 2088 | - _[ART环境下dex加载流程分析及frida dump dex方案](https://bbs.kanxue.com/thread-277771.htm)_ 2089 | 2090 | - _[拨云见日:安卓APP脱壳的本质以及如何快速发现ART下的脱壳点](https://bbs.kanxue.com/thread-254555.htm)_ 2091 | 2092 | 2093 | 2094 | -------------------------------------------------------------------------------- /dump_dex_from_dex_file.js: -------------------------------------------------------------------------------- 1 | function getProcessName() { 2 | var openPtr = Module.getExportByName('libc.so', 'open'); 3 | var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); 4 | 5 | var readPtr = Module.getExportByName("libc.so", "read"); 6 | var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); 7 | 8 | var closePtr = Module.getExportByName('libc.so', 'close'); 9 | var close = new NativeFunction(closePtr, 'int', ['int']); 10 | 11 | var path = Memory.allocUtf8String("/proc/self/cmdline"); 12 | var fd = open(path, 0); 13 | if (fd != -1) { 14 | var buffer = Memory.alloc(0x1000); 15 | 16 | var result = read(fd, buffer, 0x1000); 17 | close(fd); 18 | result = ptr(buffer).readCString(); 19 | return result; 20 | } 21 | 22 | return "-1"; 23 | } 24 | 25 | 26 | function mkdir(path) { 27 | var mkdirPtr = Module.getExportByName('libc.so', 'mkdir'); 28 | var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']); 29 | 30 | 31 | var opendirPtr = Module.getExportByName('libc.so', 'opendir'); 32 | var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']); 33 | 34 | var closedirPtr = Module.getExportByName('libc.so', 'closedir'); 35 | var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']); 36 | 37 | var cPath = Memory.allocUtf8String(path); 38 | var dir = opendir(cPath); 39 | if (dir != 0) { 40 | closedir(dir); 41 | return 0; 42 | } 43 | mkdir(cPath, 755); 44 | chmod(path); 45 | } 46 | 47 | function chmod(path) { 48 | var chmodPtr = Module.getExportByName('libc.so', 'chmod'); 49 | var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']); 50 | var cPath = Memory.allocUtf8String(path); 51 | chmod(cPath, 755); 52 | } 53 | 54 | function readStdString(str) { 55 | const isTiny = (str.readU8() & 1) === 0; 56 | if (isTiny) { 57 | return str.add(1).readUtf8String(); 58 | } 59 | 60 | return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); 61 | } 62 | 63 | function findSymbolInLib(libname, keywordList) { 64 | const libBase = Module.findBaseAddress(libname); 65 | if (!libBase) { 66 | console.error("[-] Library not loaded:", libname); 67 | return null; 68 | } 69 | 70 | const matches = []; 71 | const symbols = Module.enumerateSymbolsSync(libname); 72 | for (const sym of symbols) { 73 | if (keywordList.every(k => sym.name.includes(k))) { 74 | matches.push(sym); 75 | } 76 | } 77 | 78 | if (matches.length === 0) { 79 | console.error("[-] No matching symbol found for keywords:", keywordList); 80 | return null; 81 | } 82 | 83 | const target = matches[0]; // 取第一个匹配的 84 | console.log("[+] Found symbol:", target.name, " @ ", target.address); 85 | return target.address; 86 | } 87 | 88 | function dumpDexToFile(filename, base, size) { 89 | // packageName 90 | var processName = getProcessName(); 91 | 92 | if (processName != "-1") { 93 | const dir = "/sdcard/Android/data/" + processName + "/dump_dex"; 94 | const fullPath = dir + "/" + filename.replace(/\//g, "_").replace(/!/g, "_"); 95 | 96 | // 创建目录 97 | mkdir(dir); 98 | 99 | // dump dex 100 | var fd = new File(fullPath, "wb"); 101 | if (fd && fd != null) { 102 | var dex_buffer = ptr(base).readByteArray(size); 103 | fd.write(dex_buffer); 104 | fd.flush(); 105 | fd.close(); 106 | console.log("[+] Dex dumped to", fullPath); 107 | } 108 | } 109 | } 110 | 111 | function hookCompactDexFile() { 112 | const addr = findSymbolInLib("libdexfile.so", ["CompactDexFile", "C1"]); 113 | if (!addr) return; 114 | 115 | Interceptor.attach(addr, { 116 | onEnter(args) { 117 | const base = args[1]; 118 | const size = args[2].toInt32(); 119 | const data_base = args[3]; 120 | const data_size = args[4].toInt32(); 121 | const location_ptr = args[5]; 122 | const location = readStdString(location_ptr); 123 | 124 | console.log("\n[*] CompactDexFile constructor called"); 125 | console.log(" this :", args[0]); 126 | console.log(" base :", base); 127 | console.log(" size :", size); 128 | console.log(" data_base :", data_base); 129 | console.log(" data_size :", data_size); 130 | console.log(" location :", location); 131 | 132 | // 文件名 133 | const filename = location.split("/").pop(); 134 | 135 | // 魔数 136 | var magic = ptr(base).readCString(); 137 | console.log(" magic :", magic) 138 | 139 | // dex 格式校验 140 | if (magic.indexOf("dex") !== -1) { 141 | dumpDexToFile(filename, base, size) 142 | } 143 | } 144 | }); 145 | } 146 | 147 | function hookStandardDexFile() { 148 | const addr = findSymbolInLib("libdexfile.so", ["StandardDexFile", "C1"]); 149 | if (!addr) return; 150 | 151 | Interceptor.attach(addr, { 152 | onEnter(args) { 153 | const base = args[1]; 154 | const size = args[2].toInt32(); 155 | const data_base = args[3]; 156 | const data_size = args[4].toInt32(); 157 | const location_ptr = args[5]; 158 | const location = readStdString(location_ptr); 159 | 160 | console.log("\n[*] StandardDexFile constructor called"); 161 | console.log(" this :", args[0]); 162 | console.log(" base :", base); 163 | console.log(" size :", size); 164 | console.log(" data_base :", data_base); 165 | console.log(" data_size :", data_size); 166 | console.log(" location :", location); 167 | 168 | // 文件名 169 | const filename = location.split("/").pop(); 170 | 171 | // 魔数 172 | var magic = ptr(base).readCString(); 173 | console.log(" magic :", magic) 174 | 175 | // dex 格式校验 176 | if (magic.indexOf("dex") !== -1) { 177 | dumpDexToFile(filename, base, size) 178 | } 179 | } 180 | }); 181 | } 182 | 183 | 184 | setImmediate(function () { 185 | hookCompactDexFile() 186 | hookStandardDexFile() 187 | }); 188 | 189 | // frida -H 127.0.0.1:1234 -l dump_dex_from_dex_file.js -f com.cyrus.example -------------------------------------------------------------------------------- /dump_dex_from_open_common.js: -------------------------------------------------------------------------------- 1 | function getProcessName() { 2 | var openPtr = Module.getExportByName('libc.so', 'open'); 3 | var open = new NativeFunction(openPtr, 'int', ['pointer', 'int']); 4 | 5 | var readPtr = Module.getExportByName("libc.so", "read"); 6 | var read = new NativeFunction(readPtr, "int", ["int", "pointer", "int"]); 7 | 8 | var closePtr = Module.getExportByName('libc.so', 'close'); 9 | var close = new NativeFunction(closePtr, 'int', ['int']); 10 | 11 | var path = Memory.allocUtf8String("/proc/self/cmdline"); 12 | var fd = open(path, 0); 13 | if (fd != -1) { 14 | var buffer = Memory.alloc(0x1000); 15 | 16 | var result = read(fd, buffer, 0x1000); 17 | close(fd); 18 | result = ptr(buffer).readCString(); 19 | return result; 20 | } 21 | 22 | return "-1"; 23 | } 24 | 25 | 26 | function mkdir(path) { 27 | var mkdirPtr = Module.getExportByName('libc.so', 'mkdir'); 28 | var mkdir = new NativeFunction(mkdirPtr, 'int', ['pointer', 'int']); 29 | 30 | 31 | var opendirPtr = Module.getExportByName('libc.so', 'opendir'); 32 | var opendir = new NativeFunction(opendirPtr, 'pointer', ['pointer']); 33 | 34 | var closedirPtr = Module.getExportByName('libc.so', 'closedir'); 35 | var closedir = new NativeFunction(closedirPtr, 'int', ['pointer']); 36 | 37 | var cPath = Memory.allocUtf8String(path); 38 | var dir = opendir(cPath); 39 | if (dir != 0) { 40 | closedir(dir); 41 | return 0; 42 | } 43 | mkdir(cPath, 755); 44 | chmod(path); 45 | } 46 | 47 | function chmod(path) { 48 | var chmodPtr = Module.getExportByName('libc.so', 'chmod'); 49 | var chmod = new NativeFunction(chmodPtr, 'int', ['pointer', 'int']); 50 | var cPath = Memory.allocUtf8String(path); 51 | chmod(cPath, 755); 52 | } 53 | 54 | function readStdString(str) { 55 | const isTiny = (str.readU8() & 1) === 0; 56 | if (isTiny) { 57 | return str.add(1).readUtf8String(); 58 | } 59 | 60 | return str.add(2 * Process.pointerSize).readPointer().readUtf8String(); 61 | } 62 | 63 | function findSymbolInLib(libname, keywordList) { 64 | const libBase = Module.findBaseAddress(libname); 65 | if (!libBase) { 66 | console.error("[-] Library not loaded:", libname); 67 | return null; 68 | } 69 | 70 | const matches = []; 71 | const symbols = Module.enumerateSymbolsSync(libname); 72 | for (const sym of symbols) { 73 | if (keywordList.every(k => sym.name.includes(k))) { 74 | matches.push(sym); 75 | } 76 | } 77 | 78 | if (matches.length === 0) { 79 | console.error("[-] No matching symbol found for keywords:", keywordList); 80 | return null; 81 | } 82 | 83 | const target = matches[0]; // 取第一个匹配的 84 | console.log("[+] Found symbol:", target.name, " @ ", target.address); 85 | return target.address; 86 | } 87 | 88 | function dumpDexToFile(filename, base, size) { 89 | // packageName 90 | var processName = getProcessName(); 91 | 92 | if (processName != "-1") { 93 | const dir = "/sdcard/Android/data/" + processName + "/dump_dex"; 94 | const fullPath = dir + "/" + filename.replace(/\//g, "_").replace(/!/g, "_"); 95 | 96 | // 创建目录 97 | mkdir(dir); 98 | 99 | // dump dex 100 | var fd = new File(fullPath, "wb"); 101 | if (fd && fd != null) { 102 | var dex_buffer = ptr(base).readByteArray(size); 103 | fd.write(dex_buffer); 104 | fd.flush(); 105 | fd.close(); 106 | console.log("[+] Dex dumped to", fullPath); 107 | } 108 | } 109 | } 110 | 111 | 112 | function hookDexFileLoaderOpenCommon() { 113 | const addr = findSymbolInLib("libdexfile.so", ["DexFileLoader", "OpenCommon"]); 114 | if (!addr) return; 115 | 116 | Interceptor.attach(addr, { 117 | onEnter(args) { 118 | const base = args[0]; // const uint8_t* base 119 | const size = args[1].toInt32(); // size_t size 120 | const location_ptr = args[4]; // const std::string& location 121 | const location = readStdString(location_ptr); 122 | 123 | console.log("\n[*] DexFileLoader::OpenCommon called"); 124 | console.log(" base :", base); 125 | console.log(" size :", size); 126 | console.log(" location :", location); 127 | 128 | // 文件名 129 | const filename = location.split("/").pop(); 130 | 131 | // 魔数 132 | var magic = ptr(base).readCString(); 133 | console.log(" magic :", magic) 134 | 135 | // dex 格式校验 136 | if (magic.indexOf("dex") !== -1) { 137 | dumpDexToFile(filename, base, size) 138 | } 139 | }, 140 | onLeave(retval) {} 141 | }); 142 | } 143 | 144 | setImmediate(hookDexFileLoaderOpenCommon); 145 | 146 | 147 | // frida -H 127.0.0.1:1234 -l dump_dex_from_open_common.js -f com.cyrus.example -------------------------------------------------------------------------------- /find_symbols.js: -------------------------------------------------------------------------------- 1 | // Frida 脚本:查找所有模块中包含 "OpenCommon" 或 "DexFileLoader" 的函数符号 2 | function scanModulesForKeywords(keywords) { 3 | const modules = Process.enumerateModules(); 4 | keywords = keywords.map(k => k.toLowerCase()); 5 | 6 | for (const module of modules) { 7 | try { 8 | const symbols = Module.enumerateSymbols(module.name); 9 | for (const symbol of symbols) { 10 | if (symbol.type === 'function') { 11 | const lowerName = symbol.name.toLowerCase(); 12 | if (keywords.some(k => lowerName.includes(k))) { 13 | console.log(`[+] ${module.name} -> ${symbol.name} @ ${symbol.address}`); 14 | } 15 | } 16 | } 17 | } catch (e) { 18 | // 某些模块无法枚举,忽略 19 | } 20 | } 21 | } 22 | 23 | setImmediate(() => { 24 | console.log("[*] Scanning for symbols containing 'OpenCommon' or 'DexFileLoader' ..."); 25 | scanModulesForKeywords(["DexFile"]); 26 | console.log("[*] Done."); 27 | }); 28 | 29 | 30 | // frida -H 127.0.0.1:1234 -F -l find_symbols.js -o log.txt 31 | -------------------------------------------------------------------------------- /list_module_functions.js: -------------------------------------------------------------------------------- 1 | function listAllFunctions(moduleName) { 2 | const baseAddr = Module.findBaseAddress(moduleName); 3 | if (!baseAddr) { 4 | console.error(`[-] ${moduleName} not found.`); 5 | return; 6 | } 7 | 8 | console.log(`[+] ${moduleName} base address:`, baseAddr); 9 | 10 | const symbols = Module.enumerateSymbolsSync(moduleName); 11 | let count = 0; 12 | 13 | for (let sym of symbols) { 14 | if (sym.type === 'function') { 15 | console.log(`[${count}]`, sym.address, sym.name); 16 | count++; 17 | } 18 | } 19 | 20 | console.log(`[*] Total function symbols found in ${moduleName}:`, count); 21 | } 22 | 23 | // 列出 libart.so 的所有函数 24 | setImmediate(function () { 25 | listAllFunctions("libart.so"); 26 | }); 27 | 28 | 29 | // frida -H 127.0.0.1:1234 -F -l list_module_functions.js -o log.txt --------------------------------------------------------------------------------