KernelSU x86_64 支持:系统调用表加固引发的 Hook 失效原理与 KSU_X86_PATCH_SYSCALL_DISPATCHER 修复方案
【免费下载链接】KernelSUA Kernel based root solution for Android项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSU
KernelSU 完整支持x86_64架构,但近年来上游内核对系统调用路径的安全加固(间接分支硬化)使传统的 syscall table 修改手法失效。本文基于官方指南 x86_64-support,结合kernel/目录下的 hook 源码,完整解析这一问题的产生机制,以及两条经过源码验证的修复路线:构建选项KSU_X86_PATCH_SYSCALL_DISPATCHER与内核源码补丁法。读完本篇,你可以理解 x86_64 上 syscall hook 的底层实现(统一调度器、ni_syscall 槽位、动态跳板补丁),并在自建内核时正确完成 KernelSU 的 x86_64 集成。
一、问题背景:上游加固为什么让 syscall hook 失效
在新版本内核中,上游合入了对系统调用表(syscall table)路径的加固改动(上游提交1e3ad78334a69b36e107232e337f9d693dcc9df2)。该改动的核心是:将系统调用路径中的间接分支(indirect branch,即通过函数指针表查表调用)改写为一串直接的条件分支。
KernelSU 的syscall_hook机制恰恰依赖修改 syscall table 的表项:把某个系统调用号对应的函数指针替换为统一调度器(unified dispatcher),从而拦截并路由到 KernelSU 的 hook 处理函数。加固后内核直接通过条件分支跳转到各系统调用实现,不再查表,于是对这些表项的修改被内核完全忽略。
如果 KernelSU 在这种内核上加载、尝试 hook syscall table 而没有正确应对这个限制,系统调用将无法被路由。此时 KernelSU 的处理方式是干净地中止初始化,而不是带着失效的 hook 继续运行导致内核 panic。这一保护逻辑可以直接在源码中确认,内核入口文件 中:
int __init kernelsu_init(void) { #if defined(__x86_64__) && !defined(CONFIG_KSU_X86_PATCH_SYSCALL_DISPATCHER) // If the kernel has the hardening patch, X86_FEATURE_INDIRECT_SAFE must be set if (!boot_cpu_has(X86_FEATURE_INDIRECT_SAFE)) { pr_alert("... X86_FEATURE_INDIRECT_SAFE is not enabled! ..."); pr_alert("... KernelSU will abort initialization to prevent kernel panic."); return -ENOSYS; } #endif也就是说:在 x86_64 上,若未开启KSU_X86_PATCH_SYSCALL_DISPATCHER,初始化会检查 CPU 特性位X86_FEATURE_INDIRECT_SAFE(这是传统内核源码补丁方案创建的特性),不存在就直接返回-ENOSYS中止加载。这正是指南中“cleanly abort initialization to prevent a kernel panic”的代码级依据。
二、先理解机制:KernelSU 在 x86_64 上如何 hook 系统调用
要理解两种修复方案,需要先看清 x86_64 系统调用 hook 实现 的工作方式:
统一调度器(unified dispatcher):
ksu_syscall_dispatcher是一个只安装在一个空闲表项上的入口。它检查regs->orig_ax是否等于ksu_dispatcher_nr(调度器槽位的 syscall 号),不匹配则返回-ENOSYS;匹配时从regs->ax中取出真正的系统调用号(由 tracepoint 暂存),恢复寄存器状态后查路由表syscall_hooks[orig_nr]分发(dispatcher 实现)。ni_syscall 槽位:初始化函数
ksu_syscall_hook_init会先通过 ksu_find_ni_syscall_slots 遍历sys_call_table,找出指向__x64_sys_ni_syscall(未实现调用占位)的表项,占用其一作为调度器槽位。找不到就打印failed to find ni_syscall slot for dispatcher并直接返回——这是初始化失败路径。表项改写:
ksu_syscall_table_hook通过 patch_syscall_table 调用ksu_patch_text覆写表项,并用hooked_entries数组记录原始指针,以便模块卸载时逐一恢复(退出路径)。拦截入口是 sys_enter tracepoint:hook 管理器 注册了
sys_enter优先 tracepoint,当某个被 hook 的调用号(setresuid、execve、execveat、newfstatat、faccessat)进入内核时,把原调用号存入ax、把orig_ax改写为调度器槽位号,等内核“查表”时就会落到 KernelSU 的调度器上。
关键点在于:整套机制的前提是内核最终仍会通过sys_call_table[nr]间接跳转。加固后的内核在x64_sys_call处改用直接条件分支,表项修改不再被使用,hook 全部失效。
三、方案一:启用 KSU_X86_PATCH_SYSCALL_DISPATCHER(KernelSU 3.3.0+ 推荐)
KernelSU 3.3.0 引入了官方机制:构建选项KSU_X86_PATCH_SYSCALL_DISPATCHER。开启后,KernelSU 在运行时动态修补被加固的 syscall dispatcher,使其重新查表,从而无需之前的内核源码补丁集。这是使用 KernelSU 3.3.0 或更新版本构建内核时的推荐做法。
3.1 配置项定义
在 Kconfig 中:
config KSU_X86_PATCH_SYSCALL_DISPATCHER bool "Dynamically patch x64's hardened syscall dispatcher to support syscall hooks" depends on KSU && X86_64 default n help Dynamically patch x64's hardened syscall dispatcher to support syscall hooks. This is a replacement for a kernel source code patch, and is useful for x86_64 LKM mode.要点:
- 仅对
KSU && X86_64可用,默认关闭; - 官方说明明确指出它是“内核源码补丁的替代品”,并且特别适用于 x86_64 LKM(可加载内核模块)模式——LKM 模式下无法修改内核源码,这个运行时修补是唯一选择;
- 在 LKM 构建时,Kbuild 会将其转换为宏定义参与编译:
ccflags-y += -DCONFIG_KSU_X86_PATCH_SYSCALL_DISPATCHER=1。
3.2 运行时修补做了什么
开启该选项后,ksu_syscall_hook_init会额外执行 patch_abs_jump,把内核中x64_sys_call函数的入口替换为一个绝对跳转跳板:
static long my_x64_sys_call(const struct pt_regs *regs, unsigned int nr) { return ksu_syscall_tablenr; // 重新强制走 sys_call_table 查表 }patch_abs_jump的细节(源码):
- 通过
find_kernel_symbol_exact精确解析目标符号地址; - 若目标以
endbr64(4 字节0xf3 0x0f 0x1e 0xfa,CET 间接分支入口指令)开头,自动跳过; - 在入口处写入 14 字节的
jmp *(%rip)+.quad 目标地址序列,先把原始 14 字节备份到x64_sys_call_patch_orig_insn; - 写入通过
ksu_patch_text完成。x86_64 上的 内存补丁实现 会:经由init_mm页表把内核虚拟地址翻译为物理地址(phys_from_virt),用set_fixmap(FIX_BTMAP_BEGIN)建立 fixmap 映射,copy_to_kernel_nofault写入,全程由stop_machine冻结所有 CPU 保证原子性。
此外还有一个版本相关的特殊分支(源码):对 5.16 之前的内核(注释指向部分 AVD 13-5.15 镜像缺少上游提交fb13b11d53875e28e7fbf0c26b288e4ea676aa9f),还需要把整个do_syscall_64替换为my_do_syscall_64,在其中用array_index_nospec查ksu_syscall_table完成调用,同时保留syscall_enter_from_user_mode/syscall_exit_to_user_mode的调用链。
模块卸载时,ksu_syscall_hook_exit 会用备份的原始指令恢复x64_sys_call(及旧内核上的do_syscall_64),并逐一还原所有被改写的 syscall table 表项,保证卸载后系统调用路径完全回到内核原状。
四、方案二:沿用原内核源码补丁
如果不想开启KSU_X86_PATCH_SYSCALL_DISPATCHER,可以继续走原有的内核源码补丁路线。这些补丁的作用是创建 CPU 特性X86_FEATURE_INDIRECT_SAFE,并允许通过内核启动参数syscall_hardening=off关闭针对系统调用的间接分支加固——这正是第二节中 内核初始化检查 所依赖的特性位。
按内核版本选择对应补丁(提交哈希,可在对应的 kernel_common / kernel-zenith 仓库中检索):
内核 6.6: fe9a9b4c320577c30e1f22d04039e414c6a3cdec df772e99e392f24b395ceaf7b26974e3e4828ee9 内核 6.12: dd2c602268fdc81f4d3b662f6a15142ac0ec7bcd 7d99237ae5da61c19447138da3282ae37d43857b 内核 6.18: 40b1c323d1ad29c86e041d665c7f089b9a3ccfb5 f5813e10b7630e1ccd86fc2c4cf30eef60b64a82采用此方案时,需要在设备内核的 cmdline 中加入syscall_hardening=off特性激活参数,确保boot_cpu_has(X86_FEATURE_INDIRECT_SAFE)为真,KernelSU 初始化才能通过检查。
两个方案只能二选一,严禁同时启用:两者都会让x64_sys_call恢复查表行为,叠加使用会造成重复改写同一入口。
五、安全警告(必须知晓)
无论选择哪种方案,你都在主动绕过或削弱一项用于防御推测执行漏洞(speculative execution)的缓解措施:
- 这会重新打开系统调用路径上的间接分支攻击面(即缓解间接分支预测侧信道类漏洞的保护被移除);
- 如果你运行的是生产服务器,或对侧信道安全有严格要求的系统,不要使用这两种方案中的任何一种;
- 这些方案面向的是以“获得 KernelSU 的 root 能力”为优先、可以接受放弃该项硬件漏洞缓解的测试环境。
六、如何选择方案
官方指南给出的决策建议:
- 使用 KernelSU 3.3.0 或更新版本,且可以修改 KernelSU 构建配置→ 开启
KSU_X86_PATCH_SYSCALL_DISPATCHER。这是免内核源码补丁的官方机制,对 LKM 模式是唯一可行路径; - 希望保持现有内核侧补丁工作流→ 继续使用第四节的源码补丁方案。
从源码结构看,两者在初始化入口互斥:开启选项后kernelsu_init会跳过X86_FEATURE_INDIRECT_SAFE特性检查(该检查仅存在于!defined(CONFIG_KSU_X86_PATCH_SYSCALL_DISPATCHER)分支),改由运行时跳板直接让内核重新查表。
七、关键源码路径速查
| 内容 | 路径 |
|---|---|
KSU_X86_PATCH_SYSCALL_DISPATCHER配置定义 | kernel/Kconfig |
| LKM 模式下宏定义注入 | kernel/Kbuild |
| x86_64 架构编译选择 | kernel/Kbuild |
| syscall 表 hook / 调度器 / 跳板补丁 | kernel/hook/x86_64/syscall_hook.c |
| x86_64 内核文本修补(stop_machine + fixmap) | kernel/hook/x86_64/patch_memory.c |
X86_FEATURE_INDIRECT_SAFE检查与中止逻辑 | kernel/core/init.c |
| sys_enter tracepoint 拦截与 hook 注册 | kernel/hook/syscall_hook_manager.c |
适用前提与限制:以上分析基于当前仓库代码,对应 KernelSU 3.3.0 及之后的内核侧实现;KSU_X86_PATCH_SYSCALL_DISPATCHER依赖KPROBES(KSU配置项的前提)与EXT4_FS,且仅在X86_64平台生效。x86_64 的 GKI 内核普遍已回移植加固提交(源码注释指出该硬化除 5.10 外几乎回移植到所有 GKI 内核),因此在较新 x86_64 内核上构建 KernelSU 时,两个方案至少需要启用其一。
【免费下载链接】KernelSUA Kernel based root solution for Android项目地址: https://gitcode.com/GitHub_Trending/ke/KernelSU
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考