KernelSU 关键流程分析
本章把内核层与用户空间串起来,按时序分析各关键操作的端到端流程。实现细节请对照 内核层实现 与 用户空间实现。
本章对应当前重构后的实现。旧版流程(Kprobe hook reboot/cap_task_fix_setuid、workqueue umount、prctl 通信)已不再适用。
1. 系统初始化流程
kernelsu_init(core/init.c)先做共同初始化,再按 ksu_late_loaded 分两条路径。判定依据:#ifdef MODULE 下 ksu_late_loaded = (current->pid != 1)——pid==1 表示由 init/ksuinit 在 boot 早期加载(非 late),否则是运行时加载(late)。
kernelsu_init()
├─ 共同初始化
│ ├─ ksu_init_symbol_resolver() // kallsyms(含 CFI 变体)
│ ├─ ksu_syscall_hook_init() // 找 ni_syscall 空槽,安装分发器
│ ├─ ksu_feature_init() // 清空 feature 表
│ ├─ ksu_sulog_init / adb_root_init / lsm_hook_init / selinux_hide_init
│ └─ ksu_supercalls_init() // dump IOCTL 表 + 注册 reboot kprobe(仅装 fd)
│
├─ [GKI/早加载] !ksu_late_loaded
│ ├─ ksu_syscall_hook_manager_init() // 注册 sys_enter tracepoint + 4 个 handler
│ ├─ ksu_allowlist_init() // 建默认 profile(domain=u:r:ksu:s0, umount=true)
│ ├─ ksu_throne_tracker_init()
│ ├─ ksu_ksud_init() // 安装 init.rc 注入 hook(read/fstat) + input_event kprobe
│ └─ ksu_file_wrapper_init()
│ → 后续 SELinux 规则/allowlist 加载/manager 扫描由 boot 事件驱动
│
└─ [LKM 后加载] ksu_late_loaded
├─ apply_kernelsu_rules() / cache_sid() / setup_ksu_cred() // 立即建 ksu domain
├─ escape_to_root_for_init() // 把当前 ksud 提到 ksu domain(enforce 前)
├─ ksu_allowlist_init() / ksu_load_allow_list()
├─ ksu_syscall_hook_manager_init()
├─ ksu_throne_tracker_init / observer_init / file_wrapper_init
├─ ksu_boot_completed = true; track_throne(false);
└─ if (permissive) setenforce(true) // 强制重新 enforce
平台前置检查:x86 需 X86_FEATURE_INDIRECT_SAFE(否则 -ENOSYS 中止);arm64 LKM 有 stack protector workaround;非 debug LKM 末尾 kobject_del 自隐藏。
2. init.rc 注入与 ksud 启动闭环(GKI 路径)
正常加载下,内核需要让系统在启动各阶段拉起 ksud。机制是 hook init 读取 init.rc 的过程(runtime/ksud_integration.c):
init 进程 read("/system/etc/init/hw/init.rc")
│ ksu_ksud_init() 已用 ksu_syscall_table_hook 直接 patch 了 read/fstat 槽
▼
ksu_handle_sys_read → ksu_install_rc_hook(file)
│ 校验 comm=="init" 且路径=="/system/etc/init/hw/init.rc"
│ 用 fops_proxy 替换该 file 的 f_op
▼
read 到 EOF 时,proxy 把 KERNEL_SU_RC + /metadata[/watchdog]/ksu/modules.rc 追加到缓冲区
│ (fstat 也被 hook,把 st_size 撑大以容纳追加内容)
▼
注入的 rc 含触发器(均 exec u:r:ksu:s0 root -- /data/adb/ksud <stage>):
on post-fs-data → ksud post-fs-data
on nonencrypted / vold.decrypt=... → ksud services
on property:sys.boot_completed=1 → ksud boot-completed
│ 注入完成后 stop_init_rc_hook() 撤销 read/fstat hook(只处理首次读)
完整闭环:
内核注入 init.rc
→ init 在各阶段 exec ksud <stage>
→ ksud 经 IOCTL REPORT_EVENT 把 post-fs-data/boot-completed/module-mounted 回报内核
→ 内核执行 on_post_fs_data() / on_boot_completed() / on_module_mounted() 回调
on_post_fs_data: ksu_load_allow_list() + ksu_observer_init() + selinux_hide post-fs-data
on_module_mounted: ksu_module_mounted = true(此后 kernel_umount 才真正卸载)
on_boot_completed: ksu_boot_completed = true + track_throne(true)
此外,内核还在 execve hook 里探测关键进程(ksu_handle_execveat_ksud):init second_stage(Android 10+)触发 apply_kernelsu_rules+cache_sid+setup_ksu_cred+selinux_hide second_stage;首次 app_process -Xzygote 触发 on_post_fs_data() 并关闭 execve 探测 hook。
3. Manager 识别流程
track_throne(prune_only=false) // boot_completed 或 fsnotify 触发
├─ 解析 /data/system/packages.list → uid_data 链表(package, uid)
├─ 当前 manager appid 是否还在列表?
│ ├─ 不在且 appid 有效 → ksu_invalidate_manager_uid()(manager 被卸载)
│ └─ appid 无效 → search_manager("/data/app", depth=2, uid_data)
│ └─ BFS 遍历,遇 base.apk:
│ ├─ 命中 apk_path_hash 缓存(已知非 manager)→ 跳过
│ └─ is_manager_apk(path):纯 V2 签名 + 证书 SHA-256 == EXPECTED_HASH
│ ├─ 是 → crown_manager():按包名匹配 uid → ksu_set_manager_appid(uid%100000),停止
│ └─ 否 → 加入 apk 路径缓存
└─ ksu_prune_allowlist(is_uid_exist, uid_data) // 删除已卸载应用的授权条目(保留 uid=9999)
触发时机:boot_completed(track_throne(true) 仅 prune);以及 pkg_observer 用 fsnotify 监听 /data/system,当 packages.list 被重写(装/卸应用)时 track_throne(false) 重扫。Manager 身份以 appid(uid%100000) 记录,天然适配多用户/重装/更新。
4. driver fd 安装流程
KernelSU 的"设备"是匿名 inode [ksu_driver],进程对它 ioctl 即调用内核功能。fd 有两条安装路径:
路径 A — Manager(内核主动植入):
zygote fork Manager → setresuid(manager_uid)
│ sys_enter tracepoint(Manager 进程被标记)→ 分发器 → ksu_hook_setresuid
│ 调真实 setresuid 成功后 → ksu_handle_setresuid(old,new)
▼
setuid_hook 检测 new_uid 是 manager:
├─ ksu_seccomp_allow_cache(filter, __NR_reboot) // 放行 reboot
├─ ksu_set_task_tracepoint_flag(current)
└─ ksu_install_fd() // 直接装 [ksu_driver] fd
▼
Manager 启动后 ksu.cc::scan_driver_fd() 遍历 /proc/self/fd 捡回该 fd
路径 B — ksud / su(reboot magic 协议):
进程 syscall(SYS_reboot, 0xDEADBEEF, 0xCAFEBABE, 0, &out_fd)
│ reboot kprobe(reboot_handler_pre)识别 magic
▼
task_work_add(current, ..., TWA_RESUME)
│ 返回用户态前执行 ksu_install_fd() + copy_to_user(out_fd)
▼
进程拿到 fd(ksucalls.rs::init_driver_fd 即先扫 /proc/self/fd,扫不到才走此路)
之后所有 supercall 都是对该 fd 的 ioctl(命令表见 用户空间实现 §2.2)。
5. Root 授予流程
有两种获 root 方式,最终都落到内核 escape_with_root_profile(改 cred)。
5.1 传统 su 方式(sucompat)
授权应用 execve("/system/bin/su", argv, envp)
│ 该进程在 setresuid 时已被标记 → sys_enter tracepoint → 分发器 → ksu_hook_execve
▼
su_compat 开启 + ksu_is_allow_uid_for_current → ksu_handle_execve_sucompat
├─ override_creds(ksu_cred) + filp_open(/data/adb/ksud, O_PATH) → 得到 ksud 的 fd
├─ 把寄存器改写成 execveat(fd, "", argv, envp, AT_EMPTY_PATH) // 实际执行 ksud
├─ escape_with_root_profile() // 提权
└─ emit sulog(SUCOMPAT 事件)
▼
内核执行 ksud(argv[0]=="su",已是 root)→ cli::run 进 su::root_shell
└─ 解析 Magisk 兼容参数、tty fd 包装(get_wrapped_fd)、set_identity → exec 目标 shell
(faccessat/newfstatat 对 /system/bin/su 的探测被改写指向 ksud/sh,让"su 存在性检测"通过;详见 libsu 与 su 兼容层。)
5.2 IOCTL 方式(ksud debug su / Manager)
持有 [ksu_driver] fd 的进程 → ioctl(fd, KSU_IOCTL_GRANT_ROOT)
│ 权限检查 allowed_for_su(Manager 或已授权 uid)
▼
do_grant_root → escape_with_root_profile() + emit sulog(IOCTL_GRANT_ROOT)
5.3 提权核心 escape_with_root_profile
无论哪种方式,提权都执行(policy/app_profile.c,详见 内核层实现 §3.3):
prepare_creds()
├─ 若已 root 或线程设了 TIF_KSU_DISABLE_ESCAPE_WITH_ROOT → 放弃
├─ profile = ksu_get_root_profile(uid)
├─ 设 uid/gid 四元组、securebits=0
├─ 刷新 cred->user / ucounts(避免 RLIMIT_NPROC 残留)
├─ capabilities.effective → cap_effective/permitted/bset
├─ setup_groups() + setup_selinux("u:r:ksu:s0")
├─ commit_creds()
├─ disable_seccomp()
├─ 若 flags & NO_NEW_PRIVS → set_thread_flag(禁止再提权)
├─ 给本进程所有线程打 tracepoint 标记
└─ setup_mount_ns(profile->namespaces) // INHERITED/GLOBAL/INDIVIDUAL
6. 应用启动与模块卸载流程
zygote fork 普通应用 → setresuid(app_uid)
│ zygote 被标记 → sys_enter tracepoint → 分发器 → ksu_hook_setresuid
│ 调真实 setresuid 成功 → ksu_handle_setresuid(old=zygote_uid, new=app_uid)
▼
setuid_hook:
├─ new_uid 是 manager → 装 fd(见 §4)
├─ new_uid 被授权 su → 放行 reboot + 打标记
├─ 否则 → 撤标记(缩小 hook 范围)
└─ ksu_handle_umount(old, new) // kernel_umount
├─ !ksu_module_mounted 或 feature 关 → 返回
├─ 仅 is_appuid/is_isolated_process
├─ !ksu_uid_should_umount(new) → 返回(root app / 配了不卸载 的不处理)
├─ 旧进程 SELinux 上下文必须是 zygote(is_zygote)→ 否则忽略(安全)
└─ override_creds(ksu_cred) → 遍历 mount_list → try_umount(各挂载点)
效果:授权应用(或配置了不卸载的)保留模块可见;其余普通应用在其 mount namespace 中卸载模块挂载,看不到 root 修改。ksu_uid_should_umount 的判定逻辑见 内核层实现 §3.4。
7. 模块安装与生效流程
Manager「刷入模块」→ KsuCli.flashModule(zip) → libsu root shell → libksud.so module install <zip>
▼ ksud(switch_mnt_ns 到全局 ns)
install_module_to_system:
├─ ensure_boot_completed(未开机完成则拒绝)
├─ 读 module.prop 取 id,判断是否 metamodule
├─ check_install_safety(活动 metamodule 不稳定则阻断)
├─ 解压到 modules_update/<id>(设权限 + restorecon)
├─ exec_install_script(installer.sh [+ metamodule/metainstall.sh])
├─ 建 modules/<id>,写 update 标记
└─ regenerate_preinit_rc()
▼ 重启后 / 下次 post-fs-data
on_post_data_fs(init_event.rs):
handle_updated_modules(modules_update→modules)→ prune_modules(处理 remove 标记)
→ regenerate_preinit_rc → restorecon → load_sepolicy_rule → apply profile sepolicy
→ init_features → metamodule post-fs-data.sh → 模块 post-fs-data.sh
→ load_system_prop → metamodule metamount.sh(挂载!)→ post-mount
每阶段顺序统一为「公共 *.d 脚本 → metamodule 脚本(优先)→ 普通模块脚本」。模块挂载真正发生在 metamodule 的 metamount.sh(meta-overlayfs),ksud 自身不直接 OverlayFS(见 高级特性与安全 §3)。
8. App Profile 配置流程(双通路)
Manager AppProfileScreen 修改某应用 profile
│ 初始读取:Natives.getAppProfile(pkg, uid)(JNI);若 allowSu 补 rules=getSepolicy(pkg)(ksud)
▼ 保存 onProfileChange:
├─ 若 allowSu 且 rules 非空 → KsuCli.setSepolicy(pkg, rules) // 通路 B:ksud profile set-sepolicy
│ (sepolicy 文本存于 ksud /data/adb/ksu/profile/selinux/)
└─ Natives.setAppProfile(profile) // 通路 A:JNI → IOCTL SET_APP_PROFILE
▼ 内核 do_set_app_profile(only_manager)
ksu_set_app_profile(profile):
├─ profile_valid(version==4、groups≤32、domain 合法)
├─ RCU 哈希表 hlist_replace_rcu / hash_add_rcu(key=curr_uid)
├─ 若 curr_uid==9999 → 更新默认 non-root profile(全局"卸载模块"开关)
└─ ksu_mark_running_process() // 让标记按新策略刷新
(持久化由 ksu_persistent_allow_list 经 task_work 写 /data/adb/ksu/.allowlist)
这就是 profile 结构走 JNI、sepolicy 文本走 ksud 的"双通路"(见 用户空间实现 §5.4)。授权(allow_su)后,该应用执行 su 或调 GRANT_ROOT 即可在 §5 流程中获 root。
9. 启动形态流程(GKI vs LKM)
GKI:boot-patch 把 ksuinit+kernelsu.ko 塞进 boot ramdisk
开机 → ksuinit 挂 /proc /sys → load /kernelsu.ko(pid==1,非 late)→ execve 真 init
→ 走 §1 正常路径(注入 init.rc)→ §2 闭环
LKM:ksud late-load(运行时)
ksuinit::load_module(kallsyms 手动重定位)加载 ko(pid!=1,late)
→ 走 §1 late 路径(立即建 ksu domain/escape/强制 enforce,不注入 rc)
→ 跑全套 stage + 重启 Manager
(magica:先借 adb root 临时权限,再在完整 root 下 late-load --post-magica)
详见 高级特性与安全 §4。
10. 总结:一次完整生命周期
开机
→ 内核模块加载(GKI 内置 / LKM late-load)→ 安装分发器、注册 tracepoint、reboot kprobe
→ (GKI) 注入 init.rc → init 各阶段 exec ksud → REPORT_EVENT 回报 → on_* 回调
→ post-fs-data:搬运/清理模块、加载 allowlist、应用 sepolicy、metamodule 挂载模块
→ boot-completed:track_throne 扫描并确认 Manager、prune 失效授权
运行中
→ 每次 zygote fork app 的 setresuid:标记进程 / 给 manager 装 fd / 按策略卸载模块
→ 授权应用执行 su 或 Manager 调 GRANT_ROOT → escape_with_root_profile 提权
→ Manager 经 JNI 直连 IOCTL 读写状态/Profile/feature;经 ksud 管理模块/boot/sepolicy
包变更
→ fsnotify 监听 packages.list → track_throne 重扫 Manager + prune 白名单
下一章节:高级特性与安全 深入各 feature、metamodule、启动形态与反检测能力。
评论
- 还没有评论,来说点什么吧。