├── .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 | 
30 |
31 |
32 | DEF 的安全研究员选择的两个脱壳点:art::OpenAndReadMagic 和 DexFile::DexFile
33 |
34 |
35 |
36 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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 | 
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
--------------------------------------------------------------------------------