溺的文档
KernelSU · 第 5 篇 / 共 8 篇

KernelSU 关键流程分析

2026-06-30 · 阅读 1

本章把内核层与用户空间串起来,按时序分析各关键操作的端到端流程。实现细节请对照 内核层实现用户空间实现

本章对应当前重构后的实现。旧版流程(Kprobe hook reboot/cap_task_fix_setuid、workqueue umount、prctl 通信)已不再适用。


1. 系统初始化流程

kernelsu_initcore/init.c)先做共同初始化,再按 ksu_late_loaded 分两条路径。判定依据:#ifdef MODULEksu_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、启动形态与反检测能力。

评论

  • 还没有评论,来说点什么吧。

无需注册或登录,填个昵称即可评论。