SUSFS:KernelSU 的 root 隐藏增强
解读对象:
susfs4ksu,分支gki-android15-6.6-dev,版本 v2.2.0 适用内核:GKI 内核 / Android 15 / Linux 6.6 文档定位:面向想要理解 SUSFS 工作原理、架构与实现细节的开发者与安全研究者
目录
- 项目是什么
- 背景:Root 检测与隐藏的攻防
- 整体架构
- 用户态 ↔ 内核通信机制
- 核心隐藏机制(理论基础)
- 功能模块详解
- 内核 Hook 点全景
- 用户空间工具
ksu_susfs命令参考 - KernelSU 模块部署流程
- 编译与安装
- 已知限制与注意事项
- 源码观察与潜在问题
- 目录文件索引
- 版本与致谢
1. 项目是什么
SUSFS(SUS File System) 是一套为 KernelSU 设计的 root 隐藏(root hiding)增强系统。它本身不是一个独立的文件系统,而是一组:
- 内核补丁:在 Linux 内核的 VFS、proc、selinux、namespace 等关键路径插入 hook,实现"对特定进程隐藏/伪造文件系统信息"的能力;
- 用户空间工具
ksu_susfs:一个 root shell 下运行的命令行程序,用于向内核下发"要隐藏什么、要伪造成什么样"的配置规则; - KernelSU 模块
susfs4ksu:一个标准的 KSU 模块,负责部署ksu_susfs工具,并在开机各阶段自动调用它应用隐藏规则。
一句话概括:SUSFS 让"被 root 的设备"在普通 App 眼里看起来像一台"没有被 root 的干净设备"。
⚠️ 项目 README 明确警告:这是实验性代码,可能损害系统或带来性能损失,使用需自担风险。
2. 背景:Root 检测与隐藏的攻防
许多 App(银行、支付、游戏、流媒体 DRM 等)会主动检测设备是否被 root,一旦发现就拒绝运行。检测手段通常包括:
| 检测面 | 典型做法 | SUSFS 的对策 |
|---|---|---|
| 可疑文件/目录 | 探测 /system/bin/su、Magisk/KSU 模块目录、TWRP 残留等 |
sus_path 路径隐藏 |
| 可疑挂载点 | 读 /proc/self/mountinfo 找 overlay / bind mount |
sus_mount 挂载隐藏 |
| 文件元数据异常 | stat() 发现系统文件被改过(时间戳、inode、大小变了) |
sus_kstat 元数据伪造 |
| 内存映射痕迹 | 读 /proc/self/maps 找注入的 .so(如 Zygisk 模块) |
sus_map 映射隐藏 |
| 被篡改的配置文件 | 读取 hosts、prop 等被修改的文件 | open_redirect 打开重定向 |
| 内核版本特征 | uname() 发现是自编译内核(带 -dirty、自定义字符串) |
spoof_uname 版本伪造 |
| 启动参数特征 | 读 /proc/cmdline、/proc/bootconfig 看 verifiedbootstate 等 |
spoof_cmdline_or_bootconfig |
| SELinux 日志 | 从 dmesg/kmsg 找 u:r:su:s0 等 AVC denial |
avc_log_spoofing 日志伪造 |
SUSFS 的核心思路是在内核层面做"选择性欺骗":只对"需要被隐藏的目标进程"(普通 App)撒谎,而对 root/su 进程、系统进程仍返回真实信息。这样既骗过了检测 App,又不影响 root 功能本身正常工作。
3. 整体架构
┌──────────────────────────────────────────────────────────────────────┐
│ 用户空间 (Userspace) │
│ │
│ KSU 模块 susfs4ksu 命令行工具 ksu_susfs │
│ ├─ post-fs-data.sh ──────┐ │
│ ├─ service.sh ├──调用──► ksu_susfs add_sus_path ... │
│ ├─ boot-completed.sh ─────┘ ksu_susfs add_sus_kstat ... │
│ └─ utils.sh (辅助函数) ksu_susfs set_uname ... │
│ │ │
│ │ syscall(SYS_reboot, │
│ │ 0xDEADBEEF, │
│ │ 0xFAFAFAFA, │
│ ▼ CMD, &info) │
├──────────────────────────────────────────────┼───────────────────────┤
│ 内核空间 (Kernel) ▼ │
│ │
│ KernelSU 集成补丁 ksu_handle_sys_reboot() │
│ (10_enable_susfs_for_ksu.patch) ──► 校验 magic + uid,分发命令 │
│ │ │
│ ▼ │
│ SUSFS 核心 (fs/susfs.c) susfs_add_sus_path() / ... │
│ ├─ 维护隐藏规则(链表/哈希表) 给目标 inode 打标记 / 存规则 │
│ └─ 提供判定与伪造函数 │
│ ▲ │
│ 内核 VFS Hook 补丁 │ 调用判定/伪造 │
│ (50_add_susfs_in_*.patch) ────────┘ │
│ namei.c / stat.c / readdir.c / │
│ namespace.c / proc/* / selinux/* ◄── App 进程的系统调用经过这里 │
└──────────────────────────────────────────────────────────────────────┘
三大组成部分:
| 组件 | 目录 | 作用 |
|---|---|---|
| 内核补丁 | kernel_patches/ |
SUSFS 核心实现 + 内核 hook + KSU 集成 |
| 用户工具 | ksu_susfs/ |
下发隐藏规则的 CLI(NDK 编译,arm64) |
| KSU 模块 | ksu_module_susfs/ |
自动化部署与配置 |
内核补丁的三个文件
kernel_patches/fs/susfs.c(1471 行) + 头文件include/linux/susfs.h、include/linux/susfs_def.h—— SUSFS 的核心逻辑。这三个是完整文件,直接复制进内核源码树(不是 diff)。kernel_patches/50_add_susfs_in_gki-android15-6.6.patch(2353 行) —— 对原生 Linux 内核源码的修改(diff),在几十个 VFS/proc/selinux 函数里插入对susfs_*()的调用。kernel_patches/KernelSU/10_enable_susfs_for_ksu.patch(2824 行) —— 对 KernelSU 源码的修改(diff),把 SUSFS 接入 KSU 的命令分发、cred 管理、setuid hook、umount 等流程,并加入 Kconfig 开关。
4. 用户态 ↔ 内核通信机制
SUSFS 复用了 KernelSU 的 reboot 系统调用 supercall 通道,没有新增系统调用,隐蔽性更好。
用户态调用
ksu_susfs 工具通过以下方式发起请求(概念形式):
syscall(SYS_reboot,
KSU_INSTALL_MAGIC1, // 0xDEADBEEF —— KernelSU 安装魔数
SUSFS_MAGIC, // 0xFAFAFAFA —— SUSFS 专属魔数(magic2)
CMD_SUSFS_xxx, // 具体命令码,如 0x55550
&info); // 指向命令对应的结构体(含输入参数与 err 返回字段)
每个命令都有一个对应的结构体(定义在 include/linux/susfs.h),结构体里都有一个 int err 字段:
- 用户态把
err预置为ERR_CMD_NOT_SUPPORTED(126); - 内核处理后把
err写回(0成功,或具体 errno); - 若返回值仍是 126,说明内核未编译该功能(对应
CONFIG_*没开),工具会提示"please enable it in kernel"。
内核态分发
内核侧的入口是 KSU 集成补丁新增的 ksu_handle_sys_reboot()(见 dispatch.c):
int ksu_handle_sys_reboot(int magic1, int magic2, unsigned int cmd, void __user **arg)
{
if (magic1 != KSU_INSTALL_MAGIC1) // 1) 必须是 KSU 魔数
return -EINVAL;
// 2) magic2 == SUSFS_MAGIC 且调用方必须是 root
if (magic2 == SUSFS_MAGIC && current_uid().val == 0) {
switch (cmd) {
case CMD_SUSFS_ADD_SUS_PATH: susfs_add_sus_path(arg); return 0;
case CMD_SUSFS_ADD_SUS_KSTAT: susfs_add_sus_kstat(arg); return 0;
case CMD_SUSFS_SET_UNAME: susfs_set_uname(arg); return 0;
/* ... 其余命令 ... */
default: return -EINVAL;
}
}
if (magic2 == KSU_INSTALL_MAGIC2) // 3) 否则回退到 KSU 自身的 supercall
return ksu_supercall_reboot_handler(arg);
return -EINVAL;
}
三重校验:magic1 == 0xDEADBEEF + magic2 == 0xFAFAFAFA + uid == 0。三者全中才会执行 SUSFS 命令;magic2 不同则交给 KSU 原有逻辑。每个命令对应的处理函数都用 #ifdef CONFIG_KSU_SUSFS_* 包裹——功能未启用时该 case 不存在,于是返回 -EINVAL,最终体现为用户态那个 126。
命令码常量集中定义在
kernel_patches/include/linux/susfs_def.h:11-34(用户态与内核态共享同一份定义)。
5. 核心隐藏机制(理论基础)
理解 SUSFS 必须先理解它的两个底层基石:"哪些进程要被骗" 和 "哪些对象要被处理"。
5.1 标记"要被骗的进程":TIF_PROC_UMOUNTED
SUSFS 隐藏的目标是 "被卸载了 root 挂载的普通 App 进程"。这个状态用进程 thread_info 上的一个 flag 位标记:
#define TIF_PROC_UMOUNTED 33 // susfs_def.h:59
判定辅助函数(susfs_def.h:96-107):
// 进程是否已被卸载 root 挂载
static inline bool susfs_is_current_proc_umounted(void) {
return test_thread_flag(TIF_PROC_UMOUNTED);
}
// 是否是"被卸载的 App 进程"(带标志 且 uid >= 10000)—— 隐藏功能的主要生效条件
static inline bool susfs_is_current_proc_umounted_app(void) {
return test_thread_flag(TIF_PROC_UMOUNTED) && current_uid().val >= 10000;
}
5.2 标记的触发链:从 zygote fork 到打标
这是 SUSFS 与 KSU 协作的核心。当 zygote 孵化一个新 App 时会调用 setresuid() 切换 uid,KSU 的 ksu_handle_setresuid()(被 10 号补丁重写,见 setuid_hook.c)拦截这一时刻:
int ksu_handle_setresuid(uid_t ruid, uid_t euid, uid_t suid)
{
// 只关心由 zygote 派生的进程
if (!susfs_is_sid_equal(current_cred(), susfs_zygote_sid))
return 0;
if (is_isolated_process(ruid)) // 隔离服务进程 → 卸载
goto do_umount;
if (is_uid_manager(ruid)) { ... return 0; } // KSU 管理器 App → 放行
if (ruid == WEBVIEW_ZYGOTE_UID) return 0; // webview zygote → 不卸载
// 普通 App 且策略判定需要卸载 root 挂载
if (is_appuid(ruid) && ksu_uid_should_umount(ruid))
goto do_umount;
if (ksu_is_allow_uid_for_current(ruid)) disable_seccomp(); // 授权 root 的 App
return 0;
do_umount:
ksu_handle_umount(current_uid().val, ruid); // 卸载 KSU 的挂载,App 看不到 root 文件
ksu_handle_extra_susfs_work(); // 调度 susfs_extra_works 工作队列
susfs_set_current_proc_umounted(); // ★ 给本进程打上 TIF_PROC_UMOUNTED
return 0;
}
关键点:
- 只有"普通 App / 隔离进程"会被打标并卸载挂载;KSU 管理器、授权 root 的 App、系统进程不打标。
- 打标后调度的
susfs_extra_works工作队列会执行susfs_run_sus_path_loop()(susfs.c:1451-1460),把sus_path_loop列表里的路径重新打标——因为进程切换/dcache 回收后标记可能丢失,需要补打。延迟到工作队列执行是为了不阻塞 setresuid,并尽量减少时间侧信道。
5.3 标记"要被处理的对象":inode->i_mapping->flags
SUSFS 不为每个敏感文件维护一个大列表去逐一比对路径(那样太慢),而是直接在目标文件的 inode 上打标记位,利用内核已有的 inode->i_mapping->flags(地址空间标志)的高位:
#define AS_FLAGS_SUS_PATH 33 // susfs_def.h:61-65
#define AS_FLAGS_SUS_MOUNT 34
#define AS_FLAGS_SUS_KSTAT 35
#define AS_FLAGS_OPEN_REDIRECT 36
#define AS_FLAGS_SUS_MAP 39
下发规则时(如 susfs_add_sus_path):kern_path() 解析路径 → 找到 inode → set_bit(AS_FLAGS_SUS_PATH, &inode->i_mapping->flags)。
检查时(如 App 进行路径查找):内核 hook 调用 susfs_is_inode_sus_path(),只需 test_bit() 一次即可判定,O(1) 且无需字符串比较,对热路径友好。
对 FUSE 文件系统(Android 的 /sdcard 走 FUSE)做了特殊处理:同时给 FUSE inode 和其底层 inode 打标(susfs.c:71-84)。
5.4 "对谁生效"的 UID 方案
open_redirect 等功能支持精细控制对哪类进程生效,定义在 susfs.h:28-34:
enum UID_SCHEME {
UID_NON_APP_PROC = 0, // 非 App 进程(uid 不在 10000~19999)
UID_ROOT_PROC_EXCEPT_SU_PROC, // uid==0 但不是 su 域的进程
UID_NON_SU_PROC, // 所有非 su 域进程(慎用)
UID_UMOUNTED_APP_PROC, // 被卸载的 App 进程
UID_UMOUNTED_PROC, // 所有被卸载的进程(最激进,慎用)
};
6. 功能模块详解
下表为功能总览,随后逐一展开。"生效对象" 一列尤其重要——大多数隐藏只对"被卸载的 App 进程"生效,对 root/su 进程透明。
| 功能 | 命令码 | 核心数据结构 | 生效对象 |
|---|---|---|---|
sus_path 路径隐藏 |
0x55550 / 0x55553 |
inode 标记 + 循环列表 | umounted App (uid≥10000) |
sus_mount 挂载隐藏 |
0x55561 |
特殊 mnt_id 标记 | 非 su 进程 |
sus_kstat 元数据伪造 |
0x55570/71/72 |
SUS_KSTAT_HLIST 哈希表 |
umounted App (uid≥10000) |
sus_map 映射隐藏 |
0x60020 |
inode 标记 | umounted App (uid≥10000) |
open_redirect 打开重定向 |
0x555c0 |
OPEN_REDIRECT_HLIST 哈希表 |
由 uid_scheme 决定 |
spoof_uname 版本伪造 |
0x55590 |
seqlock 保护的缓冲 | 所有进程 |
spoof_cmdline_or_bootconfig |
0x555b0 |
seqlock 保护的缓冲 | 所有进程 |
avc_log_spoofing 日志伪造 |
0x60010 |
static key 开关 | 全局 |
enable_log 日志开关 |
0x555a0 |
static key 开关 | 全局(调试用) |
show 信息查询 |
0x555e1/e2/e3 |
—— | 查询用 |
6.1 sus_path —— 路径隐藏
目标:让指定文件/目录对普通 App "凭空消失"——stat 报不存在、open 失败、getdents(列目录)里不出现。
实现(susfs.c:47-237):
- 下发:
susfs_add_sus_path()给目标 inode 的i_mapping->flags打AS_FLAGS_SUS_PATH位。 - 判定:
susfs_is_inode_sus_path()在路径查找的多个内核函数里被调用。命中条件是 进程是 umounted App + inode 带标记 + inode 的属主 uid ≠ 当前进程 uid(is_i_uid_not_allowed,susfs.c:174-176)。最后一条很关键:它让 App 仍能看到自己拥有的文件,只隐藏"不属于自己"的可疑文件。 - 循环补打:
add_sus_path_loop(命令码0x55553)把路径加入LH_SUS_PATH_LOOP链表,每次有进程被卸载时由工作队列重新打标(应对频繁变化或被重建的路径,如/sdcard/TWRP)。
为防止 dcache 缓存泄露,代码里还准备了一个 susfs_fake_qstr_name = "..5.u.S"(susfs.c:45)用于重新测试 dcache 查找——README/注释提醒不要有同名文件。
add_sus_path(一次性)适合永久存在的路径;add_sus_path_loop(循环补打)适合会被回收/重建的路径。
6.2 sus_mount —— 挂载隐藏
这是 SUSFS 最精巧的设计之一。目标是让 KSU 制造的 bind/overlay 挂载不出现在 /proc/<pid>/{mounts,mountinfo,mountstats} 中。
思路:给 KSU 创建的挂载分配一段"特殊号段"的 ID 作为指纹,显示时再据此过滤/改写。
相关常量(susfs_def.h:45-47):
#define VFSMOUNT_MNT_FLAGS_KSU_UNSHARED_MNT 0x80000000 // 标记 unshare 命名空间复制出来的挂载
#define DEFAULT_KSU_MNT_ID 2000000000 // KSU 挂载的 mnt_id 从 20 亿起
#define DEFAULT_KSU_MNT_GROUP_ID 200000 // KSU 挂载的 mnt_group_id 从 20 万起
实现(50 号补丁的 fs/namespace.c):
mnt_alloc_group_id():若当前是 ksu 域,分配从DEFAULT_KSU_MNT_GROUP_ID起的 group id。vfs_create_mount()/clone_mnt():若当前是 ksu 域(或源挂载已是高号段),改用susfs_alloc_non_unshare_ksu_vfsmnt()分配从DEFAULT_KSU_MNT_ID起的 mnt_id。- unshare 命名空间复制(
CL_COPY_MNT_NS)时用susfs_alloc_unshare_ksu_vfsmnt()复用原 id,并打上VFSMOUNT_MNT_FLAGS_KSU_UNSHARED_MNT标志位(这样mnt_free_id()知道这个 id 不是 ida 分配的,不去释放它)。 - 显示侧(
fs/proc_namespace.c的show_vfsmnt/show_mountinfo/show_vfsstat):对非 su 进程,用susfs_get_non_sus_mnt_id_from_mnt()把高号段 mnt_id 重映射回正常值或跳过,从而隐藏 KSU 挂载。
性能优化:用 susfs_is_sdcard_android_data_not_decrypted 这个 static branch 做开关。开机早期 /sdcard 尚未解密、挂载活动频繁时才逐挂载检查 ksu 域;一旦检测到 /data/media/0/Android 可访问(说明解密完成、早期挂载结束),就关闭检查以省开销。这个检测由 susfs_start_sdcard_monitor_fn()(susfs.c:1281-1449)用 fsnotify 监控实现。
开关命令 hide_sus_mnts_for_non_su_procs(0x55561):控制是否对非 su 进程隐藏挂载。默认 false——这样 Zygisk 类方案能在 post-fs-data 阶段先正常读到挂载,避免缓存问题(susfs.c:241-243 有详细注释)。
6.3 sus_kstat —— 文件元数据伪造
目标:当系统文件被 bind mount 替换后,其 stat() 信息(inode 号、设备号、大小、时间戳、块数等)会变化而暴露篡改。sus_kstat 让这些字段返回"原始值"。
数据结构:用哈希表 SUS_KSTAT_HLIST(susfs.c:267)以 inode 号为 key 存储伪造规则。可伪造的字段由位标志控制(susfs.h:63-74):
KSTAT_SPOOF_INO (1<<0) // inode 号
KSTAT_SPOOF_DEV (1<<1) // 设备号
KSTAT_SPOOF_NLINK (1<<2) // 硬链接数
KSTAT_SPOOF_SIZE (1<<3) // 大小
KSTAT_SPOOF_ATIME/MTIME/CTIME (秒/纳秒) // 三种时间戳
KSTAT_SPOOF_BLOCKS (1<<10) // 块数
KSTAT_SPOOF_BLKSIZE (1<<11) // 块大小
三种用法:
- 静态
add_sus_kstat_statically(0x55572):直接把目标 stat 各字段全部手工指定(用stat -c取值,填default表示该字段不伪造)。适合不需要真正挂载、只想改 stat 表现的场景。 - 动态两步法
add_sus_kstat(0x55570)+update_sus_kstat(0x55571):挂载前add记录原始 stat,挂载后update把"当前(已变)的 inode 号"更新进规则、但伪造值仍用原始的。配合 bind mount 使用。 update_sus_kstat_full_clone:完整克隆,连 nlink/size 也一并保持原值。
伪造点:susfs_sus_kstat_spoof_generic_fillattr()(susfs.c:483-555)在内核 generic_fillattr()(stat 填充)时被调用,命中规则就按 flags 逐字段覆写 struct kstat。另有 susfs_sus_kstat_spoof_show_map_vma() 用于 /proc/pid/maps 里显示的 dev/ino。
下发时还会按 __ARCH_WANT_STAT64 等宏对 spoofed_dev 做 huge_decode_dev / old_decode_dev 编码转换(susfs.c:336-344),以匹配用户态 stat 看到的设备号格式。
6.4 sus_map —— 内存映射隐藏
目标:隐藏被 mmap 的真实文件路径,使其不出现在 /proc/self/maps、smaps、smaps_rollup、map_files、pagemap 等中——主要用于隐藏注入到 App 的 .so(如 Zygisk 模块)。
实现(susfs.c:1096-1129):susfs_add_sus_map() 给目标 inode 打 AS_FLAGS_SUS_MAP 标记。判定宏 SUSFS_IS_INODE_SUS_MAP(susfs_def.h:109-112)要求 inode 带标记 + 是 umounted App。内核 fs/proc/task_mmu.c 的 show_map_vma 等被 hook,命中后跳过该 VMA 行。
局限(README/工具帮助强调):不隐藏匿名内存;不隐藏注入库自身做的 inline/PLT hook;对实现良好的注入检测可能无效。
6.5 open_redirect —— 文件打开重定向
目标:当符合条件的进程打开 target 文件时,偷偷重定向到 redirected 文件。典型用途:让 App 读到"干净的原版文件",而 root 自己读到"被修改的文件"(或反之)。
实现(susfs.c:762-1091):用哈希表 OPEN_REDIRECT_HLIST 存双向映射(正向重定向 + 反向 readlink 查询)。下发时给 target 与 redirected 两个 inode 都打 AS_FLAGS_OPEN_REDIRECT 标记。生效范围由 uid_scheme(见 5.4)决定。涉及多处伪造,覆盖面很广:
| 伪造函数 | 作用的内核路径 |
|---|---|
susfs_open_redirect_spoof_do_sys_openat |
open() 实际重定向 |
susfs_open_redirect_spoof_vfs_readlink |
readlink() 返回伪造目标 |
susfs_open_redirect_spoof_do_proc_readlink |
/proc/*/fd 软链接 |
susfs_open_redirect_spoof_vfs_statfs |
statfs() 文件系统信息 |
susfs_open_redirect_spoof_seq_show |
/proc/*/fdinfo 的 mnt_id/ino |
susfs_open_redirect_spoof_show_map_vma |
/proc/*/maps 的 ino/dev/name |
注意:FUSE 文件系统不支持 open_redirect(
susfs.c:819-824明确拒绝)。
6.6 spoof_uname —— 内核版本伪造
目标:让 uname() 的 release(如 uname -r)和 version(如 uname -v)字段返回伪造值,掩盖自编译内核特征(-dirty、自定义编译信息等)。对所有进程生效。
实现(susfs.c:608-663):susfs_set_uname() 把伪造值存入 my_uname(seqlock 保护,读多写少场景高效)。填 default 表示用真实值。susfs_spoof_uname() 在内核 uname 系统调用路径(kernel/sys.c 的 override_release 附近)被调用,覆写返回的 new_utsname。用 static key susfs_is_uname_spoof_buffer_set 控制是否已设置,避免无谓开销。
6.7 spoof_cmdline_or_bootconfig —— 启动参数伪造
目标:伪造 /proc/cmdline(非 GKI)或 /proc/bootconfig(GKI)的内容,掩盖如 androidboot.verifiedbootstate、解锁状态等特征。对所有进程生效。
实现(susfs.c:692-759):从用户给的文件读入内容(≤8192 字节),存入内核缓冲(seqlock 保护)。susfs_spoof_cmdline_or_bootconfig() 在 fs/proc/bootconfig.c 等的 seq_file 输出处被调用,直接输出伪造内容。
6.8 avc_log_spoofing —— SELinux 日志伪造
目标:内核 AVC(SELinux 访问拒绝缓存)日志里若出现 u:r:su:s0 等 su 域上下文,会被检测利用。启用后把 su 的 tcontext 伪造成普通 App 上下文(u:r:priv_app:s0:c512,c768)。全局开关。
实现(susfs.c:1131-1156):static key susfs_is_avc_log_spoofing_enabled 控制;hook 在 security/selinux/avc.c 的 avc_audit_post_callback。
注意:启用会干扰开发者排查 SELinux 问题,调试时建议关闭。
6.9 enable_log —— 内核日志开关
控制 SUSFS 自身的内核日志输出(SUSFS_LOGI/SUSFS_LOGE,susfs.c:31-38)。用 static key susfs_is_log_enabled 实现,关闭时近乎零开销。需内核开启 CONFIG_KSU_SUSFS_ENABLE_LOG。生产环境建议关闭,调试时打开。
6.10 show —— 信息查询
show version(0x555e1):返回 SUSFS 版本(SUSFS_VERSION = "v2.2.0",susfs.h:12)。show variant(0x555e3):返回GKI或NON-GKI(按内核版本 ≥5.0.0 判定,susfs.h:13-17)。show enabled_features(0x555e2):返回内核实际编译进了哪些CONFIG_KSU_SUSFS_*功能(susfs.c:1171-1243用一串#ifdef拼字符串)。用于诊断内核到底支持哪些能力。
7. 内核 Hook 点全景
50_add_susfs_in_gki-android15-6.6.patch 在原生内核里插桩。下表给出被修改的内核文件及其服务的 SUSFS 功能(覆盖了几乎所有"信息泄露面"):
| 内核文件 | 被 hook 的关键函数 | 服务的功能 |
|---|---|---|
fs/namei.c |
lookup_dcache、lookup_fast、__lookup_slow、walk_component、lookup_open、vfs_readlink 等 |
sus_path、open_redirect |
fs/stat.c |
generic_fillattr、vfs_getattr_nosec、vfs_fstat、vfs_statx |
sus_kstat |
fs/readdir.c |
filldir、filldir64、fillonedir(含 compat 变体) |
sus_path(列目录时隐藏条目) |
fs/namespace.c |
mnt_alloc_id/group_id、mnt_free_id、vfs_create_mount、clone_mnt、copy_mnt_ns |
sus_mount |
fs/proc_namespace.c |
show_vfsmnt、show_mountinfo、show_vfsstat |
sus_mount(挂载显示过滤) |
fs/open.c |
do_sys_openat2、do_faccessat、file_open_root |
open_redirect、sus_path |
fs/proc/task_mmu.c |
show_map_vma、show_smap、show_smaps_rollup、pagemap_read |
sus_map、sus_kstat |
fs/proc/base.c |
proc_pid_get_link、do_proc_readlink、proc_map_files_readdir |
open_redirect、sus_map |
fs/proc/fd.c |
seq_show(fdinfo) |
open_redirect |
fs/proc/bootconfig.c |
bootconfig 输出 | spoof_cmdline_or_bootconfig |
fs/statfs.c |
vfs_get_fsid 等 |
open_redirect |
fs/notify/fdinfo.c |
inotify fdinfo 显示 | 隐藏 inotify 监控痕迹 |
fs/read_write.c |
ksys_read |
辅助 |
fs/exec.c |
bprm_execve、do_execveat_common |
执行路径相关 |
kernel/sys.c |
__sys_setresuid、override_release(uname) |
spoof_uname、触发 umount |
kernel/kallsyms.c |
s_show |
隐藏 ksu/susfs 内核符号 |
kernel/reboot.c |
reboot 系统调用 |
supercall 入口 |
mm/memory.c |
__access_remote_vm |
内存访问相关 |
security/selinux/avc.c |
avc_audit_post_callback |
avc_log_spoofing |
security/selinux/hooks.c |
selinux_setprocattr 等 |
SELinux 集成 |
security/selinux/selinuxfs.c |
selinuxfs 读写 | SELinux 状态伪装 |
security/selinux/ss/services.c |
context_struct_compute_av 等 |
SELinux 集成 |
drivers/input/input.c |
input_handle_event |
输入事件(特殊触发) |
fs/Makefile |
—— | 加入 susfs.o 编译 |
KSU 集成补丁 10_enable_susfs_for_ksu.patch 修改的则是 KernelSU 自身(注意该 KSU 采用分层目录结构 kernel/{core,feature,hook,policy,runtime,selinux,sulog,supercall}/,是重构版/fork):
| KSU 文件 | 改动 |
|---|---|
kernel/Kconfig |
新增全部 CONFIG_KSU_SUSFS_* 开关 |
kernel/Kbuild、kernel/Makefile |
编译 SUSFS 相关对象 |
kernel/supercall/dispatch.c |
新增 ksu_handle_sys_reboot() 命令分发;开机完成时启动 sdcard 监控 |
kernel/hook/setuid_hook.c |
重写 ksu_handle_setresuid(),接入 umount + 打 TIF_PROC_UMOUNTED |
kernel/feature/kernel_umount.c |
内核卸载逻辑 |
kernel/feature/sucompat.c |
su 兼容(inline hook 方式) |
kernel/feature/selinux_hide.c |
SELinux 隐藏 |
kernel/core/init.c |
调用 susfs_init() |
kernel/selinux/* |
提供 susfs_is_current_ksu_domain、susfs_is_sid_equal 等 |
8. 用户空间工具 ksu_susfs 命令参考
工具入口在 ksu_susfs/jni/main.c,按 argv[1] 分发到 features/ 下各模块。所有命令必须以 root 运行。
| 命令 | 用法 | 说明 |
|---|---|---|
add_sus_path |
add_sus_path <path> |
隐藏路径(一次性) |
add_sus_path_loop |
add_sus_path_loop <path> |
隐藏路径(每次进程卸载时补打,适合易变路径) |
hide_sus_mnts_for_non_su_procs |
... <0|1> |
是否对非 su 进程隐藏挂载 |
add_sus_kstat_statically |
... <path> <ino> <dev> <nlink> <size> <atime> <atime_ns> <mtime> <mtime_ns> <ctime> <ctime_ns> <blocks> <blksize> |
静态伪造 stat(字段可填 default) |
add_sus_kstat |
add_sus_kstat <path> |
挂载前记录原始 stat(配合 update) |
update_sus_kstat |
update_sus_kstat <path> |
挂载后更新 inode(保留原 size/blocks) |
update_sus_kstat_full_clone |
... <path> |
挂载后更新,完整克隆所有字段 |
add_sus_map |
add_sus_map <path> |
隐藏该文件的内存映射痕迹 |
add_open_redirect |
add_open_redirect <target> <redirected> <uid_scheme> |
打开重定向(uid_scheme 0~4) |
set_uname |
set_uname <release> <version> |
伪造 uname(填 default 用真实值) |
set_cmdline_or_bootconfig |
... <file> |
从文件读取内容伪造 cmdline/bootconfig |
enable_avc_log_spoofing |
... <0|1> |
SELinux AVC 日志伪造开关 |
enable_log |
enable_log <0|1> |
SUSFS 内核日志开关 |
show |
show version|variant|enabled_features |
查询信息 |
直接运行 ksu_susfs(无参数)会打印完整帮助。编译产物会被推送到 /data/adb/ksu/bin/ksu_susfs,可在 root shell / Termux root / KSU 模块脚本中直接调用。
典型工作流示例(动态伪造一个被 bind mount 替换的系统文件):
# 挂载前:记录原始 stat 并加入隐藏
ksu_susfs add_sus_kstat /system/etc/hosts
# 执行 bind mount 把自定义 hosts 盖上去
mount -o bind "$MODDIR/hosts" /system/etc/hosts
# 挂载后:更新 inode(伪造值仍用原始的)
ksu_susfs update_sus_kstat /system/etc/hosts
9. KernelSU 模块部署流程
模块 ksu_module_susfs 把工具部署到位,并在开机各阶段自动应用规则。
模块信息(module.prop):id=susfs4ksu,name=SUSFS-FOR-KERNELSU,version=v2.2.0。
安装阶段(customize.sh)
- 检查
/data/adb/ksu/bin是否存在(不存在则中止——说明不是 KSU 环境); - 解压模块,将
tools/ksu_susfs_arm64复制为/data/adb/ksu/bin/ksu_susfs并chmod 755; - 仅支持 arm64,其他架构报错退出;
- 清理临时文件。
开机三阶段
| 阶段 | 脚本 | 主要动作 |
|---|---|---|
| post-fs-data | post-fs-data.sh |
各类隐藏配置的演示模板(默认注释,按需启用):sus_kstat、set_uname、open_redirect、cmdline 伪造、ext4 loop/jbd2 隐藏等 |
| service | service.sh |
清理可疑系统属性(如新 Pixel 的 ro.boot.verifiedbooterror);调整挂载隐藏开关 |
| boot-completed | boot-completed.sh |
应用最终隐藏:用 ksud kernel umount 把 KSU 挂载加入卸载列表;add_sus_path 隐藏可疑路径;add_sus_path_loop 处理易变路径;add_sus_map 隐藏映射 |
辅助函数库(utils.sh)
susfs_clone_perm(to, from):克隆权限、属主、SELinux 上下文——bind mount 前确保替身文件与原文件权限一致,避免权限差异被检测。susfs_hexpatch_prop_name(name, cur, new):在二进制属性文件/dev/__properties__里按 hex 直接改属性值(要求新旧等长),绕过 resetprop 限制。susfs_list_full_file_access_for_third_party_apps():列出声明了MANAGE_EXTERNAL_STORAGE权限的第三方 App(这些 App 可能绕过部分隐藏)。
其他文件
sepolicy.rule:为 bind mount 相关操作补充 SELinux 放行规则(netd/zygote 的 search 权限等)。.susfs_no_auto_add_try_umount/susfs_no_auto_add_try_umount:标志文件,控制是否自动把可疑挂载加入卸载列表(用于调试/自定义)。uninstall.sh:卸载时删除/data/adb/ksu/bin/ksu_susfs。META-INF/.../update-binary:遵循 Magisk 模块格式以兼容刷入工具。
10. 编译与安装
编译用户工具
# 需要 NDK(ndk-build 在 PATH 中)
./build_ksu_susfs_tool.sh
# 产物:libs/arm64-v8a/ksu_susfs,并自动复制到 ksu_module_susfs/tools/ksu_susfs_arm64
编译配置(ksu_susfs/jni/Application.mk / Android.mk):仅 arm64-v8a,release,-O3 -flto=thin,-fstack-protector-strong、-fvisibility=hidden,APP_STL := none。
打包 KSU 模块
./build_ksu_module.sh # 产出 ksu_module_susfs.zip
给内核打补丁(GKI,官方 Google 构建流程,摘自 README)
# 在 $KERNEL_REPO 下(common/ 为内核根,KernelSU/ 为 KSU)
cp ./kernel_patches/KernelSU/10_enable_susfs_for_ksu.patch $KERNEL_REPO/KernelSU/
cp ./kernel_patches/50_add_susfs_in_gki-android15-6.6.patch $KERNEL_REPO/common/
cp ./kernel_patches/fs/susfs.c $KERNEL_REPO/common/fs/
cp ./kernel_patches/include/linux/* $KERNEL_REPO/common/include/linux/
cd $KERNEL_REPO/KernelSU && patch -p1 < 10_enable_susfs_for_ksu.patch
cd $KERNEL_REPO/common && patch -p1 < 50_add_susfs_in_gki-android15-6.6.patch # 失败的 hunk 需手工合
# 确保 CONFIG_KSU 与 CONFIG_KSU_SUSFS 已开启,按需用 menuconfig 调子功能
# Android 14+ 从 Google artifacts 构建时需删除 abi_gki_protected_exports_* 文件
重要前提(README):补丁基于带 tag 的官方 KernelSU 和本仓库带 tag / "Bump version to vX.X.X" 提交,版本要对齐才能干净套用。自 v2.0.0 起 SUSFS 不再依赖 KPROBES/KRETPROBES/HAVE_SYSCALL_TRACEPOINTS,全部改用 inline hook(含 sucompat),因此可能与自定义的 sucompat 手动 hook 补丁冲突。
11. 已知限制与注意事项
- 路径选择:强烈不建议把
/sdcard或/storage/emulated/0的子路径加入sus_path——文件管理器走 Android API 时调用 uid 会变成媒体提供者(非 root 受限进程),导致隐藏逻辑误判,可能让文件无法正常显示。 - Magic mount 泄露:若 KSU 管理器用 magic mount,SUSFS 可能捕获不到全部 sus 挂载,需手工
ksu_susfs add_try_umount <泄露挂载路径> 1补卸载。 - sus_map 不是万能的:不隐藏匿名内存、不隐藏注入库自身的 hook,对强检测可能失效。
- open_redirect 的 uid_scheme 2/3/4 影响面大,使用需谨慎,可能影响系统稳定。
- avc_log_spoofing / enable_log 会影响调试,排障时记得关。
- KABI 兼容性:某些内核结构体缺少
android_kabi_reservedX成员会编译失败,需手工补(README "Known Compiler Issues")。 - 仅适配 arm64。
12. 源码观察与潜在问题
通读源码时发现一处疑似缺陷,记录如下供参考:
KSTAT_SPOOF_CTIME_TV_SEC 位定义疑似笔误(kernel_patches/include/linux/susfs.h:71):
#define KSTAT_SPOOF_CTIME_TV_SEC (1 < 8) // ← 疑似应为 (1 << 8)
这里用的是比较运算符 < 而非位移 <<。(1 < 8) 求值为 1,与 KSTAT_SPOOF_INO(1 << 0 = 1)取值相同。后果是:设置"伪造 ctime 秒"标志时,实际操作的是 INO 标志位,ctime 秒可能无法独立伪造。其余时间戳位(如 MTIME_TV_SEC 用的是 1 << 6)写法正常。该位在 susfs_sus_kstat_spoof_generic_fillattr()(susfs.c:542-543)中被使用,受影响。
这属于代码层面的观察,是否在实际版本中已修复,建议以最新上游为准——本仓库当前分支快照中确为此写法。
此外可注意的实现亮点:
- 大量使用 static key / static branch(
jump_label),让未启用的功能在热路径上几乎零成本。 - 并发控制完备:sus_path 用 SRCU + mutex,sus_kstat/open_redirect 用 RCU 哈希表 + mutex,uname/cmdline 用 seqlock(读多写少)。
- sdcard 解密监控用
fsnotify且把清理逻辑延迟到delayed_work,规避在 fsnotify 的 SRCU 上下文里调用fsnotify_destroy_group()造成的自死锁(susfs.c:1356-1361有注释说明)。
13. 目录文件索引
susfs4ksu/
├── README.md # 官方说明(打补丁/编译/已知问题)
├── LICENSE # 许可证(GPL)
├── build_ksu_susfs_tool.sh # 编译用户工具(ndk-build)
├── build_ksu_module.sh # 打包 KSU 模块为 zip
│
├── kernel_patches/ # ① 内核补丁
│ ├── fs/
│ │ └── susfs.c # ★ SUSFS 核心实现(1471 行,完整文件)
│ ├── include/linux/
│ │ ├── susfs.h # 结构体/枚举/函数声明,版本号
│ │ └── susfs_def.h # 命令码、标志位、内联判定函数(用户态共享)
│ ├── 50_add_susfs_in_gki-android15-6.6.patch # 原生内核 hook 补丁(2353 行)
│ └── KernelSU/
│ └── 10_enable_susfs_for_ksu.patch # KSU 集成补丁(2824 行)
│
├── ksu_susfs/ # ② 用户空间工具(NDK 工程)
│ └── jni/
│ ├── main.c # 入口与命令分发
│ ├── susfs_utils.c # 工具函数(stat、数字校验等)
│ ├── includes/ # susfs_defs.h / susfs_utils.h
│ ├── features/ # 各功能子命令实现(10 组 .c/.h)
│ │ ├── sus_path.c sus_mount.c sus_kstat.c sus_map.c
│ │ ├── open_redirect.c spoof_uname.c spoof_cmdline_or_bootconfig.c
│ │ └── avc_log_spoofing.c enable_log.c show.c
│ ├── Android.mk Application.mk # 编译配置(仅 arm64)
│
└── ksu_module_susfs/ # ③ KernelSU 模块
├── module.prop # 模块元信息
├── customize.sh # 安装脚本(部署 ksu_susfs 到 /data/adb/ksu/bin)
├── post-fs-data.sh # 早期阶段(隐藏配置模板)
├── service.sh # service 阶段(清理属性/调挂载开关)
├── boot-completed.sh # 开机完成(应用隐藏规则)
├── utils.sh # 辅助函数(clone_perm / hexpatch_prop 等)
├── uninstall.sh # 卸载清理
├── sepolicy.rule # SELinux 放行规则
├── tools/ksu_susfs_arm64 # 预编译工具二进制
└── META-INF/... # Magisk 兼容安装脚本
14. 版本与致谢
- 当前版本:SUSFS
v2.2.0(susfs.h:12,module.prop) - 分支:
gki-android15-6.6-dev(GKI / Android 15 / Linux 6.6) - 作者/联系:simonpunk(Telegram
@simonpunk)
致谢(README):
- KernelSU:https://github.com/tiann/KernelSU
- KernelSU fork:https://github.com/5ec1cff/KernelSU
- @Kartatz:提供思路与原始 commit
一图总结 SUSFS 的工作时序
开机 → KSU 加载 → susfs_init()
│
├─ 模块脚本调用 ksu_susfs 下发规则
│ └─ reboot(0xDEADBEEF,0xFAFAFAFA,CMD,&info)
│ └─ ksu_handle_sys_reboot() → susfs_add_xxx() → 给 inode 打标 / 存哈希表
│
└─ zygote fork App → setresuid()
└─ ksu_handle_setresuid()
├─ 普通 App?→ ksu_handle_umount()(卸载 root 挂载)
│ + susfs_set_current_proc_umounted()(打 TIF_PROC_UMOUNTED)
│ + 调度 susfs_extra_works(补打 sus_path)
└─ 此后该 App 的每次 stat/open/readdir/读 /proc
└─ 内核 hook 调 susfs_is_inode_sus_path()/spoof_* → 隐藏或伪造
(而 root/su 进程一律看到真实信息)
本文档基于仓库
gki-android15-6.6-dev分支当前快照通读源码整理,引用的行号以该快照为准。
评论
- 还没有评论,来说点什么吧。