type
Post
status
Published
date
Jul 13, 2026
slug
summary
基于最新版
Magisk 的 Zygisk ,整理并移植到 r0zygisk 的完整过程,同时把 KernelSU / SukiSU 环境下的 WebUI 一并补齐。tags
r0zygisk
看雪
category
转载
icon
password
网址
上次编辑时间
Jul 13, 2026 07:27 AM
comment
AI 总结
Vibe Coding 从源码阅读到模块开发:基于最新版 Magisk Zygisk,移植实现一个带 WebUI、支持 Android 16 的 Zygisk 等效模块r0zygisk(一)
这篇文章主要记录了基于最新版
Magisk 的 Zygisk ,整理并移植到 r0zygisk 的完整过程,同时把 KernelSU / SukiSU 环境下的 WebUI 一并补齐。完整源码和发布包已经开源在 GitHub Releases:
整个过程可以归纳成八条主线:
- 先解决项目本身无法构建的问题。
- 再修复生成 zip 后
SukiSU / KernelSU无法安装的问题(缺少正确目录的libzygisk.so)。
- 然后迁移
r0zygisk的 Zygisk API,以及 Android 16 / Baklava 相关 JNI 签名。
- 接着梳理
zygiskd的执行链路,为后续排查daemon / loader / companion问题建立上下文。
- 再重构 WebUI,并围绕真实刷入反馈连续修复
exec、状态显示和 SukiSU 模块描述污染问题。
- 完成第一版阶段性收敛,固化可复现的构建与刷入结果。
- 对 Hunter 检测链路做逆向定位,明确命中点在 native 侧
checkZygisk分支。
- 结合迁移记录完成第二版大修:切换注入模式并统一命名为
r0z。
目录
- 项目背景与环境
- 先确认最新版 Magisk Zygisk 是否已适配 Android 16
- 借助 code-panorama + Codex 梳理 Zygisk 源码
- 开始移植:把最新版 Zygisk 迁到 r0zygisk
- WebUI 重构与联调记录
- 最终状态
- 第二版大修:通过 Hunter 的 Zygisk 检测
- 结合 AI:使用新的注入方式完成第二版代码
- 总结
一、项目背景与环境
https://github.com/xuanyuanzhifeng/code-panorama
这个工具对源码阅读和结构定位帮助很大。
文章整体也使用了 AI 辅助完成,具体环境是:
codex cli + gpt5.4 模型。
!结果测试覆盖了 Android 14 到 Android 16 机型。
项目初始目录结构如下:
!
r0zygisk 是参考 ZygiskNext 的内容继续做的,使用 Android Studio 编译,开发环境如下。Android Gradle Plugin 和 Gradle Wrapper 版本:
- Android Gradle Plugin:
8.9.1
- Gradle Wrapper:
8.11.1
JDK 配置在
gradle.properties 中:- org.gradle.java.home=/Library/Java/JavaVirtualMachines/jdk-17.jdk/Contents/Home
项目打包过程中,前期遇到的问题主要有这些:
- Gradle 使用 Java 8,改为项目级固定 JDK 17。
- Java/Gradle 访问 Google Maven TLS 失败,增加国内 Maven 镜像。
- 中文路径导致 Gradle/JVM 编码问题,增加 UTF-8 JVM 参数。
- 当前目录不是 Git 仓库,版本号读取失败,增加 fallback。
- 本机缺少项目指定 NDK,改为本机已安装的 NDK
26.1.10909125。
- 源码包缺少
lsplt子模块,按.gitmodules克隆补齐。
- Rust 缺少 Android 交叉编译 target,用
rustup target add安装四个目标平台。
开发环境准备好之后,就开始进入正式工作:基于最新版
Magisk 的 Zygisk ,做一个支持 Android 16 的 r0zygisk 。二、先确认最新版 Magisk Zygisk 是否已适配 Android 16
动手移植之前,先确认当前
Magisk-master 这版到底有没有完成 Android 16 适配。我先让 AI 帮我判断这版
Magisk-master 的适配情况,提问如下:
得到的分析结果如下:
通过对
Magisk 仓库的查看和 AI 分析结果,可以确认这个版本已经适配 Android 16,那么后续就可以直接基于这版 Zygisk 来做迁移。三、借助 code-panorama + Codex 梳理 Zygisk 源码
3.1 分析过程
在开始改代码之前,先把最新版
Magisk Zygisk 的执行流程和关键入口梳理清楚。阅读
Magisk 源码时,我先把前面提到的开源库本地安装并运行起来:
!点一下生成全景图,就开始分析了:
!
生成结果如下:
!
借助这个视图,可以快速定位主要入口。
由于我主要关注的是
zygisk ,因此后续单独针对这部分代码继续下钻:
!使用这个库后,项目入口和主要结构会清晰很多。具体使用过程这里就不展开了。
在这个基础上,我再结合
codex 继续分析代码。在
/Magisk-master/native/src/core/zygisk 目录下,打开 codex,给它下指令:
等待 AI 执行:
!
结果:
!
3.2 Magisk Zygisk 源码执行流程与加载过程逐段详解
#### 3.2.0 分析标题与范围
本文基于当前目录
/native/src/core/zygisk 中的源码编写,目标是把 Zygisk 的“如何被加载、如何接管 Zygote、如何加载模块、如何与 magiskd/zygiskd 协作、最后如何清理卸载”串成一条完整链路,并按文件解释代码。目录内文件职责:
| 文件 | 作用 |
| --- | --- |
|
zygisk.hpp | Zygisk 内部公共声明、native bridge 常量、日志宏、NativeBridge ABI 结构 |
| entry.cpp | Zygisk 的两个 C++ 入口:NativeBridge 注入口、zygisk companion 进程入口 |
| hook.cpp | bootstrap 核心:PLT hook、JNI hook、真实 native bridge 续载、Zygisk 自卸载 |
| module.hpp | 模块 ABI、API 表、specialize 参数对象、 ZygiskContext / ZygiskModule 类型定义 |
| module.cpp | 模块加载、API 适配、pre/post specialize 调度、FD 清理、denylist/unmount 处理 |
| api.hpp | 面向 Zygisk 模块作者的公开 API 头文件 |
| jni_hooks.hpp | 由脚本生成的 Zygote JNI native 方法包装器 |
| gen_jni_hooks.py | 生成 jni_hooks.hpp 的脚本,维护不同 Android/OEM JNI 签名 |
| daemon.rs | magiskd 侧 Zygisk 状态、属性注入、模块 fd 下发、companion 连接管理 |
| mod.rs | Rust 模块入口,导出 C++ 调用的 exec_companion_entry |#### 3.2.1 总执行链路
##### 3.2.1.1 启用阶段:magiskd 写入 native bridge 属性
Zygisk 不是通过修改 Zygote 可执行文件直接进入的,而是借用了 Android native bridge 加载机制。
Rust 侧
ZygiskState::set_prop() 修改属性:写入策略:
- 如果原属性为空或
"0",写成libzygisk.so。
- 如果原本已有 native bridge,则写成
libzygisk.so加上原值。
因此 Zygote 启动 native bridge 时会优先加载
libzygisk.so 。如果系统原本就配置了 native bridge,Zygisk 后续会从属性字符串里剥出原值并重新加载真实 native bridge,避免破坏原系统行为。##### 3.2.1.2 注入阶段:Zygote 加载 libzygisk.so
Android 的
LoadNativeBridge 会查找动态库中的导出符号 NativeBridgeItf 。Zygisk 在 entry.cpp 中导出同名符号:当
isCompatibleWith() 被调用时,Zygisk 已经进入 Zygote 进程。这里执行:zygisk_logging()初始化日志。
hook_entry()开始 bootstrap。
- 返回
false,表示自己不是真正要使用的 native bridge。
##### 3.2.1.3 Bootstrap 阶段:安装早期 PLT hook
hook_entry() 创建全局 HookContext ,调用 HookContext::hook_plt() 。第一批 PLT hook 挂在两个库上:
| 目标库 | 被 hook 的符号 | 目的 |
| --- | --- | --- |
|
libnativebridge.so | dlclose | 捕获 native bridge 加载流程结束点,拿到 runtime callbacks,并补载真实 native bridge |
| libandroid_runtime.so | strdup | 捕获 com.android.internal.os.ZygoteInit 字符串,触发 JNI 方法替换 |
| libandroid_runtime.so | fork | Zygisk 提前 fork 后,让原始逻辑看到缓存 pid,避免重复 fork |
| libandroid_runtime.so | unshare | 进入新 mount namespace 后执行 denylist unmount |
| libandroid_runtime.so | selinux_android_setcontext | secontext 切换前预取 logd |
| libandroid_runtime.so | __android_log_close | fork/specialize 过程中控制 log fd 关闭 |##### 3.2.1.4 接管 Zygote JNI:替换 specialize native 方法
AndroidRuntime 启动 Java 层 ZygoteInit 前会处理类名字符串。
strdup("com.android.internal.os.ZygoteInit") 触发 Zygisk 的 new_strdup() ,调用 hook_zygote_jni() 。hook_zygote_jni() 做三件事:- 找到当前
JavaVM和JNIEnv。
- 用 NativeBridgeRuntimeCallbacks 枚举
com/android/internal/os/Zygote已注册 native 方法。
- 用
RegisterNatives替换这些目标方法:
nativeForkAndSpecialize
nativeSpecializeAppProcess
nativeForkSystemServer
替换后的函数来自
jni_hooks.hpp 。它们都是包装器:先构造 ZygiskContext ,执行 pre 回调,再调用原始 JNI 函数,最后执行 post 回调。##### 3.2.1.5 App/system\_server fork 与模块加载
当 Zygote 要创建 app 或 system\_server 时,流程进入 Zygisk 包装器:
- 构造
AppSpecializeArgs_v5或ServerSpecializeArgs_v1。
- 构造栈上
ZygiskContext,全局g_ctx指向它。
- pre 阶段:
- 对 fork 型方法,Zygisk 先调用真实
fork()。
- 子进程向 magiskd 查询当前 uid/process 的 flags。
- 如果进程不在 enforced denylist 且不是 Magisk app,则接收模块 so 的 fd。
- 使用
android_dlopen_ext(..., ANDROID_DLEXT_USE_LIBRARY_FD, ...)从 fd 加载模块。
- 调用模块
zygisk_module_entry、onLoad、preAppSpecialize或preServerSpecialize。
- 调用原始 Zygote native specialize 函数。
- post 阶段:
- 调用模块
postAppSpecialize或postServerSpecialize。
- 根据模块选项可能
dlclose模块。
- 恢复 Zygote JNI hook。
- 安装自卸载 hook。
##### 3.2.1.6 companion 进程
模块如果需要 root 权限,可以调用
Api::connectCompanion() 。这不会让 app 进程变成 root,而是:- 模块进程向 magiskd 发
ZygiskRequest::ConnectCompanion。
- magiskd 按 32/64 位选择或启动一个
zygiskd。
zygiskd预先加载所有模块 companion entry。
- 每次模块请求 companion 时,magiskd 把 client fd 转交给对应
zygiskd。
zygiskd调用目标模块的zygisk_companion_entry(client)。
##### 3.2.1.7 清理与自卸载
子进程 specialize 完成后,Zygisk 不希望一直留在 app/system\_server 进程中。析构
ZygiskContext 时:- 清掉模块 API 表。
- 标记
g_hook->should_unmap = true。
- 恢复 Zygote JNI 方法。
- 在
libart.so上 hookpthread_attr_destroy。
后续 JVM 创建线程时会调用
pthread_attr_destroy 。Zygisk 在这个安全时机:- 恢复早期 PLT hook。
- 用
musttail跳转到dlclose(self_handle)。
- 避免
dlclose返回到已经被 unmap 的 Zygisk 代码。
#### 3.2.2 zygisk.hpp 逐行/逐段解释
##### 行 1-4:基础包含
#pragma once 防止头文件重复包含。 jni.h 提供 JNI 类型, core.hpp 提供 Magisk core 内部工具、日志、IPC 等声明。##### 行 6-8:关键常量
ZYGISKLDR 是 Zygisk loader 库名: libzygisk.so 。NBPROP 是 native bridge 属性名: ro.dalvik.vm.native.bridge 。Rust 与 C++ 两边都使用同一语义。##### 行 9-19:按 ABI 区分日志前缀
64 位编译时日志前缀为
zygisk64 ,32 位为 zygisk32 。这样同一设备有双 Zygote 时,logcat 中可以区分是哪一套代码在运行。##### 行 21-23:极详细日志开关
ZLOGV 默认被定义成空操作。取消注释可把 verbose 日志转成 debug 日志,但正常构建不启用,避免刷屏和性能影响。##### 行 25-26:跨文件函数声明
hook_entry() 是 native bridge 回调进入 bootstrap 的入口。hookJniNativeMethods() 是 Zygisk API 暴露给模块的 JNI hook 实现入口,内部会转给当前全局 HookContext 。##### 行 28-42:NativeBridge ABI 结构
这两个结构是 Android native bridge 头文件中的 ABI 子集:
NativeBridgeRuntimeCallbacks 提供三类能力:- 通过 method id 获取 shorty。
- 查询某个 class 的 native 方法数量。
- 导出某个 class 的 native 方法表。
Zygisk 依赖它枚举并替换 Zygote native 方法。
NativeBridgeCallbacks 是 native bridge 动态库导出的 NativeBridgeItf 结构。Zygisk 只填了版本、padding、 isCompatibleWith 。#### 3.2.3 entry.cpp 逐行/逐段解释
##### 行 1-9:系统与内部头文件
android/dlext.h 用于从 fd 加载 so。 dlfcn.h 用于 dlsym 。 poll.h 用于 companion daemon 等待请求。##### 行 13-14:companion entry 类型
comp_entry 是模块 companion 函数类型: void (*)(int) 。exec_companion_entry 是 Rust 侧 mod.rs 导出的 C ABI 函数。C++ 不直接创建线程,而是把 companion 请求交给 Rust 线程池执行。##### 行 16-19:zygiskd 参数校验
zygiskd(int socket) 是 root companion daemon 主循环。开头要求:- 当前 uid 必须是 root。
- 传入 socket fd 必须有效。
不满足直接退出。
##### 行 20-26:设置进程名与日志
64 位进程命名为
zygiskd64 ,32 位为 zygiskd32 。这和 magiskd 中按 ABI 维护两个 socket 对应。##### 行 28-49:加载模块 companion entry
从 magiskd 传来的 socket 中接收一组模块 fd。每个 fd:
fstat确认是普通文件。
- 使用
android_dlopen_ext("/jit-cache", RTLD_LAZY, &info)从 fd 加载。
- 查找符号
zygisk_companion_entry。
- 不管成功与否,都往
modulesvector 放入 entry 指针,保证模块 id 与数组下标一致。
- 关闭原 fd。
"/jit-cache" 只是提供给 linker 的伪路径,真实库来自 fd。##### 行 51-53:启动确认
write_int(socket, 0) 告诉 magiskd:zygiskd 已经加载模块 companion 表,可以开始转发请求。##### 行 54-73:zygiskd 主循环
poll 阻塞等待 magiskd 发来 client fd。每次请求:recv_fd(socket)收到一个 client socket。
- 从 client 读
module_id。
- 如果 id 合法且该模块有 companion entry,调用
exec_companion_entry(client, entry)。
- 否则关闭 client。
这里的 companion entry 不是直接同步执行,而是进入 Rust 线程池,允许并发处理。
##### 行 76-84:zygisk\_main
这是 magisk applet 入口,只有内部执行
magisk zygisk companion <fd> 时使用。参数匹配
argc == 3 && argv[1] == "companion" 时进入 zygiskd(parse_int(argv[2])) 。##### 行 86-96:NativeBridge 注入口
导出符号
NativeBridgeItf ,让 Android native bridge loader 能找到。isCompatibleWith lambda 是 Zygisk 第一次执行自己代码的关键点:- 初始化 Zygisk 日志。
- 调用
hook_entry()。
- 打印加载成功。
- 返回
false。
返回
false 是有意的:Zygisk 要借 native bridge 入口执行注入,而不是最终承担 native bridge 功能。#### 3.2.4 hook.cpp 逐行/逐段解释
##### 行 1-15:依赖
sys/mman.h 、 sys/resource.h 、 dlfcn.h 、 unwind.h 支撑内存映射扫描、fd 上限、动态符号、栈回溯。lsplt.hpp 是 PLT hook 库。Zygisk bootstrap 和模块 API 的 PLT hook 都依赖它。module.hpp 提供 ZygiskContext , jni_hooks.hpp 提供生成好的 JNI 包装函数列表。##### 行 18-90:源码内 bootstrap 流程图
这段注释是官方写在源码里的关键说明。它表达的核心是:
NativeBridgeItf被 native bridge loader 调用。
- Zygisk 安装 PLT hook。
dlclosehook 捕获 native bridge 加载结束。
strdup("ZygoteInit")hook 捕获 JVM 即将进入 ZygoteInit 的时机。
- Zygisk 替换 Zygote JNI native 方法。
- specialize 后通过
pthread_attr_destroyhook 自卸载。
##### 行 91-99:常量与类型别名
kZygoteInit 是 Java 层入口类全名。kZygote 是 JNI 查找类名格式: com/android/internal/os/Zygote 。kForkApp 、 kSpecializeApp 、 kForkServer 是要替换的 native 方法名。JNIMethods 是 std::span<JNINativeMethod> ,方便对生成数组做统一处理。JNIMethodsDyn 是动态分配的 JNI 方法表和数量。##### 行 100-119:HookContext
HookContext 继承 JniHookDefinitions ,因此直接持有三组生成的 JNI hook 数组:- app fork 方法数组。
- app specialize 方法数组。
- system\_server fork 方法数组。
字段含义:
| 字段 | 含义 |
| --- | --- |
|
plt_backup | 记录已经成功注册的 PLT hook,用于后续恢复 |
| runtime_callbacks | NativeBridgeRuntimeCallbacks,枚举 JNI native 方法时使用 |
| self_handle | Zygisk 自身的 dlopen handle,用于最后 dlclose |
| should_unmap | 是否允许自卸载 |方法分两类:
- bootstrap/卸载:
hook_plt、hook_unloader、restore_plt_hook、post_native_bridge_load。
- JNI 替换:
hook_zygote_jni、restore_zygote_hook、hook_jni_methods。
##### 行 123-136:全局上下文
g_ctx 指向当前 specialize 流程中的 ZygiskContext 。它通常指向栈对象,只在包装 JNI 函数执行期间有效。g_hook 指向长期存在的 HookContext ,从 native bridge 加载后一直存在到自卸载前。get_defs() 返回 g_hook ,供 jni_hooks.hpp 生成的包装函数获取原始 native 函数指针。##### 行 140-149:strdup hook
new_strdup(const char *str) 检查字符串是否等于 com.android.internal.os.ZygoteInit 。如果匹配,调用
g_hook->hook_zygote_jni() 。随后调用原始
strdup 。这个 hook 的意义是抓住 AndroidRuntime 即将启动 ZygoteInit 的窗口,此时 JVM 已创建,Zygote native 方法也已经可枚举和替换。##### 行 151-154:fork hook
new_fork() 的逻辑:- 如果
g_ctx存在并且g_ctx->pid >= 0,返回缓存 pid。
- 否则调用原始
fork()。
Zygisk 在
fork_pre() 中会提前执行真正 fork。随后原始 Zygote native 函数内部再调用 fork() 时,会被这个 hook 拦截并返回同一个 pid,避免 fork 两次。##### 行 156-167:unshare hook
Zygote specialize 过程中可能调用
unshare(CLONE_NEWNS) 创建私有 mount namespace。如果当前在 Zygisk specialize 流程中,且创建 mount namespace 成功,并且 flags 中有
DO_REVERT_UNMOUNT ,则调用 revert_unmount() 。这用于 denylist 或模块强制隐藏时,从目标进程 mount namespace 中卸载 Magisk/模块痕迹。##### 行 169-175:selinux\_android\_setcontext hook
secontext 改变后,进程权限和 SELinux 限制会发生变化。这里在切换前调用
zygisk_get_logd() ,预先拿到 logd fd,避免后续上下文下无法访问或日志管道异常。##### 行 177-185:\_\_android\_log\_close hook
Zygote fork/specialize 时会关闭日志 fd。Zygisk 根据
SKIP_CLOSE_LOG_PIPE 控制是否关闭自己的 log 管道,然后调用原函数。##### 行 187-194:dlclose hook
这是 native bridge bootstrap 的关键:
- native bridge loader 在加载结束附近会
dlcloseloader handle。
- Zygisk 把
libnativebridge.so的dlclosePLT 替换成new_dlclose。
- 第一次触发时
self_handle还为空,调用post_native_bridge_load(handle)。
- 返回 0,不真正关闭。
post_native_bridge_load() 会用这个 handle 记录 Zygisk 自身,并通过栈回溯找到真实 LoadNativeBridge 和 runtime callbacks。##### 行 196-223:pthread\_attr\_destroy hook
Zygisk 不能直接在自己的函数栈上调用
dlclose(self_handle) ,否则代码被 unmap 后返回地址可能落在已卸载内存里。解决方式:
- 后期 hook
libart.so的pthread_attr_destroy。
- JVM 创建 daemon 线程时会调用它。
- Zygisk 在主线程触发时恢复 PLT hook。
- 如果仍允许卸载,取出
self_handle,删除g_hook。
- 使用
[[clang::musttail]] return dlclose(self_handle);,让dlclose直接返回给pthread_attr_destroy的调用者。
这样不会返回到 Zygisk 的已卸载代码。
##### 行 229-237:fd 上限与 ZygiskContext 构造
get_fd_max() 读取 RLIMIT_NOFILE ,默认准备 32768。ZygiskContext 构造函数保存 JNIEnv 、specialize 参数、初始化 pid/flags/fd 位图和 mutex,并设置全局 g_ctx = this 。##### 行 239-260:ZygiskContext 析构
析构时先把
g_ctx 清空,因为它指向栈对象。如果不是子进程,直接返回。父 zygote 不能卸载 Zygisk,否则后续 fork 无法继续被 hook。
子进程中:
- 关闭 Zygisk logd。
- 重新初始化 Android logging。
- 清空每个模块的 API 表,防止 post 后继续使用。
- 设置
should_unmap = true。
- 恢复 Zygote JNI hook。
- 安装
pthread_attr_destroy卸载 hook。
##### 行 264-276:unwind\_get\_region\_start
封装
_Unwind_GetRegionStart 。ARM32 下如果 PC 处于 Thumb 模式,要把最低位置 1,保证函数地址匹配真实调用地址。##### 行 278-344:find\_runtime\_callbacks
目标是从栈回溯上下文中找
NativeBridgeRuntimeCallbacks * 。步骤:
- 扫描
/proc/self/maps,找libart.so的可读写段。
- 这个 callbacks 对象位于 libart 的 writable 区域。
- 不同 ABI 参数传递方式不同:
- arm64:查 r19-r28。
- arm32:查 r4-r10。
- x86:从 ebp 相对位置读第二参数。
- x86\_64:查 rbx/r12-r15 等 callee-saved 寄存器。
- riscv:查 callee-saved x8/x9/x18-x27。
- 找到落在 libart writable 范围内的值,即认为是 callbacks 指针。
这是 Zygisk 能枚举已注册 JNI 方法的基础。
##### 行 346-381:post\_native\_bridge\_load
被
dlclose hook 触发。主要流程:
- 保存
self_handle。
- 使用
_Unwind_Backtrace回溯调用栈。
- 找到帧所属库是
libnativebridge.so的函数地址,认为它是android::LoadNativeBridge。
- 在同一帧上下文中调用
find_runtime_callbacks()。
- 若属性
ro.dalvik.vm.native.bridge的值长于libzygisk.so,说明后面拼接了真实 native bridge 名。
- 调用真实
LoadNativeBridge(原 native bridge, callbacks)补载它。
- 保存 callbacks 到
runtime_callbacks。
##### 行 385-429:注册第一批 PLT hook
register_hook() 包装 lsplt::RegisterHook ,成功后把 (dev, inode, symbol, old_func) 存入 plt_backup 。hook_plt() 扫描 maps 找:libandroid_runtime.so的 dev/inode。
libnativebridge.so的 dev/inode。
然后注册:
最后
lsplt::CommitHook() 执行实际 patch,并删除没有成功备份 old\_func 的记录。##### 行 431-446:注册自卸载 hook
hook_unloader() 扫描 maps 找 libart.so ,注册 pthread_attr_destroy hook。这一步发生在子进程 specialize 完成后,而不是最初 bootstrap 时。##### 行 448-460:恢复 PLT hook
遍历
plt_backup ,把每个 symbol 替回 old\_func。若任何一步失败,设置 should_unmap = false ,避免在 hook 状态不确定时卸载自己。##### 行 464-482:读取并注册 JNI native 方法
get_jni_methods() 通过 runtime callbacks:- 查询 class 的 native 方法数量。
- 分配数组。
- 填充方法表。
register_jni_methods() 逐个调用 RegisterNatives ,允许失败。失败时清 exception,并把该方法的 fnPtr 置空。##### 行 484-522:hook\_jni\_methods
这是替换 JNI native 方法的核心。
流程:
- 读取旧方法表。
- 对传入的候选 methods 逐个尝试
RegisterNatives。
- 再读取新方法表。
- 对每个成功替换的方法,根据 name/signature 找到旧方法指针。
- 把旧方法指针写回
method.fnPtr。
注意源码中特别强调:runtime callbacks 返回的 signature 不是标准格式,所以不能只靠字符串提前判断,而是要直接
RegisterNatives 试。##### 行 525-532:按类名 hook JNI 方法
对模块 API 提供的入口。若 callbacks/env/class 无效,则把所有
fnPtr 清空。否则 FindClass 后调用上面的核心替换函数。##### 行 534-609:hook\_zygote\_jni
这是“接管 Zygote specialize”的关键函数。
步骤:
- 找
JNI_GetCreatedJavaVMs。
- 先
dlsym(RTLD_DEFAULT, ...)。
- 找不到就扫描并
dlopenlibnativehelper.so。
- 获取
JavaVM。
- 从
JavaVM获取当前JNIEnv。
FindClass("com/android/internal/os/Zygote")。
- 通过 callbacks 获取 Zygote 已注册 native 方法表。
- 遍历方法名:
- 遇到
nativeForkAndSpecialize,用fork_app_methods替换。
- 遇到
nativeSpecializeAppProcess,用specialize_app_methods替换。
- 遇到
nativeForkSystemServer,用fork_server_methods替换。
- 如果任何已存在目标方法替换失败,恢复已替换的方法,并清空对应数组,避免半 hook 状态。
##### 行 611-616:恢复 Zygote JNI 方法
把
fork_app_methods 、 specialize_app_methods 、 fork_server_methods 中保存的旧函数指针重新注册回 Zygote 类。##### 行 620-627:导出给 entry/API 的入口
hook_entry() 创建 g_hook 并安装早期 PLT hook。hookJniNativeMethods() 是公开 API 的 C++ 实现,转给 g_hook->hook_jni_methods 。#### 3.2.5 module.hpp 逐行/逐段解释
##### 行 1-7:基础声明
包含 regex、list 和公开 API 头
api.hpp 。模块 API 兼容层需要正则保存 PLT hook 规则。##### 行 8-29:前置声明与 ABI 版本别名
Zygisk 支持多个 API/ABI 版本。这里用别名表达版本兼容关系:
AppSpecializeArgs_v2 = AppSpecializeArgs_v1。
AppSpecializeArgs_v4 = AppSpecializeArgs_v3。
module_abi_v2/v3/v4/v5都等同于 v1 布局。
api_abi_v3 = api_abi_v2,api_abi_v5 = api_abi_v4。
这表示某些版本只扩展语义或参数对象,不改变模块 ABI 函数表布局。
##### 行 31-69:App specialize 参数 v3/v5
AppSpecializeArgs_v3 保存 app specialize 的参数引用。必须用引用,因为模块可以修改这些参数,让原始 Zygote specialize 使用修改后的值。必选字段包括 uid/gid/gids/runtime\_flags/rlimits/mount\_external/se\_info/nice\_name/instruction\_set/app\_data\_dir。
可选字段用指针表示,因为不同 Android 版本和 OEM 签名不同:
AppSpecializeArgs_v5 在 v3 基础上增加 mount_sysprop_overrides 。##### 行 71-97:App specialize 参数 v1 兼容层
旧 API v1/v2 没有
rlimits ,构造函数从 v5 转换到 v1 视图。模块如果声明旧 API,Zygisk 会创建这个兼容对象再调用模块回调。##### 行 99-113:system\_server specialize 参数
ServerSpecializeArgs_v1 保存 system\_server 参数引用:模块可在 pre 阶段修改这些值。
##### 行 115-122:模块 ABI 函数表
module_abi_v1 是模块注册给 Zygisk 的 ABI:| 字段 | 含义 |
| --- | --- |
|
api_version | 模块使用的 Zygisk API 版本 |
| impl | 模块对象指针 |
| preAppSpecialize | app specialize 前回调 |
| postAppSpecialize | app specialize 后回调 |
| preServerSpecialize | system\_server specialize 前回调 |
| postServerSpecialize | system\_server specialize 后回调 |##### 行 124-131:flags 掩码
static_assert 保证内部 flag 与公开 API flag 值一致。UNMOUNT_MASK 表示“进程在 denylist 且 denylist enforced”。PRIVATE_MASK 是不应该暴露给模块的内部 flag,包括 enforced 状态和 Magisk app 标记。##### 行 133-168:Zygisk API ABI 表
api_abi_base 每个版本都有:impl指向ZygiskModule。
registerModule用于模块注册自身 ABI。
v1 提供:
v2 增加:
v4 改造 PLT hook API,从 regex 匹配改为 dev/inode 精确定位,并增加:
ApiTable union 让同一块内存按不同 ABI 版本解释。##### 行 170-209:ZygiskModule
ZygiskModule 表示一个已加载到目标进程的 Zygisk 模块。重要字段:
| 字段 | 含义 |
| --- | --- |
|
id | 模块在 magiskd 模块列表中的下标,用于 companion/getModuleDir |
| unload | 模块是否要求 post 后 dlclose |
| handle | dlopen handle |
| entry | zygisk_module_entry 函数 |
| api | Zygisk 注入给模块的 API 表 |
| mod | 模块回填的 ABI 表 |重要方法:
onLoad()调用模块入口。
pre/post App/ServerSpecialize()调模块回调。
valid()验证模块 ABI 是否完整。
connectCompanion()请求 root companion。
getModuleDir()从 magiskd 获取模块目录 fd。
setOption()处理模块选项。
tryUnload()根据选项 dlclose 模块。
##### 行 211-221:全局符号与内部 flags
g_ctx 在 hook.cpp 定义,供模块 API 访问当前 specialize 上下文。old_fork 是 PLT hook 保存的原始 fork。内部 flags:
| flag | 含义 |
| --- | --- |
|
POST_SPECIALIZE | 已进入 post 阶段 |
| APP_FORK_AND_SPECIALIZE | 当前是 fork app 流程 |
| APP_SPECIALIZE | 当前是 specialize app 流程 |
| SERVER_FORK_AND_SPECIALIZE | 当前是 system\_server fork 流程 |
| DO_REVERT_UNMOUNT | 需要执行 denylist unmount |
| SKIP_CLOSE_LOG_PIPE | 不关闭 Zygisk log pipe |##### 行 227-284:ZygiskContext
ZygiskContext 是一次 specialize 流程的上下文,生命周期通常是 JNI 包装函数的栈帧。字段:
| 字段 | 含义 |
| --- | --- |
|
env | 当前 JNIEnv |
| args | app 或 server specialize 参数 |
| process | 进程名 |
| modules | 当前子进程加载的 Zygisk 模块列表 |
| pid | Zygisk 预 fork 的结果,子进程为 0,父进程为 child pid |
| flags | 当前流程内部状态 |
| info_flags | magiskd 返回的进程状态 |
| allowed_fds | fork 后允许保留的 fd 位图 |
| exempted_fds | 模块通过 API 申请豁免关闭的 fd |
| register_info | 旧版 regex PLT hook 注册项 |
| ignore_info | 旧版 regex PLT hook 排除项 |方法按功能分组:
run_modules_pre/post():加载和执行模块。
fork/app/server/nativeXXX_pre/post():各类 JNI 包装流程。
get_module_info():向 magiskd 查询 flags 和模块 fd。
sanitize_fds():关闭不允许保留的 fd。
exempt_fd():实现模块 fd 豁免 API。
plt_hook_*():兼容旧版 regex PLT hook API。
#### 3.2.6 module.cpp 逐行/逐段解释
##### 行 14-19:zygisk\_request
连接 magiskd 的
RequestCode::ZYGISK ,写入具体 ZygiskRequest 子命令,然后返回 socket fd。后续
GetInfo 、 ConnectCompanion 、 GetModDir 都从这里发起。##### 行 21-27:ZygiskModule 构造
保存 id、handle、entry。清零 API 表,设置:
模块入口拿到 API 表后,会调用
registerModule 把模块 ABI 回填给 Zygisk。##### 行 29-69:RegisterModuleImpl
模块注册入口。
流程:
- 空指针检查。
- 读取模块声明的
api_version。
- 如果版本大于当前
ZYGISK_API_VERSION,拒绝。
- 保存模块 ABI 指针到
mod。
- 按版本填充 API 函数指针。
v1 API:
hookJniNativeMethods指向全局实现。
- 旧版
pltHookRegister/Exclude/Commit走g_ctx的 regex 兼容层。
connectCompanion、setOption转到当前ZygiskModule。
v2 API:
getModuleDir。
getFlags。
v4 API:
pltHookRegister(dev, inode, ...)直接走 lsplt。
exemptFd转给g_ctx->exempt_fd。
##### 行 71-85:valid
模块必须满足:
- 已注册 ABI。
- API 版本在 1-5。
impl与四个 pre/post 回调指针都不为空。
否则模块会从
modules 列表删除。##### 行 87-98:connectCompanion
向 magiskd 发
ConnectCompanion 请求。写入当前进程 ABI:再写模块 id。返回的 fd 是连接到 companion handler 的 socket。
##### 行 100-106:getModuleDir
向 magiskd 发
GetModDir ,写模块 id,接收一个目录 fd。这个 fd 指向模块根目录。##### 行 108-119:setOption
支持两个模块选项:
FORCE_DENYLIST_UNMOUNT:设置DO_REVERT_UNMOUNT。
DLCLOSE_MODULE_LIBRARY:post 后允许dlclose模块库。
##### 行 121-127:flags 与模块卸载
getFlags() 返回 info_flags 去掉内部私有位后的结果。tryUnload() 如果模块设置 unload,就 dlclose(handle) 。##### 行 131-160:调用不同 ABI 版本的模块回调
call_app 宏负责兼容旧 API:- API v1/v2:构造
AppSpecializeArgs_v1兼容对象。
- API v3/v4/v5:直接传
AppSpecializeArgs_v5指针。
server 参数没有这种版本分流,直接调用。
##### 行 164-181:旧版 regex PLT hook 注册
v1/v2 模块用 regex 匹配库路径。Zygisk 保存:
- regex。
- symbol。
- callback。
- backup 指针。
exclude 规则也保存 regex 与 symbol。symbol 为空表示排除整个库。
##### 行 183-221:旧版 regex PLT hook 提交
plt_hook_process_regex() 扫描 maps:- 只处理 offset 为 0、private、可读的映射。
- 对每条注册 regex 匹配路径。
- 再检查 exclude 规则。
- 未被排除就
lsplt::RegisterHook(map.dev, map.inode, symbol, callback, backup)。
plt_hook_commit() 加锁、处理 regex、释放 regex 对象、清空列表,然后 lsplt::CommitHook() 。##### 行 225-241:get\_module\_info
向 magiskd 发
GetInfo :- 写 uid。
- 写进程名。
- 写 ABI 位数。
- 读取
info_flags。
- 如果
zygisk_should_load_module(info_flags)为真,接收模块 fd 列表。
返回 fd 是因为 system\_server 流程后续还要把加载失败的模块 id 写回 magiskd。
##### 行 243-294:sanitize\_fds
先关闭 Zygisk logd fd。
父进程直接返回。子进程中:
- 如果支持
fds_to_ignore,把模块豁免的 fd 合并到 Zygote 的忽略列表。
- 把已有
fds_to_ignore中的 fd 标记为 allowed。
- 创建新 jintArray,把原 fd 与 exempted fd 合并。
- 扫描
/proc/self/fd。
- 不在
allowed_fds中且不是扫描目录 fd 的,全部关闭。
目的是避免从 Zygote 继承不该进入 app 的 fd,防止崩溃和泄漏。
##### 行 296-307:fd 豁免 API
exempt_fd(fd) :- 如果已经 post specialize,或者当前跳过关闭 log pipe,返回 true。
- 如果当前流程不能豁免 fd,返回 false。
- 否则加入
exempted_fds。
can_exempt_fd() 要求当前是 APP_FORK_AND_SPECIALIZE ,且 app 参数有 fds_to_ignore 。##### 行 309-346:fork\_pre/fork\_post
fork_pre() 是 Zygisk 避免第三方模块代码污染父 Zygote 的关键。流程:
- block
SIGCHLD。
- 调用原始
old_fork()。
- 父进程直接返回。
- 子进程扫描
/proc/self/fd,记录当前允许保留的 fd。
- 目录 fd 标记为不允许。
- Zygisk logd fd 单独标记为不允许。
之后原始 Zygote 方法再调用
fork() 时,会被 new_fork() 返回缓存 pid,不会真的再 fork。fork_post() unblock SIGCHLD 。##### 行 348-386:run\_modules\_pre
加载并执行模块 pre 阶段。
第一轮遍历 fd:
- fd 必须是普通文件。
- 使用
android_dlopen_ext从 fd 加载模块。
- 查找
zygisk_module_entry。
- 成功则构造
ZygiskModule(i, h, e)。
- system\_server 中 dlopen 失败会记录 warning,并把 fd 标成 -1,稍后上报失败模块。
第二轮:
- 调用
onLoad(env)。
- 模块通过 API 表注册自身 ABI。
valid()通过则保留,否则删除。
第三轮:
- app 流程调用
preAppSpecialize(args.app)。
- system\_server 流程调用
preServerSpecialize(args.server)。
##### 行 388-398:run\_modules\_post
设置
POST_SPECIALIZE ,再对每个模块:- app 调
postAppSpecialize。
- system\_server 调
postServerSpecialize。
- 根据模块选项
tryUnload()。
##### 行 400-421:app specialize 共享 pre/post
app_specialize_pre() :- 设置
APP_SPECIALIZE。
- 向 magiskd 查询进程 flags 和模块 fd。
- 如果进程在 enforced denylist,设置
DO_REVERT_UNMOUNT,不加载模块。
- 否则加载并运行模块 pre。
app_specialize_post() :- 调模块 post。
- 如果当前进程是 Magisk app,设置环境变量
ZYGISK_ENABLED=1。
- 释放
nice_name的 UTF chars。
##### 行 423-445:system\_server pre/post
server_specialize_pre() :- 用 uid 1000 和进程名
system_server查询 magiskd。
- 如果没有模块,写回 0。
- 如果有模块,加载并运行 pre。
- 收集加载失败的模块 id,写回 magiskd。magiskd 会在对应模块目录下创建
zygisk/unloaded标记。
server_specialize_post() 调 run_modules_post() 。##### 行 449-460:nativeSpecializeAppProcess 包装流程
这是 Android Q 以后部分流程使用的“不 fork,只 specialize 当前进程”的路径。
pre:
- 从
nice_name获取进程名。
- 设置
SKIP_CLOSE_LOG_PIPE。
- 调 app pre。
post:
- 调 app post。
##### 行 462-480:nativeForkSystemServer 包装流程
pre:
- 设置
SERVER_FORK_AND_SPECIALIZE。
- 进程名固定为
system_server。
- 调
fork_pre()。
- 子进程执行 server pre。
- 清理 fd。
post:
- 子进程执行 server post。
- 父子都执行
fork_post()解锁 SIGCHLD。
##### 行 482-500:nativeForkAndSpecialize 包装流程
pre:
- 从
nice_name获取 app 进程名。
- 设置
APP_FORK_AND_SPECIALIZE。
- 先真实 fork。
- 子进程执行 app pre。
- 清理 fd。
post:
- 子进程执行 app post。
- 父子都执行
fork_post()。
#### 3.2.7 api.hpp 逐行/逐段解释
##### 行 1-21:版权、用途和警告
这是面向模块开发者的公开 API 头。源码提示不要直接修改,并建议模块开发使用发布版 sample 仓库中的头文件。
##### 行 24-27:JNI 与 API 版本
包含
jni.h ,定义当前 API 版本 ZYGISK_API_VERSION 5 。##### 行 28-100:公开说明
注释解释了 Zygisk 的模型:
- Android app 进程从 Zygote fork。
- fork 后执行 specialize,把进程放入应用沙箱。
- Zygisk 允许模块在 app/system\_server specialize 前后运行代码。
- 模块代码运行在目标 app/system\_server 进程,不运行在长期的 Zygote daemon 中。
- 需要 root 能力时,使用 companion handler。
##### 行 102-141:zygisk::ModuleBase
模块作者继承此类,实现:
| 方法 | 调用时机 |
| --- | --- |
|
onLoad(Api*, JNIEnv*) | 模块 so 被加载后立即调用 |
| preAppSpecialize(AppSpecializeArgs*) | app specialize 前,仍接近 Zygote 权限 |
| postAppSpecialize(const AppSpecializeArgs*) | app specialize 后,处于 app 沙箱 |
| preServerSpecialize(ServerSpecializeArgs*) | system\_server specialize 前 |
| postServerSpecialize(const ServerSpecializeArgs*) | system\_server specialize 后 |##### 行 143-178:公开参数对象
AppSpecializeArgs 暴露 app specialize 参数。必选字段保证所有支持 Android 版本存在;可选字段要先判空。ServerSpecializeArgs 暴露 system\_server 参数。字段都是引用,模块可在 pre 阶段修改它们,影响后续原始 Zygote specialize 调用。
##### 行 185-200:模块选项
FORCE_DENYLIST_UNMOUNT :强制对当前进程执行 Magisk/模块文件 unmount。DLCLOSE_MODULE_LIBRARY :post 后卸载模块库。注释明确警告:如果模块 hook 了进程函数,不应启用该选项,否则回调地址会变成悬空指针。##### 行 202-209:公开状态 flags
PROCESS_GRANTED_ROOT 表示当前进程 uid 已被授权 root。PROCESS_ON_DENYLIST 表示当前进程在 denylist 中。内部 flag 如 denylist enforced、Magisk app 不直接暴露给模块。
##### 行 211-290:zygisk::Api
模块可用 API:
connectCompanion():连接 root companion 进程。
getModuleDir():获取模块根目录 fd。
setOption():设置模块选项。
getFlags():获取当前进程状态。
exemptFd():让指定 fd 不被 Zygote 自动关闭。
hookJniNativeMethods():替换某个 Java class 已注册 native 方法。
pltHookRegister():按 dev/inode/symbol 注册 PLT hook。
pltHookCommit():提交 PLT hook。
这些 API 在 post 后会失效,因为 Zygisk 会卸载并清空 API 表。
##### 行 292-310:注册宏
REGISTER_ZYGISK_MODULE(clazz) 导出 zygisk_module_entry ,Zygisk 加载模块后查找这个符号。REGISTER_ZYGISK_COMPANION(func) 导出 zygisk_companion_entry ,zygiskd 加载模块 companion 后查找这个符号。##### 行 317-360:内部 ABI 实现
internal::module_abi 是模块侧构造的 ABI 表,保存 API 版本、模块对象和四个回调 trampoline。internal::api_table 是 Zygisk 传入模块的函数表,布局对应 module.hpp 中的 api_abi_v4 。entry_impl<T>() :- 创建静态
Api。
- 绑定传入 table。
- 创建静态模块对象
T module。
- 创建静态 ABI。
- 调
table->registerModule注册。
- 成功后调用模块
onLoad(&api, env)。
##### 行 364-387:Api inline 方法
这些方法只是薄封装:检查函数指针是否存在,再通过
tbl 调用 Zygisk 内部实现。##### 行 391-398:默认导出声明
声明两个 C 符号:
模块可以只注册其中一个,也可以两个都注册。
#### 3.2.8 jni\_hooks.hpp 逐行/逐段解释
这是
gen_jni_hooks.py 生成的文件,不建议手写修改。它的职责是覆盖不同 Android 版本和厂商修改后的 Zygote native 方法签名。##### 行 1-7:生成声明
声明
JniHookDefinitions 和 get_defs() 。 get_defs() 在 hook.cpp 中返回 g_hook ,因此生成代码可以访问保存旧函数指针的数组。##### 行 9-230:fork\_app\_methods
数组包含 12 个
nativeForkAndSpecialize 签名:- AOSP Android L/O/P/R/U/B 等版本变化。
- Samsung M/N/O/P 变体。
- Nubia U 变体。
每个元素结构相同:
name固定为nativeForkAndSpecialize。
signature是对应 Android/OEM 的 JNI 签名。
fnPtr是一个静态 lambda。
lambda 统一流程:
- 根据参数构造
AppSpecializeArgs_v5 args(...)。
- 对当前签名存在的可选参数,把
args.xxx = &xxx。
- 构造
ZygiskContext ctx(env, &args)。
- 调
ctx.nativeForkAndSpecialize_pre()。
- 通过
get_defs()->fork_app_methods[index].fnPtr调回原始 JNI native 函数。
- 调
ctx.nativeForkAndSpecialize_post()。
- 返回
ctx.pid。
注意第 5 步中
fnPtr 在 hook 完成后已经被替换成原始函数指针,这是 HookContext::hook_jni_methods() 做的。##### 行 232-361:specialize\_app\_methods
数组包含 7 个
nativeSpecializeAppProcess 签名。这个路径返回
void ,因为它不 fork,只 specialize 当前进程。lambda 流程与 fork app 类似,但调用:
##### 行 363-394:fork\_server\_methods
数组包含 2 个
nativeForkSystemServer 签名:- AOSP 标准签名。
- Samsung Q 变体。
lambda 流程:
- 构造
ServerSpecializeArgs_v1。
- 构造
ZygiskContext。
ctx.nativeForkSystemServer_pre()。
- 调原始
nativeForkSystemServer。
ctx.nativeForkSystemServer_post()。
- 返回
ctx.pid。
#### 3.2.9 gen\_jni\_hooks.py 逐行/逐段解释
##### 行 3-19:JNI 类型建模
JType 保存 C++ 类型名和 JNI 签名字符。JArray 根据元素类型生成数组类型:- primitive 数组如
jintArray。
- 非 primitive 数组统一为
jobjectArray。
##### 行 21-38:参数建模
Argument 保存参数名、类型、是否要写入 AppSpecializeArgs 的可选字段。Anon 表示 Zygisk 不关心但签名中必须保留的 OEM 参数,自动命名为 _0 、 _1 等。##### 行 40-69:JNI 方法建模
Return 保存返回值表达式和返回类型。JNIMethod 能生成:- C++ 参数列表。
- C++ 函数指针类型。
- lambda 签名。
- JNI signature 字符串。
##### 行 71-139:三类 hook 生成器
JNIHook 是抽象基类。ForkApp 生成 nativeForkAndSpecialize 包装体:- 初始化 args。
- 设置可选参数指针。
- 构造
ZygiskContext。
- 调 pre。
- 调原始函数。
- 调 post。
- 返回
ctx.pid。
SpecializeApp 改目标名为 nativeSpecializeAppProcess ,返回 void。ForkServer 改目标名为 nativeForkSystemServer ,初始化 server args。##### 行 140-181:公共参数定义
定义 AOSP 与 OEM 签名中会出现的参数对象,例如 uid/gid/gids/runtime\_flags、
fds_to_ignore 、 is_child_zygote 、 mount_sysprop_overrides 、server capabilities 等。带
set_arg=True 的参数会被写入 AppSpecializeArgs_v5 可选字段。##### 行 183-612:具体签名列表
逐个定义不同 Android 版本/OEM 的方法签名:
fas_*:fork app。
spec_*:specialize app。
server_*:fork system\_server。
Samsung、Nubia、XR 等变体通过
Anon 或额外参数保持 ABI 匹配,但 Zygisk 只提取自己关心的参数。##### 行 615-631:生成数组代码
gen_jni_def(field, methods) 生成一个 std::array<JNINativeMethod, N> 。每个元素包含:
- 方法名。
- JNI 签名。
- 包装 lambda。
##### 行 634-669:写出 jni\_hooks.hpp
脚本打开
jni_hooks.hpp 并写入:- 文件头。
- 前置声明。
JniHookDefinitionsstruct。
- 三组方法数组。
#### 3.2.10 daemon.rs 逐行/逐段解释
##### 行 1-17:依赖
引入模块根目录常量、MagiskD、FFI 枚举、resetprop、Unix socket 扩展、fd 工具、fork 工具、日志工具等。
##### 行 18-25:常量与加载判断
NBPROP 与 C++ 一致,是 native bridge 属性。ZYGISKLDR 与 C++ 一致,是 libzygisk.so 。UNMOUNT_MASK 表示 denylist enforced 且当前进程在 denylist。zygisk_should_load_module(flags) 返回 true 的条件:- 不是 enforced denylist 命中。
- 不是 Magisk app。
##### 行 27-59:exec\_zygiskd
启动 companion daemon。
关键点:
- 清除 remote socket 的 close-on-exec,让 fd 在 exec 后仍然有效。
- 64 位 magiskd 中,如果目标是 32 位 companion,执行
magisk32;否则执行magisk。
- 构造执行路径:
get_magisk_tmp()/magisk或magisk32。
- 执行参数:
这个参数最终进入 C++
zygisk_main() 。##### 行 61-67:ZygiskState
字段:
| 字段 | 含义 |
| --- | --- |
|
lib_name | 当前写入 native bridge 属性的值,用于恢复原属性 |
| sockets | 32 位和 64 位 zygiskd 的连接 socket |
| start_count | zygote crash 计数,用于回滚 |##### 行 69-108:connect\_zygiskd
处理模块
connectCompanion() 请求。流程:
- 从 client 读 ABI 位数。
- 选择 32 位或 64 位 zygiskd socket。
- 如果已有 socket,用
poll检查是否仍有效。
- 有效则直接把 client fd 发给 zygiskd。
- 无效或不存在,则创建 socket pair。
- fork 子进程执行
exec_zygiskd。
- 把当前 ABI 的所有模块 fd 发送给新 zygiskd。
- 等 zygiskd ack。
- 把当前 client fd 发给 zygiskd。
- 保存 local socket 供复用。
##### 行 110-127:reset
Zygisk 状态重置:
- restore 为 true 时重置 crash 计数并恢复属性。
- restore 为 false 时清空 companion sockets,增加 crash 计数。
- crash 超过 3 次,认为 zygote 崩溃过多,回滚 native bridge 属性。
##### 行 129-146:set\_prop
如果
lib_name 不为空,说明已设置过,直接返回。否则读取原 native bridge 属性:
- 空或
"0":lib_name = "libzygisk.so"。
- 非空:
lib_name = "libzygisk.so" + 原值。
写回
ro.dalvik.vm.native.bridge 。如果 Huawei Maple 开启,则设置
ro.maple.enable=0 ,因为 Maple 特殊 Zygote 可能绕开 native bridge 创建 system\_server。##### 行 148-155:restore\_prop
如果
lib_name 比 libzygisk.so 长,后半段就是原 native bridge;否则恢复为 "0" 。写回属性并清空
lib_name 。##### 行 158-176:MagiskD::zygisk\_handler
magiskd 收到
RequestCode::ZYGISK 后进入这里。读取
ZygiskRequest ,分发:GetInfo:查询进程 flags 和模块 fd。
ConnectCompanion:启动/复用 zygiskd,转发 client fd。
GetModDir:发送模块目录 fd。
##### 行 178-189:get\_module\_fds
从模块列表取每个模块对应 ABI 的 zygisk so fd:
- 64 位取
m.z64。
- 32 位取
m.z32。
- 无效 fd 用
STDOUT_FILENO占位,因为通过 socket 发送 fd 时必须是有效 fd;magiskd 中 stdout 始终是/dev/null,zygiskd/目标进程会把它视为无效模块。
##### 行 191-240:get\_process\_info
处理目标进程查询。
读取:
- uid。
- process 名。
- ABI 位数。
计算 flags:
update_deny_flags写入 denylist/enforced 信息。
- 如果 uid 是当前用户的 Magisk manager uid,设置
ProcessIsMagiskApp。
- 如果 uid 已授权 root,设置
ProcessGrantedRoot。
然后:
- 先写 flags 给客户端。
- 如果允许加载模块,发送模块 fd。
- 如果不是 system\_server,结束。
- 如果是 system\_server,继续读取 C++ 写回的 failed module ids。
- 对失败模块创建
zygisk/unloaded标记文件。
##### 行 242-257:get\_mod\_dir
读取模块 id,查模块列表,打开模块根目录并把 fd 发送给客户端。
##### 行 260-265:zygisk\_enabled
FFI 辅助方法,返回 atomic
zygisk_enabled 状态。#### 3.2.11 mod.rs 逐行/逐段解释
##### 行 1-6:模块组织与导出
引入
daemon 子模块,并重新导出:##### 行 8-28:exec\_companion\_entry
这是给 C++
entry.cpp 调用的 C ABI 函数。参数:
client:目标进程与 companion handler 通信的 socket fd。
companion_handler:模块导出的zygisk_companion_entry。
执行流程:
- 把任务提交到
ThreadPool::exec_task。
- 调用前用
fd_get_attr(client)记录 fd 对应文件的 dev/inode。
- 执行模块 companion handler。
- handler 返回后再次检查 fd。
- 如果 fd 仍是同一个文件,关闭它。
这样做是为了避免模块 handler 自己关闭 fd 后,系统复用了相同 fd 数字,Zygisk 又误关了别的文件。
#### 3.2.12 关键时序图
##### 3.2.12.1 Zygote 启动注入
##### 3.2.12.2 app fork 流程
##### 3.2.12.3 system\_server fork 流程
##### 3.2.12.4 companion 流程
#### 3.2.13 几个容易误解的点
##### 3.2.13.1 模块不是加载在长期 Zygote daemon 中
公开 API 注释也强调:模块只在 fork 后的 app/system\_server 进程中加载。Zygisk 先 fork,再在子进程加载第三方模块,避免模块代码污染父 Zygote。
##### 3.2.13.2 isCompatibleWith() 返回 false 不代表注入失败
Zygisk 不是要成为真正 native bridge,它只是借这个入口完成 bootstrap。真正 native bridge 如存在,会在
post_native_bridge_load() 中补载。##### 3.2.13.3 为什么要 hook fork
Zygisk 需要在第三方模块加载前先真实 fork,确保模块只进入子进程。但原始 Zygote native 函数内部仍会调用 fork。
new_fork() 返回缓存 pid,使原始逻辑继续按正常路径执行但不再创建新进程。##### 3.2.13.4 为什么要枚举 JNI 方法再 RegisterNatives
不同 Android 版本和厂商修改了
Zygote native 方法签名。Zygisk 不能假设固定签名,所以生成大量候选签名,逐个 RegisterNatives 尝试,成功后再反查旧函数指针。##### 3.2.13.5 为什么要自卸载
Zygisk 的职责是在 specialize 前后提供模块回调。完成后继续常驻 app 进程会增加检测面、内存占用和 hook 风险。因此子进程完成后恢复 hook 并
dlclose 自己。#### 3.2.14 关键函数索引
| 函数/符号 | 文件 | 作用 |
| --- | --- | --- |
|
NativeBridgeItf | entry.cpp | native bridge 注入入口 |
| zygisk_main | entry.cpp | companion applet 入口 |
| zygiskd | entry.cpp | root companion daemon 主循环 |
| hook_entry | hook.cpp | 初始化 HookContext 并安装 bootstrap PLT hook |
| HookContext::hook_plt | hook.cpp | hook dlclose/fork/unshare/strdup/... |
| HookContext::post_native_bridge_load | hook.cpp | 找 callbacks 并补载真实 native bridge |
| HookContext::hook_zygote_jni | hook.cpp | 替换 Zygote native specialize 方法 |
| ZygiskContext::fork_pre | module.cpp | 提前 fork 并记录 fd |
| ZygiskContext::run_modules_pre | module.cpp | dlopen 模块并调用 onLoad/pre |
| ZygiskContext::run_modules_post | module.cpp | 调用 post 并按需卸载模块 |
| ZygiskModule::RegisterModuleImpl | module.cpp | 模块 ABI 注册和 API 表填充 |
| ZygiskState::set_prop | daemon.rs | 写入 native bridge 属性 |
| MagiskD::get_process_info | daemon.rs | 返回进程 flags 和模块 fd |
| ZygiskState::connect_zygiskd | daemon.rs | 启动/复用 companion daemon |
| exec_companion_entry | mod.rs | 在线程池中执行 companion handler |#### 3.2.15 用一句话概括
Zygisk 的核心是:magiskd 通过 native bridge 属性让 Zygote 加载
libzygisk.so ,Zygisk 在 native bridge 初始化窗口安装 PLT hook,再在 ZygoteInit 前替换 Zygote 的 JNI specialize 方法;真正创建 app/system\_server 时,它先 fork 到子进程,向 magiskd 获取模块 fd,加载模块执行 pre/post 回调,完成后恢复 hook 并从目标进程自卸载。源码流程梳理清楚之后,就可以开始真正移植了。
四、开始移植:把最新版 Zygisk 迁到 r0zygisk
指令:
AI执行过程:
- 环境检测
4.1 Zygisk 迁移改动
#### 1\. 同步公开 API 到 v5
文件:
改动:
- 使用
Magisk-master/native/src/core/zygisk/api.hpp覆盖 r0zygisk 旧版公开 API 头文件。
ZYGISK_API_VERSION从旧版本升级为:
意义:
- 新编译的 Zygisk 模块会按 API v5 注册。
- r0zygisk loader 需要能识别并加载 API v5 模块。
#### 2\. 内部参数结构升级为 AppSpecializeArgs\_v5
文件:
改动:
- 增加
AppSpecializeArgs_v5。
AppSpecializeArgs_v5继承AppSpecializeArgs_v3,并单独持有:
- 增加 API v5 兼容别名:
call_app增加case 5,让 API v5 模块按新版参数传入。
意义:
- 兼容 Magisk 最新 Zygisk API v5。
- 保留对旧 API v1-v4 模块的兼容。
#### 3\. Hook 上下文改用 AppSpecializeArgs\_v5
文件:
改动:
ZygiskContext中 app 参数指针从:
改为:
ZygiskModule::valid()接受 API v5:
意义:
- r0zygisk 的模块加载逻辑可以接受 v5 模块。
- Zygote JNI wrapper 传入的是新版 specialize 参数对象。
#### 4\. 更新 JNI hook 生成器
文件:
改动:
- wrapper 初始化参数从
AppSpecializeArgs_v3改为AppSpecializeArgs_v5。
- 增加 Magisk 最新 Zygisk 中已有的新参数:
- 增加新的 Zygote JNI 签名候选:
- 重新生成:
新增生成结果中包含:
意义:
nativeForkAndSpecialize_b是 Android 16 / Baklava 相关适配的重要线索。
- 新增的 OEM / XR 签名提高了对新系统和厂商 ROM 的兼容范围。
4.2 版本与输出文件名改动
#### 1\. 固定版本名和版本号
文件:
改动前:
改动后:
意义:
- 项目从自己的第一版开始。
versionCode固定为1。
#### 2\. 去掉 zip 文件名中的 commit hash
文件:
改动前:
改动后:
意义:
- 不再因为目录不是 git 仓库而输出
unknown。
- 最终文件名符合要求。
#### 3\. 去掉 module.prop 版本显示中的 commit hash
文件:
改动前:
改动后:
生成结果:
4.3 构建验证
执行过的验证命令:
结果:
执行完整打包:
结果:
最终新产物:
项目已经开源,发布页在 GitHub Releases:
4.4 本次修改过的主要文件
4.5 当前阶段状态
当前源码已经完成:
- Zygisk API v5 迁移。
- Android 16 / Baklava 相关 JNI 签名候选加入。
- r0zygisk 自有 loader / zygiskd / ptrace 架构保留。
- 输出文件名改为
r0zygisk-v1.0.0-1-release.zip。
- 完整打包通过。
在 Android 14 的 SukiSU 环境刷入后,我又额外测试了 LSPosed 模块,确认加载正常。
到这里,移植主体已经基本完成,下一步开始补 WebUI,这部分同样结合 AI 一起推进。
五、WebUI 重构与联调记录
指令:
AI 给出的结论如下:
实机刷入后,确认 WebUI 虽然已经生成,但界面和信息组织都比较粗糙,这也是后面重做界面的直接原因。这里当时没有保留截图。
指令:
5.1 第一阶段:调研官方 ZygiskNext WebUI 功能
#### 目标
弄清楚官方 ZygiskNext WebUI 提供哪些功能,再决定哪些功能能在当前
r0zygisk fork 中真实接入。#### 排查
本地先检查项目结构,确认 WebUI 在:
再查看控制脚本:
实际内容是通过:
调用 native 里的
ctl start|stop|exit 。随后查看
loader/src/ptracer/main.cpp ,确认当前后端只支持:换句话说,当前 fork 并没有暴露官方 ZygiskNext 中那些设置项对应的配置接口。
#### 官方 WebUI 功能参考
通过 GitHub 官方仓库的
i18n 分支文案确认,ZygiskNext WebUI 功能大致包括:- 状态页
- 基本信息
- 设置页
- Root 实现显示
- Zygote monitor 状态
- Zygisk module 状态
- ZN Module 状态
- KernelSU / Magisk / APatch 排除列表策略说明
- 日志写入 dmesg
- 非 root 应用作为排除列表
- 强制排除列表
- 匿名内存加载
- Zygisk Next linker
- Zygote 注入状态说明
- 模块问题提示
但是当前
r0zygisk 后端没有这些设置项的持久化配置或控制命令。因此不能做“看起来能点,实际没有接线”的假开关。#### 第一版方案
把页面做成:
- 状态仪表盘
- 追踪器控制按钮
- 已发现 Zygisk 模块列表
- 每个按钮的说明
- 原始诊断输出
- ZygiskNext 参考功能地图,并标注当前 fork 是否接入
5.2 第二阶段:重做 WebUI
#### 修改文件
重写:
module/src/webroot/index.html
module/src/webroot/styles.css
module/src/webroot/app.js
#### 页面结构改造
新增的页面结构包括:
- 顶部标题:
Zygisk 控制台
- 总状态评分:
0/3到3/3
- 三个状态卡片:
- Daemon
- Zygote
- Root
- 三个操作按钮:
- 启动追踪
- 停止追踪
- 退出监控
- 已发现的 Zygisk 模块列表
- 操作说明
- 原始诊断信息
#### 前端状态读取逻辑
初版 WebUI 通过管理器 WebUI bridge 执行 shell:
然后从
module.prop 的 description= 中解析:#### 验证
执行:
构建通过,产物为:
5.3 第三阶段:刷入后第一次反馈
#### 用户现象
刷入后界面看起来不错,但出现:
- 当前状态显示追踪为运行
- Daemon 为运行
- 进程表未发现
zygisk
- Zygote 未确认
- Root 未识别
- 无法读取
module.prop
- 三个按钮点击没有任何反应
#### 初步判断
当时判断存在两个可能问题:
- 模块路径被硬编码为:
但实际刷入后模块目录可能在其他路径或更新目录中。
- WebUI 的
exec调用方式可能不兼容当前管理器。
#### 模块路径修复
加入自动定位模块目录逻辑:
前端不再固定依赖
/data/adb/modules/r0zygisk ,而是在刷新和点击按钮时动态查找真实模块目录。#### 第一版 exec 兼容修复
一开始用过:
并尝试根据返回值判断 Promise、同步结果或 callback。
重新打包后,问题还没有彻底解决。
5.4 第四阶段:删除参考块,并继续处理 exec 超时
#### 用户现象
实机反馈如下:
- 不再显示
ZygiskNext 参考这个块
- 诊断信息显示:
- 页面上显示:
- 无法连接执行接口
- Daemon、Zygote、Root 都等待刷新
- 按钮仍然没有结果
#### 删除参考块
从
index.html 删除:同时从
app.js 删除 officialFeatures 和 renderFeatures() ,从 styles.css 删除对应的 .feature-grid 、.feature-item 样式。#### exec 超时根因
继续查 KernelSU WebUI API 后确认:
KernelSU 的底层 bridge 不是:
而是:
关键差异是第三个参数传的是“全局回调函数名”,不是函数对象。
#### 修复方案
在
module/src/webroot/app.js 中实现:同时保留降级:
最终执行顺序:
bridge.exec(command, "{}", callbackName)
bridge.exec(command, callbackName)
bridge.exec(command)
#### 验证
执行:
并检查 zip:
确认 zip 中包含:
5.5 第五阶段:SukiSU 模块列表显示一长串 tracing 状态
#### 用户现象
实机反馈中,SukiSU 模块列表里出现了:
并询问这是什么意思。
#### 解释
这是
r0zygisk 原本把运行状态写进 module.prop 的 description= 中导致的。在
loader/src/ptracer/monitor.cpp 原始逻辑中:它会生成类似:
SukiSU 的模块列表直接显示
description ,所以用户会看到这串运行状态。#### 含义说明
这些字段含义:
monitor: tracing:追踪器正在运行
zygote64: injected:64 位 zygote 已注入
daemon64: running:64 位 daemon 正在运行
Root: KernelSU:当前 root 实现识别为 KernelSU 系
module(3):发现了 3 个 Zygisk 模块
这些状态本身说明模块运行正常,但并不适合直接显示在模块列表里。
5.6 第六阶段:迁移运行状态到 status.json
#### 调整目标
这里的目标已经明确:
即:
- SukiSU 模块列表保持干净描述
- 运行状态不要再塞进
module.prop description
- 状态写入独立文件,如:
#### native 修改
修改文件:
主要变化:
- 删除
pre_section、post_section的描述拼接逻辑。
- 不再生成临时
module.prop并 bind mount 回模块目录。
- 新增:
prepare_environment()改为:
updateStatus()写 JSON:
- 状态文本里的表情也移除,改成纯文本:
#### WebUI 修改
修改:
刷新命令新增读取:
诊断信息增加:
前端逻辑改为:
- 优先解析
status.json
- 如果旧版本还在运行,没有
status.json,则回退解析module.prop description
#### 验证
执行:
并检查 native so:
确认包含:
并确认不再依赖
description= 状态拼接。#### 用户刷入后反馈
再次刷入后,SukiSU 模块列表只显示:
解释为:
也就是
r0zygisk 不依赖 Magisk 自带 Zygisk,而是自己实现一套 Zygisk 注入和守护进程机制,让 KernelSU / SukiSU 环境也能加载 Zygisk 模块。这说明把运行状态迁移到
status.json 的方案已经生效。5.7 第七阶段:WebUI Zygote 红点,状态只有 2/3
#### 用户现象
后续实机反馈如下:
- SukiSU 模块描述已经干净
- 但 WebUI 中
ZYGOTE红点
- 显示“未确认”
- 上方状态只有
2/3
- 正常应该是
3/3
#### 根因
native 已经写出结构化 JSON:
但前端判断还主要依赖
raw 文本:如果
raw 文本解析失败,或者和旧格式有细微差异,前端就会漏判 Zygote。#### 修复
在
module/src/webroot/app.js 中把状态读取改成:判断逻辑改成优先用 JSON 字段:
显示详情也改为优先显示 JSON:
#### 验证
执行:
并确认 zip 中包含:
输出包含:
最终 zip 时间:
六、最终状态
截至本记录生成时,主要文件状态如下。
WebUI
文件:
当前能力:
- 显示总评分
0/3到3/3
- 显示 monitor / daemon / zygote / root
- 从
status.json读取结构化状态
- 回退兼容旧
description状态
- 自动定位真实模块目录
- 显示已安装 Zygisk 模块列表
- 支持追踪器操作:
- 启动追踪
- 停止追踪
- 退出监控
- 诊断区显示:
module.dir
module.prop
status.json
processes
modules
Native Monitor
文件:
当前行为:
- 不再把状态写入
module.prop description
- 不再污染 SukiSU 模块列表
- 状态写入:
模块描述
文件:
当前描述:
SukiSU 中显示这句话是正常现象,含义是:
补充过程中的一些截图和当前最终版本截图:
Android 15 测试:

Android 16 测试:




Android 14 SukiSU:
最后把模块加载修改为列表显示,不挤成一坨,当前最终版的 WebUI 样式:

测试后功能没有问题,但使用 Hunter 最新版 6.58 发现仍会被检测:
!
以上为使用 ptrace 的方式注入。
这里尝试了多种修改方式,最终仍被检测到;期间还出现过一次卡开机,随后重刷并重建环境后继续分析。
七、第二版大修:通过 Hunter 的 Zygisk 检测
第一版基于
ptrace 的注入方案在功能上已经稳定,但用 Hunter 6.58 实测时仍会被检测,日志里出现:这说明问题不在“是否能注入”,而在“注入行为特征是否被对方检测到”。所以第二版的核心工作不是继续堆规避技巧,而是先把 Hunter 的检测链路完整拆出来,再按检测点反推改造方向。
7.1 先定位检测触发点:检测发生在 Java 层还是 Native 层
使用
apktool 解包 com.zhenxi.hunter.apk ,全局搜索 Ptrace Check 、 find zygisk detected 、 zygote root pid 等关键字。结果很快明确:
- smali 层能看到
NativeEngine.checkZygisk()的声明与调用。
- 关键日志模板不在业务 smali 里,而在
lib/arm64-v8a/libhunter.so。
这一步很关键。因为如果误判成“Java 层静态字符串检测”,后续优化方向会完全跑偏。
7.2 从上层调用链定位触发入口
接下来把谁在触发
checkZygisk() 这件事补齐。沿着 smali 回溯后,能定位到两条主要路径:ZhenxiServerIpc路径:最终进入P1(6),再走到NativeEngine.checkZygisk()。
ZhenxiServerTwinIpc路径:最终进入P1(8),同样会调用NativeEngine.checkZygisk()。
同时在
AndroidManifest.xml 中可以看到对应服务进程(包括 hunter_server_iso 与 twin 服务),和运行时日志中的进程标签可以对上。这意味着检测不是偶发调用,而是有明确的服务化触发链路。7.3 检测核心:libhunter.so 的真实实现
因为目标 so 是
ELF aarch64 且有 strip,不能依赖常规符号名直接定位,所以采用了“字符串 + JNI 注册表 + 反汇编交叉”的方式:- 在 so 里抽到关键字符串:
Ptrace Check Zygisk
find zygisk detected ,zygote root pid
/system/lib64/libzygisk_loader.so
/system/lib64/libzygisk.so
/sbin/.magisk/modules/zygisk_lsposed
- 定位
JNI_OnLoad的动态注册逻辑,确认checkZygisk来自RegisterNatives,不是静态导出。
- 还原
JNINativeMethod表后,锁定checkZygisk对应 native 函数入口:libhunter.so@0x2a93f8。
到这一步,链路已经闭合:Java 触发点、JNI 绑定点、native 实现点三者一致。
7.4 检测逻辑拆解
checkZygisk 不是单一判断,而是“多分支命中后统一组装结果对象”的结构。结合反汇编可还原为:- 先构造标题
Ptrace Check Zygisk。
- 进行 pid/zygote 关系相关检查;命中后拼接
find zygisk detected ,zygote root pid ...。
- 进行路径/特征串检查,涉及
libzygisk_loader.so、libzygisk.so、zygisk_lsposed等标识。
- 把命中结果封装成返回对象(
ListItemBean)上报到上层。
也就是说,Hunter 在这一块并非只看某个固定文件是否存在,而是把“进程关系 + 特征路径 + 运行态信息”做了组合判定。
八、结合 AI:使用新的注入方式完成第二版代码
这一阶段的目标很明确:不再继续打补丁式修修补补,而是参考
ZygiskNext 1.3.4 的成熟路线,重做第二版运行链路。整体工作分成两部分:先改注入方式,再完成工程与模块命名收敛。8.1 第一部分:修改注入方式(从 ptrace 到 native bridge)
第一版是典型的
ptrace -> 远程 dlopen 路线:- 通过
r0z-trace32/64常驻进程监控zygote/app_process
- 命中后注入
libr0z.so
- 运行过程中会留下较明显的 ptrace 与进程关系特征
在第七部分中确认 Hunter 检测是“多维组合判定”后,这条路线风险较高,所以第二版切换为
native bridge loader -> payload -> zygisk core -> daemon 。关键改动如下:
- 新增并接入:
libzn_loader.so、libpayload.so、libzygisk.so。
这一步不是简单多加几个库,而是把“入口加载”“功能实现”“Zygisk 主体”拆成三层。好处是桥接库只负责承接系统加载,payload 只负责承载运行逻辑,真正的 Zygisk 核心则保留自己的职责边界,后续替换和排查都更清楚。
entry.cpp增加zygisk_entry,构建产物从libr0z.so + libr0z_ptrace.so切换为三段式库输出。
原来的 ptrace 路线依赖主动盯进程,特征非常明显;新的 native bridge 路线则是让系统按属性把桥接库拉起来,再由入口去串起后续流程。
zygisk_entry 就是为了把这个入口补齐。- 模块脚本不再依赖
r0z-trace,改为system.prop设置ro.dalvik.vm.native.bridge=libzn_loader.so,并在post-fs-data.sh/service.sh启动 daemon。
这里的变化核心是把“前台监控”改成“系统加载”。
r0z-trace 这类常驻监控进程最容易留下 ptrace 痕迹,所以第二版直接放弃它,把启动触发点收敛到属性注入和系统阶段脚本上。- 库安装路径统一为
$MODPATH/system/lib与$MODPATH/system/lib64。
这样做是为了按 32/64 位 ABI 分目录落盘,确保 native bridge 和后续 payload 在系统预期的库路径里可直接被加载,避免额外的路径适配和查找失败。
- daemon 状态链路改造为本地
status.json,移除对旧init_monitorsocket 的硬依赖。
旧的 socket 更像临时联调通道,不适合作为 WebUI 的稳定数据源。改成
status.json 后,状态就变成了可直接读取的结构化文件,WebUI、模块列表和调试输出可以各自独立展示,不再互相绑死。这次改造是架构级切换,不是启动命令替换,实际同时调整了注入入口、动态库产物、安装路径、启动时序与状态通道。
8.2 第二部分:统一名称为 r0z
完成注入路线切换后,第二版继续做命名统一,避免构建、日志、脚本、WebUI 之间出现旧名残留。
指令:
执行过程:
执行结果:项目完成改名后刷入测试,仍被检测;原因是检测侧仍能发现
libzygisk.so 。接下来继续让 AI 进行修改:
指令:
执行结果:
!
指令:
执行结果:
!


刷入手机结果:
!
WebUI:
!
安装和打开lsposed模块也正常:
!
最新版 Hunter 检测结果全绿:
!
ruru结果:
!
至此,第二版大修结束,已通过新版 Hunter 的 Zygisk 检测。
九、总结
这次做下来,最大的收获其实不只是把模块跑起来了,而是把一条完整的开发链路真正走通了:先确认最新版
Magisk 的 Zygisk 已经适配 Android 16 ,再借助 code-panorama 和 AI 快速梳理源码结构,后面一步步补齐 API、JNI 签名、打包链路、 WebUI 、守护进程状态和注入方式,最后再去处理真实设备上的兼容问题和检测问题。一路做下来,才把现在这个带 WebUI 、功能上等效于 Zygisk 的 r0z 模块完整落地。完整源码和发布包已经开源在 GitHub Releases:
这篇文章记录的是一个完整模块从源码阅读、迁移实现、联调修复,到最后对抗检测的过程。源码规模够大、调用链够长的时候,AI 能帮人省掉很多纯体力活,但真正决定结果的,还是自己去拆执行流程、判断注入链路、定位检测点、解决刷机和兼容性问题。至少对这次来说,目标已经完成了:模块做出来了,
WebUI 跑起来了, Android 16 适配了,第二版也通过了新版 Hunter 的检测。不过目前已经有朋友反馈,还是存在被检测到的情况。后续还会继续修改和完善,项目也会持续更新,新的进展我会继续补到后面的文章里。谢谢大家关注。
- Author:24th
- URL:https://24th.top/article/39ce5b08-46db-8013-8e6e-c96a0553b728
- Copyright:All articles in this blog, except for special statements, adopt BY-NC-SA agreement. Please indicate the source!






