首页
课程
问答
CTF
社区
招聘
峰会
发现
排行榜
知识库
工具下载
看雪20年
看雪商城
证书查询
登录
注册
首页
社区
课程
招聘
发现
问答
CTF
排行榜
知识库
工具下载
峰会
看雪商城
证书查询
社区
Android安全
发新帖
35
40
[原创] Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)
发表于: 2026-2-7 21:23
75381
[原创] Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)
Elenia
7
2026-2-7 21:23
75381
> 本文主要是想要了解清楚 Frida 在新版本有什么地方不一样了。本文主要还是着重在 Zymbiote的注入机制上,其他相关板块后续再出文章分析。 > > 然后在阅读 Frida 源码的过程也发现了很多有意思的小trick,鉴于本人水平有限如果有问题请各位大佬指出。 > > 版本说明: > > Frida 17.6 > > 参考文章: > > - <mark class="encrypted">e57K9s2c8@1M7s2y4Q4x3@1q4Q4x3V1k6Q4x3V1k6E0M7q4)9J5k6i4N6W2K9i4S2A6L8W2)9J5k6i4q4I4i4K6u0W2j5$3!0E0i4K6u0r3M7#2)9K6c8W2)9#2k6W2)9#2k6X3u0A6P5W2)9K6c8p5#2*7g2e0y4y4g2q4V1#2e0i4A6c8P5p5#2m8i4K6y4p5i4K6y4p5i4K6t1$3L8h3W2V1i4K6y4p5x3U0t1@1y4K6b7^5y4e0l9H3x3W2)9J5y4X3W2V1P5q4)9K6c8o6q4Q4x3U0k6K6L8W2)9K6c8o6q4X3x3r3f1%4x3o6M7%4k6U0p5&6x3U0M7H3x3$3c8T1j5K6l9%4z5e0x3#2j5e0j5K6x3e0p5K6z5e0R3H3i4K6t1$3j5$3S2C8M7$3#2Q4x3@1c8X3k6o6y4X3y4o6f1%4x3h3p5$3x3K6j5K6k6e0k6X3z5e0x3$3z5r3q4S2x3e0R3H3z5o6M7$3j5K6j5H3x3U0N6U0j5e0t1#2k6U0l9@1z5h3t1#2x3U0l9K6x3o6f1K6z5h3q4V1j5h3f1%4k6o6b7&6x3U0c8T1z5e0R3%4y4o6V1H3y4h3x3%4z5o6p5$3y4r3b7I4i4K6t1$3L8i4m8K6K9r3q4J5k6g2)9K6c8o6q4Q4x3U0k6K6j5$3g2F1k6g2)9K6c8o6t1K6i4K6t1$3M7%4u0U0K9h3c8Q4x3@1b7H3x3e0t1I4d9q4N6V1e0Y4f1K6d9@1y4W2b7Y4y4X3e0#2t1%4k6s2y4%4k6U0k6Q4x3U0k6K6K9r3q4J5k6i4u0Q4y4h3k6K6K9r3q4J5k6h3W2F1k6X3!0Q4x3@1b7H3z5e0f1%4z5h3x3J5y4K6l9#2x3$3t1%4j5e0m8S2j5e0u0W2k6r3j5@1z5o6V1H3y4U0f1@1x3o6t1^5x3q4)9J5y4Y4y4Z5j5i4u0W2M7W2)9#2k6Y4y4Z5j5i4u0W2K9h3&6X3L8#2)9#2k6X3k6A6M7Y4y4@1i4K6y4p5j5$3p5&6z5e0m8X3k6r3b7I4z5r3t1I4z5h3q4S2k6o6q4T1j5e0W2U0z5o6M7K6z5o6p5@1z5h3u0X3k6r3u0Q4x3U0y4J5k6l9`.`.</mark> ## 目录 ``` 1. 前言 1.1 版本说明 1.2 参考文章 2. 历史Hook方案 2.1 旧方案问题 2.2 新方案 3. 流程图 4. Zymbiote注入阶段 4.1 函数调用链 4.2 补丁 4.2.1 Payload补丁 4.2.2 ArtMethod补丁 4.3 Boot Heap 4.3.1 Boot Image文件 4.4 ensure_loaded (初始化) 4.5 inject_zymbiote (核心注入逻辑) 4.6 do_prepare_zymbiote_injection(核心准备逻辑) 4.6.1 调用链 4.6.2 初始化变量 4.6.3 解析进程内存映射 4.6.4 获取libc与runtime信息 4.6.5 定位ArtMethod信息 4.6.6 填充payload ``` ## 历史Hook方案 ### 旧方案问题 - 需要 Zygote 内注入 agent - 需要 ptrace 暂停/恢复线程 - 在所有子进程中留下痕迹 - 需要隐藏 FD,否则 Zygote 会 abort - 依赖于 syscall tracing - Ptrace 的 syscall tracing 逻辑复杂且脆弱 - Teardown 路径不安全 - 难以保证在各种时机正确回收 - 容易在 Zygote 和 system_server 中造成不稳定 ### 新方案 - 完全外部化 - 不注入 Zygote agent - 通过 /proc/$pid/mem 写入小型 payload - 不使用 ptrace - 短生命周期设计 - Payload 仅负责握手和暂停 - 执行后立即回滚,不留常驻代码 - 利用稳定入口点 - 使用 android.os.Process.setArgV0Native() 作为触发点 - 该函数在子进程启动时必然被调用 ## 流程图  ## Zymbiote 注入阶段 > Zymbiote 是 Frida 在 Android 上实现轻量级 Zygote Hook 的机制,用于在应用进程启动时注入 Frida > > 命名来源:Zymbiote = Zygote + Symbiote(共生体),表示与 Zygote 进程共生。 ### 函数调用链 ```JavaScript enable_spawn_gating() (linux-host-session.vala:261) └─ robo_launcher.enable_spawn_gating() (linux-host-session.vala:263) └─ ensure_loaded() (linux-host-session.vala:661) ├─ 创建 Unix Socket 服务器 ├─ handle_zymbiote_connections.begin() (linux-host-session.vala:785) └─ 枚举 Zygote 进程 └─ do_inject_zygote_agent.begin() (linux-host-session.vala:816) └─ inject_zymbiote() (linux-host-session.vala:838) ├─ prepare_zymbiote_injection() (linux-host-session.vala:847) │ └─ do_prepare_zymbiote_injection() (linux-host-session.vala:905) │ ├─ 解析 /proc/<pid>/maps │ ├─ 定位 payload 基址 (libstagefright.so) │ ├─ 解析 libc.so 和 libandroid_runtime.so │ ├─ 定位 ArtMethod 槽位 │ └─ 准备 payload 数据 ├─ 暂停 Zygote 进程 (SIGSTOP) ├─ 应用补丁 (ZymbiotePatches.apply) │ ├─ 写入 payload 到可执行映射 │ └─ 替换 ArtMethod 槽位指针 └─ 恢复 Zygote 进程 (SIGCONT) [子进程 fork 后] └─ frida_zymbiote_replacement_set_argv0() (zymbiote.c:44) ├─ 回滚 ArtMethod 槽位 ├─ 调用原始 setArgV0Native ├─ 连接 Unix Socket ├─ 发送 Hello 消息 ├─ 等待 ACK └─ 触发 SIGSTOP [Frida Core 处理] └─ handle_zymbiote_connection() (linux-host-session.vala:1128) ├─ read_hello() (linux-host-session.vala:1130) ├─ 判断是否需要 spawn gating └─ connection.resume() (linux-host-session.vala:1150) ├─ 发送 ACK ├─ 等待进程停止 ├─ 回滚子进程补丁 └─ 发送 SIGCONT ``` ### 补丁 > 后文会出现补丁相关的代码,所以这里解释一下。其实很好理解,这里补丁就是我们patch上去的代码 > > 补丁目前分为两种类型 #### Payload 补丁 > 这里很好理解,payload相当于我们传入的二进制代码,然后我们在 ArtMethod 改对应属性指向我们的payload,这样他们执行函数的时候就会先执行我们的payload > > - 内容:编译后的 zymbiote 二进制代码(约 800-900 字节) > - 位置:libstagefright.so 的最后一页(可执行映射)(这里为什么选择这个so捏,因为这是系统库,通常已加载) > - 作用:在目标进程中放置可执行代码 ```JavaScript // 将 payload 代码写入到可执行内存 patches.apply(payload, process_memory, payload_base); ``` #### ArtMethod 补丁 > - 内容:指针值(4 或 8 字节) > - 位置:boot heap 中的 ArtMethod 槽位 > - 作用:将函数指针从原始函数改为指向 payload ```JavaScript // 替换 ArtMethod 槽位指针 patches.apply(replaced_ptr, process_memory, art_method_slot); ``` ### Boot Heap > 这个概念在后面也会出现,我们这里可以解释一下。 > > Boot Heap = Android ART 运行时在启动时预加载的内存区域。 ```JavaScript Boot Heap 存储: ├─ 预编译的 Android 框架类 ├─ ArtMethod 对象(包括 setArgV0Native 的 ArtMethod) ├─ 预加载的类元数据 └─ 系统库的方法信息 private static bool is_boot_heap (string path) { return "boot.art" in path || "boot-framework.art" in path || "dalvik-LinearAlloc" in path; } ``` 我们从代码可以知道,系统判定为 boot_heap 就是判断路径是否包含boot.art,boot-framework.art,dalvik-LinearAlloc这些 Boot Image #### Boot Image 文件 > 关于 Boot Image 我们可以后续再出一个文章再详细介绍这个地方。 > > Android 启动时: > > 1. Zygote 进程启动 > 2. 加载 boot.art → 映射到内存 (boot heap) > 3. 加载 boot-framework.art → 映射到内存 (boot heap) > 4. 预编译的类和方法信息都在这里 > 5. ArtMethod 对象也存储在这里 ✅ ```JavaScript boot.art: ├─ 预编译的核心 Android 类 ├─ 系统库的预编译代码 └─ 启动时加载到内存 boot-framework.art: ├─ Android 框架的预编译类 ├─ 系统服务的预编译代码 └─ 启动时加载到内存 /proc/<pid>/maps 输出: 7f1234000000-7f1235000000 rw-p 00000000 /dev/ashmem/dalvik-LinearAlloc 7f1235000000-7f1236000000 rw-p 00000000 /system/framework/boot.art 7f1236000000-7f1237000000 rw-p 00000000 /system/framework/boot-framework.art ``` ### ensure_loaded (初始化) > 统一初始化入口,确保 Socket 服务器就绪,并发注入多个 Zygote 进程 - 这里 wait_async 再返回就是避免让程序以为是返回成功了的,我们等待其他实列完成。 - 创建 Unix Socket 服务器 - 生成随机名称:/frida-zymbiote-{uuid} - 使用抽象命名空间(UnixSocketAddressType.ABSTRACT) - 启动监听,等待 Payload 连接 - 枚举并注入所有 Zygote 进程 - 查找 zygote、zygote64、usap32、usap64 ( Unspecialized APP Process 也就是Android 的预 fork 进程池机制,用于加速应用启动) 这样尽可能遍历到了所有进程类型,只要有一个新的进程产生就一定会被hook,因为父进程中相关的函数已经被我们修改了。这里的实现非常优雅 - ```JavaScript 传统方式(无USAP): 应用启动请求 → Zygote fork → 初始化 → 应用运行 ↑ 耗时较长 USAP方式: Zygote 预fork → USAP池 (空闲进程) 应用启动请求 → 从USAP池取进程 → 快速配置 → 应用运行 ↑ 更快 ``` - 对每个进程调用 do_inject_zygote_agent() - 使用并发注入,等待所有完成 ```JavaScript private async void ensure_loaded (Cancellable? cancellable) throws Error, IOError { while (ensure_request != null) { try { yield ensure_request.future.wait_async (cancellable); return; } catch (Error e) { throw e; } catch (IOError e) { cancellable.set_error_if_cancelled (); } } ensure_request = new Promise<bool> (); if (server_name == null) { string name = "/frida-zymbiote-" + Uuid.string_random ().replace ("-", ""); var address = new UnixSocketAddress.with_type (name, -1, UnixSocketAddressType.ABSTRACT); try { var socket = new Socket (SocketFamily.UNIX, SocketType.STREAM, SocketProtocol.DEFAULT); socket.bind (address, true); socket.listen (); server_name = name; server_address = address; handle_zymbiote_connections.begin (socket); } catch (GLib.Error raw_err) { var err = new Error.TRANSPORT ("%s", raw_err.message); ensure_request.reject (err); throw err; } } uint pending = 1; GLib.Error? first_error = null; CompletionNotify on_complete = error => { pending--; if (error != null && first_error == null) first_error = error; if (pending == 0) { var source = new IdleSource (); source.set_callback (ensure_loaded.callback); source.attach (MainContext.get_thread_default ()); } }; foreach (HostProcessInfo info in System.enumerate_processes (new ProcessQueryOptions ())) { var name = info.name; if (name == "zygote" || name == "zygote64" || name == "usap32" || name == "usap64") { uint pid = info.pid; if (zymbiote_patches.has_key (pid)) continue; pending++; do_inject_zygote_agent.begin (pid, name, cancellable, on_complete); } } on_complete (null); yield; on_complete = null; if (first_error == null) { ensure_request.resolve (true); } else { ensure_request.reject (first_error); ensure_request = null; throw_api_error (first_error); } } ``` ### inject_zymbiote (核心注入逻辑) > 我们可以看见 do_inject_zygote_agent 包装了我们的 inject_zymbiote - 准备注入数据 - 调用 prepare_zymbiote_injection() 获取 ZymbiotePrepResult - 包含 payload、内存地址、ArtMethod 槽位等 - 暂停目标进程 - 发送 SIGSTOP 暂停 Zygote - 等待进程停止,确保内存写入安全 - 应用补丁 - 创建 ZymbiotePatches 管理补丁 - 处理已补丁情况: - 若 already_patched,从文件读取原始内容 - 写入 payload 到可执行映射(payload_base) - 替换 ArtMethod 槽位指针为 payload 地址 - 保存补丁记录到 zymbiote_patches[pid] - 恢复进程 - finally 中发送 SIGCONT 恢复执行 ```TypeScript private async void do_inject_zygote_agent (uint pid, string name, Cancellable? cancellable, CompletionNotify on_complete) { try { yield inject_zymbiote (pid, cancellable); on_complete (null); } catch (GLib.Error e) { on_complete (e); } } private async void inject_zymbiote (uint pid, Cancellable? cancellable) throws Error, IOError { // 调用 prepare_zymbiote_injection() 获取 ZymbiotePrepResult var prep = yield prepare_zymbiote_injection (pid, cancellable); // 发送 SIGSTOP 信号,暂停进程 Posix.kill ((Posix.pid_t) pid, Posix.Signal.STOP); yield wait_until_stopped (pid, cancellable); try { // 创建 ZymbiotePatches 管理补丁 var patches = new ZymbiotePatches (); // 获取 payload 数据 unowned uint8[] payload = prep.payload.get_data (); // 如果已经补丁,则打开 payload 文件,读取原始数据 if (prep.already_patched) { var handle = Posix.open (prep.payload_path, Posix.O_RDONLY); if (handle == -1) { throw new Error.PERMISSION_DENIED ("Unable to open payload backing file: %s", strerror (errno)); } var backing_file = new FileDescriptor (handle); var original = new uint8[payload.length]; backing_file.pread_all (original, prep.payload_file_offset); //写入 payload 到可执行映射(payload_base) patches.apply (payload, prep.process_memory, prep.payload_base, new Bytes.take ((owned) original)); } else { patches.apply (payload, prep.process_memory, prep.payload_base); } if (prep.already_patched) { // 替换 ArtMethod 槽位指针为 payload 地址 patches.apply (prep.replaced_ptr, prep.process_memory, prep.art_method_slot, new Bytes (prep.original_ptr)); } else { patches.apply (prep.replaced_ptr, prep.process_memory, prep.art_method_slot); } // 保存补丁记录到 zymbiote_patches[pid] zymbiote_patches[pid] = patches; } finally { Posix.kill ((Posix.pid_t) pid, Posix.Signal.CONT); } } ``` ### do_prepare_zymbiote_injection(核心准备逻辑) > 这里算得上核心注入最核心的部分,也就是解析进程内存映射,解析符号地址,定位 ArtMethod 槽位,准备 Payload 数据。 ```Java private async ZymbiotePrepResult prepare_zymbiote_injection (uint pid, Cancellable? cancellable) throws Error, IOError { var task = new Task (this, cancellable, (obj, res) => { prepare_zymbiote_injection.callback (); }); task.set_task_data ((void *) pid, null); task.run_in_thread ((t, source_object, task_data, c) => { unowned RoboLauncher launcher = (RoboLauncher) t.get_unowned_source_object (); uint pid_to_prep = (uint) task_data; try { var r = do_prepare_zymbiote_injection (pid_to_prep, launcher.server_name); t.return_pointer ((owned) r, Object.unref); } catch (GLib.Error e) { t.return_error ((owned) e); } }); yield; try { return (ZymbiotePrepResult) (owned) task.propagate_pointer (); } catch (GLib.Error e) { throw_api_error (e); } } private static ZymbiotePrepResult do_prepare_zymbiote_injection (uint pid, string server_name) throws Error, IOError { // 进行一些变量的初始化 uint64 payload_base = 0; string? payload_path = null; uint64 payload_file_offset = 0; string? libc_path = null; string? runtime_path = null; Gee.List<Gum.MemoryRange?> heap_candidates = new Gee.ArrayList<Gum.MemoryRange?> (); var iter = ProcMapsIter.for_pid (pid); while (iter.next ()) { string path = iter.path; string flags = iter.flags; if (path.has_suffix ("/libstagefright.so") && "x" in flags) { if (payload_base == 0) { payload_base = iter.end_address - Gum.query_page_size (); payload_path = path; payload_file_offset = iter.file_offset; } } else if (path.has_suffix ("/libc.so")) { if (libc_path == null) libc_path = path; } else if (path.has_suffix ("/libandroid_runtime.so")) { if (runtime_path == null) runtime_path = path; } else if (flags == "rw-p" && is_boot_heap (path)) { uint64 start = iter.start_address; uint64 end = iter.end_address; heap_candidates.add (Gum.MemoryRange () { base_address = start, size = (size_t) (end - start), }); } } if (payload_base == 0) throw new Error.NOT_SUPPORTED ("Unable to pick a payload base"); if (libc_path == null) throw new Error.NOT_SUPPORTED ("Unable to detect libc.so path"); if (runtime_path == null) throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so path"); if (heap_candidates.is_empty) throw new Error.NOT_SUPPORTED ("Unable to detect any VM heap candidates"); var libc_entry = ProcMapsSoEntry.find_by_path (pid, libc_path); if (libc_entry == null) throw new Error.NOT_SUPPORTED ("Unable to detect libc.so entry"); var runtime_entry = ProcMapsSoEntry.find_by_path (pid, runtime_path); if (runtime_entry == null) throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so entry"); Gum.ElfModule libc; try { libc = new Gum.ElfModule.from_file (libc_path); } catch (Gum.Error e) { throw new Error.NOT_SUPPORTED ("Unable to parse libc.so: %s", e.message); } Gum.ElfModule runtime; try { runtime = new Gum.ElfModule.from_file (runtime_path); } catch (Gum.Error e) { throw new Error.NOT_SUPPORTED ("Unable to parse libandroid_runtime.so: %s", e.message); } uint64 set_argv0_address = 0; runtime.enumerate_exports (e => { if (e.name == "_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring") { set_argv0_address = runtime_entry.base_address + e.address; return false; } return true; }); if (set_argv0_address == 0) throw new Error.NOT_SUPPORTED ("Unable to locate android.os.Process.setArgV0(); please file a bug"); uint pointer_size = ("/lib64/" in libc_path) ? 8 : 4; var original_ptr = new uint8[pointer_size]; var replaced_ptr = new uint8[pointer_size]; (new Buffer (new Bytes.static (original_ptr), ByteOrder.HOST, pointer_size)).write_pointer (0, set_argv0_address); (new Buffer (new Bytes.static (replaced_ptr), ByteOrder.HOST, pointer_size)).write_pointer (0, payload_base); var fd = open_process_memory (pid); uint64 art_method_slot = 0; bool already_patched = false; foreach (var candidate in heap_candidates) { var heap = new uint8[candidate.size]; var n = fd.pread (heap, candidate.base_address); if (n != heap.length) throw new Error.NOT_SUPPORTED ("Short read"); void * p = memmem (heap, original_ptr); if (p == null) { p = memmem (heap, replaced_ptr); already_patched = p != null; } if (p != null) { art_method_slot = candidate.base_address + ((uint8 *) p - (uint8 *) heap); break; } } if (art_method_slot == 0) throw new Error.NOT_SUPPORTED ("Unable to locate method slot; please file a bug"); var blob = (pointer_size == 8) #if ARM || ARM64 ? Frida.Data.Android.get_zymbiote_arm64_bin_blob () : Frida.Data.Android.get_zymbiote_arm_bin_blob (); #else ? Frida.Data.Android.get_zymbiote_x86_64_bin_blob () : Frida.Data.Android.get_zymbiote_x86_bin_blob (); #endif unowned uint8[] payload_template = blob.data; void * p = memmem (payload_template, "/frida-zymbiote-00000000000000000000000000000000".data); assert (p != null); size_t data_offset = (uint8 *) p - (uint8 *) payload_template; var payload = new Buffer (new Bytes (payload_template), ByteOrder.HOST, pointer_size); size_t cursor = data_offset; payload.write_string (cursor, server_name); cursor += 64; payload.write_pointer (cursor, art_method_slot); cursor += pointer_size; payload.write_pointer (cursor, set_argv0_address); cursor += pointer_size; string[] wanted = { "socket", "connect", "__errno", "getpid", "getppid", "sendmsg", "recv", "close", "raise", }; var index_of = new Gee.HashMap<string, int> (); for (int i = 0; i != wanted.length; i++) index_of[wanted[i]] = i; var addrs = new uint64[wanted.length]; uint pending = wanted.length; libc.enumerate_exports (e => { if (index_of.has_key (e.name)) { int idx = index_of[e.name]; addrs[idx] = libc_entry.base_address + e.address; pending--; } return pending != 0; }); for (int i = 0; i != addrs.length; i++) { assert (addrs[i] != 0); payload.write_pointer (cursor, addrs[i]); cursor += pointer_size; } return new ZymbiotePrepResult () { process_memory = fd, already_patched = already_patched, art_method_slot = art_method_slot, original_ptr = original_ptr, replaced_ptr = replaced_ptr, payload = payload.bytes, payload_base = payload_base, payload_path = payload_path, payload_file_offset = payload_file_offset, }; } ``` #### 调用链 ```JavaScript do_prepare_zymbiote_injection() (linux-host-session.vala:925) ├─ ProcMapsIter.for_pid() - 创建内存映射迭代器 ├─ ProcMapsIter.next() - 遍历内存映射 ├─ ProcMapsSoEntry.find_by_path() - 查找 SO 映射条目 ├─ Gum.ElfModule.from_file() - 解析 ELF 文件 ├─ Gum.ElfModule.enumerate_exports() - 枚举导出符号 ├─ open_process_memory() - 打开进程内存 (linux-host-session.vala:1692) │ └─ Posix.open("/proc/<pid>/mem") - 打开 /proc/<pid>/mem ├─ FileDescriptor.pread() - 读取进程内存 ├─ memmem() - 内存搜索 (linux-host-session.vala:1110) ├─ is_boot_heap() - 判断是否为 boot heap (linux-host-session.vala:1107) └─ Buffer.write_pointer() / write_string() - 填充 payload ``` #### 初始化变量 ```TypeScript uint64 payload_base = 0; string? payload_path = null; uint64 payload_file_offset = 0; string? libc_path = null; string? runtime_path = null; Gee.List<Gum.MemoryRange?> heap_candidates = new Gee.ArrayList<Gum.MemoryRange?> (); ``` - payload_base: payload 注入地址 - payload_path: payload 所在文件路径 - payload_file_offset: 文件偏移(用于已补丁情况) - libc_path: libc.so 路径 - runtime_path: libandroid_runtime.so 路径 - heap_candidates: boot heap 候选区域列表 #### 解析进程内存映射 - 创建内存映射迭代器 - ProcMapsIter.for_pid(pid) 读取 /proc/<pid>/maps - 逐行解析内存映射 - 定位 payload 基址(17.6.1 改进) - 查找 libstagefright.so 且可执行("x" in flags) - 选择映射末页:end_address - page_size - 记录文件路径和偏移,用于已补丁情况 - 定位 libc.so与 libandroid_runtime.so - 记录第一个匹配的 libc.so 路径 - 记录第一个匹配的 libandroid_runtime.so 路径 - 收集 boot heap 候选 - 检查 flags == "rw-p" 且通过 is_boot_heap() 判断 - is_boot_heap() 检查路径是否包含: - "boot.art" - "boot-framework.art" - "dalvik-LinearAlloc" - 添加到 heap_candidates ```Python var iter = ProcMapsIter.for_pid (pid); while (iter.next ()) { string path = iter.path; string flags = iter.flags; if (path.has_suffix ("/libstagefright.so") && "x" in flags) { if (payload_base == 0) { payload_base = iter.end_address - Gum.query_page_size (); payload_path = path; payload_file_offset = iter.file_offset; } } else if (path.has_suffix ("/libc.so")) { if (libc_path == null) libc_path = path; } else if (path.has_suffix ("/libandroid_runtime.so")) { if (runtime_path == null) runtime_path = path; } else if (flags == "rw-p" && is_boot_heap (path)) { uint64 start = iter.start_address; uint64 end = iter.end_address; heap_candidates.add (Gum.MemoryRange () { base_address = start, size = (size_t) (end - start), }); } } ``` #### 获取 libc 与 runtime 信息 > 其实就是校验我们必须需要的信息是否存在,然后再根据我们的 libc_path 与 runtime_path 找到 libc 与 runtime 的基地址。然后对应进行符号解析得到 ELF 对象,然后查找我们的 setArgV0Native 函数(_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring)。 > > 为什么需要查找我们的 setArgV0Native 函数捏?因为这是后续查找ArtMethod的关键,artMethod 的函数指针就是存储的 setArgV0Native 函数地址,所以我们可以通过解析符号表定位到 setArgV0Native 函数就可以间接定位到 ArtMethod ```JavaScript if (payload_base == 0) throw new Error.NOT_SUPPORTED ("Unable to pick a payload base"); if (libc_path == null) throw new Error.NOT_SUPPORTED ("Unable to detect libc.so path"); if (runtime_path == null) throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so path"); if (heap_candidates.is_empty) throw new Error.NOT_SUPPORTED ("Unable to detect any VM heap candidates"); var libc_entry = ProcMapsSoEntry.find_by_path (pid, libc_path); if (libc_entry == null) throw new Error.NOT_SUPPORTED ("Unable to detect libc.so entry"); var runtime_entry = ProcMapsSoEntry.find_by_path (pid, runtime_path); if (runtime_entry == null) throw new Error.NOT_SUPPORTED ("Unable to detect libandroid_runtime.so entry"); Gum.ElfModule libc; try { libc = new Gum.ElfModule.from_file (libc_path); } catch (Gum.Error e) { throw new Error.NOT_SUPPORTED ("Unable to parse libc.so: %s", e.message); } Gum.ElfModule runtime; try { runtime = new Gum.ElfModule.from_file (runtime_path); } catch (Gum.Error e) { throw new Error.NOT_SUPPORTED ("Unable to parse libandroid_runtime.so: %s", e.message); } uint64 set_argv0_address = 0; runtime.enumerate_exports (e => { if (e.name == "_Z27android_os_Process_setArgV0P7_JNIEnvP8_jobjectP8_jstring") { set_argv0_address = runtime_entry.base_address + e.address; return false; } return true; }); if (set_argv0_address == 0) throw new Error.NOT_SUPPORTED ("Unable to locate android.os.Process.setArgV0(); please file a bug"); ``` #### 定位 ArtMethod 信息 > 根据前面获取的 setArgV0Native 函数指针在 boot_heap 区域搜索就可以找到我们的 ArtMethod ```Java var fd = open_process_memory (pid); uint64 art_method_slot = 0; bool already_patched = false; foreach (var candidate in heap_candidates) { var heap = new uint8[candidate.size]; var n = fd.pread (heap, candidate.base_address); if (n != heap.length) throw new Error.NOT_SUPPORTED ("Short read"); void * p = memmem (heap, original_ptr); if (p == null) { p = memmem (heap, replaced_ptr); already_patched = p != null; } if (p != null) { art_method_slot = candidate.base_address + ((uint8 *) p - (uint8 *) heap); break; } } if (art_method_slot == 0) throw new Error.NOT_SUPPORTED ("Unable to locate method slot; please file a bug"); ``` #### 填充payload > 这里搜索frida-zymbiote-00000000000000000000000000000000,是因为在 zymbiote 中初始化name就是这个名字 > > static volatile const FridaApi frida = > > { > > .name = "/frida-zymbiote-00000000000000000000000000000000", > > }; > > 只要找到这个位置,那么就根据这个位置后面填写我们对应的数据即可,完善这个 FridaApi 结构体 ```Java var blob = (pointer_size == 8) #if ARM || ARM64 ? Frida.Data.Android.get_zymbiote_arm64_bin_blob () : Frida.Data.Android.get_zymbiote_arm_bin_blob (); #else ? Frida.Data.Android.get_zymbiote_x86_64_bin_blob () : Frida.Data.Android.get_zymbiote_x86_bin_blob (); #endif unowned uint8[] payload_template = blob.data; void * p = memmem (payload_template, "/frida-zymbiote-00000000000000000000000000000000".data); assert (p != null); size_t data_offset = (uint8 *) p - (uint8 *) payload_template; var payload = new Buffer (new Bytes (payload_template), ByteOrder.HOST, pointer_size); size_t cursor = data_offset; payload.write_string (cursor, server_name); cursor += 64; payload.write_pointer (cursor, art_method_slot); cursor += pointer_size; payload.write_pointer (cursor, set_argv0_address); cursor += pointer_size; string[] wanted = { "socket", "connect", "__errno", "getpid", "getppid", "sendmsg", "recv", "close", "raise", }; var index_of = new Gee.HashMap<string, int> (); for (int i = 0; i != wanted.length; i++) index_of[wanted[i]] = i; var addrs = new uint64[wanted.length]; uint pending = wanted.length; libc.enumerate_exports (e => { if (index_of.has_key (e.name)) { int idx = index_of[e.name]; addrs[idx] = libc_entry.base_address + e.address; pending--; } return pending != 0; }); for (int i = 0; i != addrs.length; i++) { assert (addrs[i] != 0); payload.write_pointer (cursor, addrs[i]); cursor += pointer_size; } ``` - 我们可以知道 payload 的结构体 > 其实就是将Payload需要的libc函数地址,ArtMethod函数地址填充进去,方便后续运作 > > 1. name[64] - Unix socket 名称 > 2. art_method_slot - ArtMethod 槽位指针地址 > 3. original_set_argv0 - 原始函数指针 > 4. socket - libc socket 函数 > 5. connect - libc connect 函数 > 6. __errno - libc errno 函数 > 7. getpid - libc getpid 函数 > 8. getppid - libc getppid 函数 > 9. sendmsg - libc sendmsg 函数 > 10. recv - libc recv 函数 > 11. close - libc close 函数 > 12. raise - libc raise 函数 ```C++ struct _FridaApi { char name[64]; void ** art_method_slot; void (* original_set_argv0) (JNIEnv * env, jobject clazz, jstring name); int (* socket) (int domain, int type, int protocol); int (* connect) (int sockfd, const struct sockaddr * addr, socklen_t addrlen); int * (* __errno) (void); pid_t (* getpid) (void); pid_t (* getppid) (void); ssize_t (* sendmsg) (int sockfd, const struct msghdr * msg, int flags); ssize_t (* recv) (int sockfd, void * buf, size_t len, int flags); int (* close) (int fd); int (* raise) (int sig); }; ```
登录后可查看完整内容
传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!!
#基础理论
#程序开发
#HOOK注入
#系统相关
#源码框架
#工具脚本
收藏
・
35
点赞
・
40
打赏
分享
分享到微信
分享到QQ
分享到微博
赞赏记录
参与人
雪币
留言
时间
铭信
你的帖子非常有用,感谢分享!
2026-9-7 21:56
ngiokweng
这个讨论对我很有帮助,谢谢!
2026-8-14 10:07
MochaCo
这个讨论对我很有帮助,谢谢!
2026-7-3 14:08
Damn7Kx
你的分享对大家帮助很大,非常感谢!
2026-5-5 23:26
git_22413Worktestse
感谢你的贡献,论坛因你而更加精彩!
2026-4-1 17:15
andy张刘
谢谢你的细致分析,受益匪浅!
2026-4-1 15:39
wx_Shutdown-r
你的分享对大家帮助很大,非常感谢!
2026-3-31 15:52
老板大七
你的分享对大家帮助很大,非常感谢!
2026-3-12 20:05
Vincent_sec
非常支持你的观点!
2026-3-10 19:50
深山樵夫
为你点赞!
2026-3-10 16:22
anthropj
你的帖子非常有用,感谢分享!
2026-3-10 15:44
by_Lin
为你点赞!
2026-3-7 01:24
0x2D1
非常支持你的观点!
2026-3-5 13:45
孤独的街
期待更多优质内容的分享,论坛有你更精彩!
2026-3-4 18:39
git_21210wzzzh2025
为你点赞!
2026-3-2 20:13
拾海
为你点赞!
2026-2-28 09:59
黄金马上青铜
感谢你分享这么好的资源!
2026-2-27 13:26
mb_wpitiize
这个讨论对我很有帮助,谢谢!
2026-2-27 10:11
mb_phsdvaem
你的分享对大家帮助很大,非常感谢!
2026-2-27 08:31
m0y1_final
感谢你的积极参与,期待更多精彩内容!
2026-2-25 16:44
Cherzsh
你的帖子非常有用,感谢分享!
2026-2-24 14:22
道总行走江离
+1
这个讨论对我很有帮助,谢谢!
2026-2-22 17:44
季东
为你点赞!
2026-2-22 01:54
ulong9527
期待更多优质内容的分享,论坛有你更精彩!
2026-2-21 14:04
wx_null_420
感谢你分享这么好的资源!
2026-2-19 09:51
jpacg
你的分享对大家帮助很大,非常感谢!
2026-2-18 22:12
风兮木晓
为你点赞!
2026-2-14 11:54
爬下全世界
你的分享对大家帮助很大,非常感谢!
2026-2-13 18:12
mb_lunshtoc
你的分享对大家帮助很大,非常感谢!
2026-2-13 15:19
IamHuskar
你的帖子非常有用,感谢分享!
2026-2-12 11:29
Cr0ssx2
你的帖子非常有用,感谢分享!
2026-2-12 11:17
mb_fhpfcoqp
为你点赞!
2026-2-11 14:21
zhangtheo
非常支持你的观点!
2026-2-10 10:08
mb_iesvmzqc
为你点赞!
2026-2-10 09:58
nonovo
非常支持你的观点!
2026-2-9 22:24
东方玻璃
为你点赞!
2026-2-9 18:13
天水姜伯约
为你点赞!
2026-2-9 15:37
fei3ei
感谢你的贡献,论坛因你而更加精彩!
2026-2-9 10:04
我的小拇指啊
期待更多优质内容的分享,论坛有你更精彩!
2026-2-8 20:43
cr_lgdx
感谢你的积极参与,期待更多精彩内容!
2026-2-8 19:44
查看更多
赞赏
×
1 雪花
5 雪花
10 雪花
20 雪花
50 雪花
80 雪花
100 雪花
150 雪花
200 雪花
支付方式:
微信支付
赞赏留言:
快捷留言
感谢分享~
精品文章~
原创内容~
精彩转帖~
助人为乐~
感谢分享~
最新回复
(
9
)
Imxz
雪 币:
112
活跃值:
(9405)
能力值:
( LV2,RANK:10 )
在线值:
发帖
6
回帖
750
粉丝
9
关注
私信
Imxz
2
楼
tql
2026-2-8 18:27
0
MsScotch
雪 币:
4164
活跃值:
(4901)
能力值:
( LV2,RANK:10 )
在线值:
发帖
3
回帖
394
粉丝
5
关注
私信
MsScotch
3
楼
tql
2026-2-9 12:23
0
thouger_
雪 币:
9
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
34
粉丝
0
关注
私信
thouger_
4
楼
牛逼
2026-2-9 15:43
0
uni7corn
雪 币:
0
活跃值:
(2445)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
182
粉丝
0
关注
私信
uni7corn
5
楼
感谢分享
2026-2-12 11:13
0
kccp
雪 币:
14
活跃值:
(740)
能力值:
( LV2,RANK:10 )
在线值:
发帖
3
回帖
83
粉丝
0
关注
私信
kccp
6
楼
6
2026-2-12 11:49
0
土豆爱学习
雪 币:
70
活跃值:
(538)
能力值:
( LV2,RANK:10 )
在线值:
发帖
4
回帖
60
粉丝
5
关注
私信
土豆爱学习
7
楼
111
2026-2-12 19:51
0
qig
雪 币:
47
活跃值:
(755)
能力值:
( LV2,RANK:10 )
在线值:
发帖
0
回帖
55
粉丝
1
关注
私信
qig
8
楼
感谢分享
2026-2-18 09:44
0
mb_zcfhleoj
雪 币:
148
能力值:
( LV1,RANK:0 )
在线值:
发帖
0
回帖
42
粉丝
0
关注
私信
mb_zcfhleoj
10
楼
感谢分享
2026-4-22 16:37
0
游客
登录
|
注册
方可回帖
回帖
表情
雪币赚取及消费
高级回复
返回
Elenia
7
13
发帖
19
回帖
330
RANK
关注
私信
他的文章
[原创] Qemu 源码分析(PCI篇)
24888
[原创] Frida 17.6 最新Hook注入方案分析(Zymbiote注入机制)
75381
[原创] Android 内核启动流程分析 (13.0.0_r3)
21391
[原创] UE4的启动流程分析
3184
[原创]Linux 内核攻击:Punch hole (2025 Backdoor skernel 复现)
20828
关于我们
联系我们
企业服务
看雪公众号
专注于PC、移动、智能设备安全研究及逆向工程的开发者社区
看原图
赞赏
×
雪币:
+
留言:
快捷留言
为你点赞!
返回
顶部