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

SUSFS:KernelSU 的 root 隐藏增强

2026-06-30 · 阅读 2

解读对象:susfs4ksu,分支 gki-android15-6.6-dev,版本 v2.2.0 适用内核:GKI 内核 / Android 15 / Linux 6.6 文档定位:面向想要理解 SUSFS 工作原理、架构与实现细节的开发者与安全研究者


目录

  1. 项目是什么
  2. 背景:Root 检测与隐藏的攻防
  3. 整体架构
  4. 用户态 ↔ 内核通信机制
  5. 核心隐藏机制(理论基础)
  6. 功能模块详解
  7. 内核 Hook 点全景
  8. 用户空间工具 ksu_susfs 命令参考
  9. KernelSU 模块部署流程
  10. 编译与安装
  11. 已知限制与注意事项
  12. 源码观察与潜在问题
  13. 目录文件索引
  14. 版本与致谢

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/ 自动化部署与配置

内核补丁的三个文件

  1. kernel_patches/fs/susfs.c(1471 行) + 头文件 include/linux/susfs.hinclude/linux/susfs_def.h —— SUSFS 的核心逻辑。这三个是完整文件,直接复制进内核源码树(不是 diff)。
  2. kernel_patches/50_add_susfs_in_gki-android15-6.6.patch(2353 行) —— 对原生 Linux 内核源码的修改(diff),在几十个 VFS/proc/selinux 函数里插入对 susfs_*() 的调用。
  3. 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):

  1. 下发:susfs_add_sus_path() 给目标 inode 的 i_mapping->flagsAS_FLAGS_SUS_PATH 位。
  2. 判定:susfs_is_inode_sus_path() 在路径查找的多个内核函数里被调用。命中条件是 进程是 umounted App + inode 带标记 + inode 的属主 uid ≠ 当前进程 uidis_i_uid_not_allowedsusfs.c:174-176)。最后一条很关键:它让 App 仍能看到自己拥有的文件,只隐藏"不属于自己"的可疑文件。
  3. 循环补打: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.cshow_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_procs0x55561):控制是否对非 su 进程隐藏挂载。默认 false——这样 Zygisk 类方案能在 post-fs-data 阶段先正常读到挂载,避免缓存问题(susfs.c:241-243 有详细注释)。

6.3 sus_kstat —— 文件元数据伪造

目标:当系统文件被 bind mount 替换后,其 stat() 信息(inode 号、设备号、大小、时间戳、块数等)会变化而暴露篡改。sus_kstat 让这些字段返回"原始值"。

数据结构:用哈希表 SUS_KSTAT_HLISTsusfs.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_statically0x55572):直接把目标 stat 各字段全部手工指定(用 stat -c 取值,填 default 表示该字段不伪造)。适合不需要真正挂载、只想改 stat 表现的场景。
  • 动态两步法 add_sus_kstat0x55570)+ update_sus_kstat0x55571):挂载 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_devhuge_decode_dev / old_decode_dev 编码转换(susfs.c:336-344),以匹配用户态 stat 看到的设备号格式。

6.4 sus_map —— 内存映射隐藏

目标:隐藏被 mmap 的真实文件路径,使其不出现在 /proc/self/mapssmapssmaps_rollupmap_filespagemap 等中——主要用于隐藏注入到 App 的 .so(如 Zygisk 模块)。

实现susfs.c:1096-1129):susfs_add_sus_map() 给目标 inode 打 AS_FLAGS_SUS_MAP 标记。判定宏 SUSFS_IS_INODE_SUS_MAPsusfs_def.h:109-112)要求 inode 带标记 + 是 umounted App。内核 fs/proc/task_mmu.cshow_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_unameseqlock 保护,读多写少场景高效)。填 default 表示用真实值。susfs_spoof_uname() 在内核 uname 系统调用路径(kernel/sys.coverride_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.cavc_audit_post_callback

注意:启用会干扰开发者排查 SELinux 问题,调试时建议关闭。

6.9 enable_log —— 内核日志开关

控制 SUSFS 自身的内核日志输出(SUSFS_LOGI/SUSFS_LOGEsusfs.c:31-38)。用 static key susfs_is_log_enabled 实现,关闭时近乎零开销。需内核开启 CONFIG_KSU_SUSFS_ENABLE_LOG。生产环境建议关闭,调试时打开。

6.10 show —— 信息查询

  • show version0x555e1):返回 SUSFS 版本(SUSFS_VERSION = "v2.2.0"susfs.h:12)。
  • show variant0x555e3):返回 GKINON-GKI(按内核版本 ≥5.0.0 判定,susfs.h:13-17)。
  • show enabled_features0x555e2):返回内核实际编译进了哪些 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_dcachelookup_fast__lookup_slowwalk_componentlookup_openvfs_readlink sus_path、open_redirect
fs/stat.c generic_fillattrvfs_getattr_nosecvfs_fstatvfs_statx sus_kstat
fs/readdir.c filldirfilldir64fillonedir(含 compat 变体) sus_path(列目录时隐藏条目)
fs/namespace.c mnt_alloc_id/group_idmnt_free_idvfs_create_mountclone_mntcopy_mnt_ns sus_mount
fs/proc_namespace.c show_vfsmntshow_mountinfoshow_vfsstat sus_mount(挂载显示过滤)
fs/open.c do_sys_openat2do_faccessatfile_open_root open_redirect、sus_path
fs/proc/task_mmu.c show_map_vmashow_smapshow_smaps_rolluppagemap_read sus_map、sus_kstat
fs/proc/base.c proc_pid_get_linkdo_proc_readlinkproc_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_execvedo_execveat_common 执行路径相关
kernel/sys.c __sys_setresuidoverride_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/Kbuildkernel/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_domainsusfs_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=susfs4ksuname=SUSFS-FOR-KERNELSUversion=v2.2.0

安装阶段(customize.sh

  1. 检查 /data/adb/ksu/bin 是否存在(不存在则中止——说明不是 KSU 环境);
  2. 解压模块,将 tools/ksu_susfs_arm64 复制为 /data/adb/ksu/bin/ksu_susfschmod 755
  3. 仅支持 arm64,其他架构报错退出;
  4. 清理临时文件。

开机三阶段

阶段 脚本 主要动作
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=hiddenAPP_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_INO1 << 0 = 1取值相同。后果是:设置"伪造 ctime 秒"标志时,实际操作的是 INO 标志位,ctime 秒可能无法独立伪造。其余时间戳位(如 MTIME_TV_SEC 用的是 1 << 6)写法正常。该位在 susfs_sus_kstat_spoof_generic_fillattr()susfs.c:542-543)中被使用,受影响。

这属于代码层面的观察,是否在实际版本中已修复,建议以最新上游为准——本仓库当前分支快照中确为此写法。

此外可注意的实现亮点:

  • 大量使用 static key / static branchjump_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.0susfs.h:12module.prop
  • 分支gki-android15-6.6-dev(GKI / Android 15 / Linux 6.6)
  • 作者/联系:simonpunk(Telegram @simonpunk

致谢(README):


一图总结 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 分支当前快照通读源码整理,引用的行号以该快照为准。

评论

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

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