溺的文档
KernelPatch · 第 19 篇 / 共 22 篇

KernelPatch Android SELinux 详解

2026-06-29 · 阅读 1

目录

  1. 概述
  2. SELinux 基础知识
  3. KernelPatch SELinux 架构
  4. 核心机制详解
  5. 工作流程
  6. 代码实现深度解析
  7. Android 特定功能
  8. 使用场景与示例
  9. 安全考虑

概述

KernelPatch 在 Android 平台上实现了一套完整的 SELinux 绕过机制,允许授权进程透明地绕过 SELinux 访问控制,而无需修改 SELinux 策略或将其设置为 Permissive 模式。这套机制通过 Hook 内核关键函数,实现了细粒度的访问控制绕过。

核心特性

  • 透明绕过:对授权进程完全透明,无需修改应用代码
  • 细粒度控制:基于进程级别的绕过,不影响其他进程
  • 上下文支持:支持 SELinux 上下文切换和管理
  • 策略修复:自动修复 Android 13+ 策略标志问题
  • SU 集成:与 SU 权限管理系统无缝集成

相关文件

kernel/patch/
├── common/
│   ├── accctl.c          # 访问控制核心实现
│   ├── taskob.c          # 任务扩展管理
│   └── sucompat.c        # SU 兼容层
├── android/
│   └── sepolicy_flags.c  # SELinux 策略标志修复
├── include/
│   ├── accctl.h          # 访问控制接口
│   ├── taskext.h         # 任务扩展结构
│   └── sepolicy_flags.h  # 策略标志定义
└── patch.c               # 初始化流程

SELinux 基础知识

SELinux 基本概念

SELinux(Security-Enhanced Linux)是一个基于强制访问控制(MAC)的安全系统:

  1. 安全上下文(Security Context)

    • 格式:user:role:type:level
    • 例如:u:r:system_app:s0
    • 每个进程和对象都有上下文
  2. SID(Security ID)

    • 上下文在内存中的整数标识符
    • 用于提高性能,避免字符串比较
  3. AVC(Access Vector Cache)

    • SELinux 的访问控制决策缓存
    • 加速权限检查
  4. 访问决策流程

用户操作
  ↓
系统调用
  ↓
SELinux Hook
  ↓
avc_has_perm() / avc_has_perm_noaudit()
  ↓
avc_denied() - 检查是否拒绝
  ↓
slow_avc_audit() - 审计拒绝操作
  ↓
返回权限决策

关键函数

  • avc_denied(): 检查访问是否被拒绝
  • slow_avc_audit(): 记录被拒绝的访问
  • avc_has_perm(): 完整的权限检查(包含审计)
  • avc_has_perm_noaudit(): 权限检查(不审计)

KernelPatch SELinux 架构

整体架构图

┌─────────────────────────────────────────────────────────┐
│                   用户空间应用                            │
│  (需要绕过 SELinux 的进程,如 root shell)                 │
└──────────────────┬──────────────────────────────────────┘
                   │ 系统调用
                   ↓
┌─────────────────────────────────────────────────────────┐
│                  内核空间                                 │
│                                                          │
│  ┌────────────────────────────────────────────┐         │
│  │          SELinux 访问控制                  │         │
│  │                                            │         │
│  │  avc_has_perm()                           │         │
│  │       ↓                                    │         │
│  │  avc_denied()  ←── Hook ──┐               │         │
│  │       ↓                    │               │         │
│  │  slow_avc_audit() ←── Hook │               │         │
│  └────────────────────────────┼───────────────┘         │
│                               │                          │
│  ┌────────────────────────────▼───────────────┐         │
│  │      KernelPatch Hook 层                   │         │
│  │                                            │         │
│  │  avc_denied_replace()                      │         │
│  │  ┌──────────────────────────────────┐     │         │
│  │  │ 1. 检查 all_allow_sid            │     │         │
│  │  │ 2. 检查 task_ext->sel_allow      │     │         │
│  │  │ 3. 检查 task_ext->priv_sel_allow │     │         │
│  │  │ 4. 如果匹配,直接允许并返回       │     │         │
│  │  └──────────────────────────────────┘     │         │
│  │                                            │         │
│  │  slow_avc_audit_replace()                  │         │
│  │  ┌──────────────────────────────────┐     │         │
│  │  │ 如果是豁免进程,跳过审计记录     │     │         │
│  │  └──────────────────────────────────┘     │         │
│  └────────────────────────────────────────────┘         │
│                                                          │
│  ┌────────────────────────────────────────────┐         │
│  │          任务扩展 (task_ext)               │         │
│  │                                            │         │
│  │  struct task_ext {                        │         │
│  │      bool sel_allow;       // SU 时设置   │         │
│  │      bool priv_sel_allow;  // 外部设置    │         │
│  │      ...                                  │         │
│  │  }                                        │         │
│  └────────────────────────────────────────────┘         │
└──────────────────────────────────────────────────────────┘

核心组件

  1. Hook 机制

    • Hook avc_denied()slow_avc_audit()
    • 在访问控制决策前拦截
  2. 任务扩展 (task_ext)

    • 附加到每个进程的扩展数据
    • 存储 SELinux 绕过标志
  3. 全局豁免上下文

    • all_allow_sctx: 全局豁免的 SELinux 上下文
    • 所有使用该上下文的进程自动绕过
  4. 策略修复

    • 修复 Android 13+ 策略标志
    • 确保网络功能正常

核心机制详解

1. 任务扩展 (task_ext)

task_ext 是附加到每个 task_struct 的扩展数据结构,存储在进程的内核栈上。

// kernel/patch/include/taskext.h:30-41
struct task_ext
{
    int size;
    pid_t pid;
    pid_t tgid;
    bool root;
    bool sel_allow;        // SU 时的 SELinux 绕过标志
    bool priv_sel_allow;   // 外部设置的 SELinux 绕过标志
    int _magic;            // 校验标记
};

关键字段说明

  • sel_allow: 当进程通过 commit_su() 获取 root 权限时自动设置
  • priv_sel_allow: 可通过外部 API 设置,用于特殊场景
  • _magic: 用于验证 task_ext 的有效性

获取 task_ext

// kernel/patch/common/taskob.c
struct task_ext *get_task_ext(struct task_struct *task)
{
    // 从 task_struct 末尾获取扩展数据
    return (struct task_ext *)((uintptr_t)task + task_ext_offset);
}

初始化流程

// kernel/patch/common/taskob.c:59-64
static void after_copy_process(hook_fargs8_t *args, void *udata)
{
    struct task_struct *new = (struct task_struct *)args->ret;
    if (unlikely(!new || IS_ERR(new))) return;
    prepare_task_ext(new, current);  // 新进程继承父进程的 sel_allow
}

2. avc_denied Hook

avc_denied() 是 SELinux 访问控制的核心函数,KernelPatch 通过 Hook 它在权限被拒绝前进行拦截。

// kernel/patch/common/accctl.c:176-206
static int avc_denied_replace(struct selinux_state *_state, 
                               void *_ssid, void *_tsid, 
                               void *_tclass, void *_requested,
                               void *_driver, void *_xperm, 
                               void *_flags, 
                               struct av_decision *_avd)
{
    // ========== 检查全局豁免上下文 ==========
    if (all_allow_sid != SECSID_NULL) {
        u32 ssid = (u32)(u64)_ssid;
        // 兼容不同内核版本的参数传递方式
        if ((uint64_t)_state <= 0xffffffffL) {
            ssid = (u32)(u64)_state;
        }
        // 如果源 SID 匹配全局豁免 SID,直接允许
        if (ssid == all_allow_sid) {
            goto allow;
        }
    }

    // ========== 检查任务级别的豁免 ==========
    struct task_ext *ext = get_current_task_ext();
    if (unlikely(task_ext_valid(ext) && 
                 (ext->sel_allow || ext->priv_sel_allow))) {
        goto allow;  // 进程被标记为豁免,直接允许
    }

    // ========== 正常 SELinux 检查 ==========
    int rc = avc_denied_backup(_state, _ssid, _tsid, _tclass, 
                                _requested, _driver, _xperm, 
                                _flags, _avd);
    return rc;

allow:
    // ========== 强制允许访问 ==========
    struct av_decision *avd = (struct av_decision *)_avd;
    // 兼容不同内核版本
    if ((uint64_t)_state <= 0xffffffffL) {
        avd = (struct av_decision *)_flags;
    }
    // 设置允许所有权限
    avd->allowed = 0xffffffff;
    avd->auditallow = 0;
    avd->auditdeny = 0;
    return 0;  // 返回 0 表示允许
}

工作流程

  1. 全局豁免检查:如果设置了 all_allow_sid,检查源 SID 是否匹配
  2. 任务豁免检查:检查当前进程的 task_ext 是否标记为豁免
  3. 正常流程:如果都不匹配,调用原始的 avc_denied() 进行正常检查
  4. 强制允许:如果匹配豁免条件,设置 avd->allowed = 0xffffffff 允许所有权限

3. slow_avc_audit Hook

slow_avc_audit() 用于记录被拒绝的访问。Hook 它以防止豁免进程产生审计日志。

// kernel/patch/common/accctl.c:212-233
static int slow_avc_audit_replace(struct selinux_state *_state,
                                  void *_ssid, void *_tsid,
                                  void *_tclass, void *_requested,
                                  void *_audited, void *_denied,
                                  void *_result,
                                  struct common_audit_data *_a)
{
    // ========== 检查全局豁免上下文 ==========
    if (all_allow_sid != SECSID_NULL) {
        u32 ssid = (u64)_ssid;
        if ((uint64_t)_state <= 0xffffffffL) {
            ssid = (u64)_state;
        }
        if (ssid == all_allow_sid) {
            return 0;  // 跳过审计
        }
    }

    // ========== 检查任务级别的豁免 ==========
    struct task_ext *ext = get_current_task_ext();
    if (unlikely(task_ext_valid(ext) && 
                 (ext->sel_allow || ext->priv_sel_allow))) {
        return 0;  // 跳过审计
    }

    // ========== 正常审计流程 ==========
    int rc = slow_avc_audit_backup(_state, _ssid, _tsid, _tclass,
                                    _requested, _audited, _denied,
                                    _result, _a);
    return rc;
}

作用

  • 防止豁免进程的操作产生 SELinux 审计日志
  • 保持日志的清洁,只记录真正的安全事件

4. Hook 安装

Hook 在 bypass_selinux() 函数中安装,该函数在 before_rest_init() 中被调用。

// kernel/patch/common/accctl.c:235-255
int bypass_selinux()
{
    // ========== Hook avc_denied ==========
    unsigned long avc_denied_addr = patch_config->avc_denied;
    if (avc_denied_addr) {
        hook_err_t err = hook((void *)avc_denied_addr, 
                              (void *)avc_denied_replace, 
                              (void **)&avc_denied_backup);
        if (err != HOOK_NO_ERR) {
            log_boot("hook avc_denied_addr: %llx, error: %d\n", 
                     avc_denied_addr, err);
        }
    }

    // ========== Hook slow_avc_audit ==========
    unsigned long slow_avc_audit_addr = patch_config->slow_avc_audit;
    if (slow_avc_audit_addr) {
        hook_err_t err = hook((void *)slow_avc_audit_addr,
                              (void *)slow_avc_audit_replace, 
                              (void **)&slow_avc_audit_backup);
        if (err != HOOK_NO_ERR) {
            log_boot("hook slow_avc_audit: %llx, error: %d\n", 
                     slow_avc_audit_addr, err);
        }
    }

    return 0;
}

初始化顺序

// kernel/patch/patch.c:60-99
static void before_rest_init(hook_fargs4_t *args, void *udata)
{
    // ... 其他初始化 ...
    
    if ((rc = bypass_selinux())) goto out;  // 第 3 步:安装 SELinux Hook
    log_boot("bypass_selinux done: %d\n", rc);
    
    if ((rc = task_observer())) goto out;   // 第 4 步:初始化任务扩展
    log_boot("task_observer done: %d\n", rc);
    
    // ... 其他初始化 ...
}

工作流程

完整工作流程图

┌─────────────────────────────────────────────────────────────┐
│  阶段 1: 内核启动阶段                                        │
└─────────────────────────────────────────────────────────────┘

start() → patch() → hook rest_init
                          ↓
┌─────────────────────────────────────────────────────────────┐
│  阶段 2: rest_init Hook 触发                                │
└─────────────────────────────────────────────────────────────┘

before_rest_init()
  ├─ bypass_kcfi()        # 绕过 KCFI 保护
  ├─ resolve_struct()     # 解析内核结构体
  ├─ bypass_selinux()     # ⭐ 安装 SELinux Hook
  │     ├─ hook(avc_denied) → avc_denied_replace
  │     └─ hook(slow_avc_audit) → slow_avc_audit_replace
  ├─ task_observer()      # ⭐ 初始化任务扩展
  │     ├─ prepare_init_ext(init_task)
  │     └─ hook(copy_process) → after_copy_process
  └─ ... 其他初始化 ...

┌─────────────────────────────────────────────────────────────┐
│  阶段 3: 运行时 - SU 权限获取                                │
└─────────────────────────────────────────────────────────────┘

用户进程调用 su 或执行 SU 命令
  ↓
execve("/system/bin/su")
  ↓
Hook execve → before_execve()
  ↓
检测到 su 路径 + UID 在白名单
  ↓
commit_su(to_uid, scontext)
  ├─ su_cred(new, to_uid)      # 设置 UID 和 Capabilities
  ├─ set_security_override_from_ctx(new, scontext)  # 设置 SELinux 上下文
  ├─ ext->sel_allow = 1        # ⭐ 设置 SELinux 绕过标志
  └─ commit_creds(new)         # 提交新的凭证

┌─────────────────────────────────────────────────────────────┐
│  阶段 4: 运行时 - 访问控制决策                               │
└─────────────────────────────────────────────────────────────┘

进程尝试访问受保护资源
  ↓
系统调用进入内核
  ↓
SELinux Hook 检查权限
  ↓
avc_has_perm() / avc_has_perm_noaudit()
  ↓
avc_denied() → avc_denied_replace() ⭐
  ├─ 检查 all_allow_sid (全局豁免)
  │     └─ 匹配 → 直接允许
  ├─ 检查 task_ext->sel_allow
  │     └─ 设置 → 直接允许
  └─ 都不匹配 → 调用 avc_denied_backup() (正常 SELinux 检查)

如果访问被拒绝
  ↓
slow_avc_audit() → slow_avc_audit_replace() ⭐
  ├─ 检查豁免条件
  │     └─ 匹配 → 跳过审计 (return 0)
  └─ 不匹配 → 记录审计日志

详细步骤说明

步骤 1: Hook 安装时机

Hook 在 rest_init 执行前安装,此时:

  • ✅ 内核符号表已可用
  • ✅ 大部分内核子系统已初始化
  • ✅ 但用户空间进程还未启动

这是安装 Hook 的最佳时机。

步骤 2: 任务扩展初始化

当新进程创建时(通过 copy_processcgroup_post_fork),task_ext 会被初始化:

// kernel/patch/common/taskob.c:35-54
static void prepare_task_ext(struct task_struct *new, 
                             struct task_struct *old)
{
    // 初始化新的 task_ext
    struct task_ext *new_ext = get_task_ext(new);
    memset(new_ext, 0, sizeof(struct task_ext));
    new_ext->size = task_ext_size;
    new_ext->_magic = TASK_EXT_MAGIC;
    
    // 继承父进程的 sel_allow 标志
    struct task_ext *old_ext = get_task_ext(old);
    new_ext->sel_allow = old_ext->sel_allow;
    
    // 设置进程信息
    new_ext->pid = __task_pid_nr_ns(new, PIDTYPE_PID, 0);
    new_ext->tgid = __task_pid_nr_ns(new, PIDTYPE_TGID, 0);
}

重要特性:子进程会继承父进程的 sel_allow 标志,这意味着如果父进程有 root 权限,子进程也会自动绕过 SELinux。

步骤 3: SU 权限获取

当用户执行 SU 命令时:

// kernel/patch/common/accctl.c:78-116
int commit_common_su(uid_t to_uid, const char *sctx)
{
    struct task_struct *task = current;
    struct task_ext *ext = get_task_ext(task);
    
    // 1. 禁用 Seccomp
    struct thread_info *thi = current_thread_info();
    thi->flags &= ~(_TIF_SECCOMP);
    
    // 2. 设置新的凭证
    struct cred *new = prepare_creds();
    su_cred(new, to_uid);  // 设置 UID 和 Capabilities
    
    // 3. ⭐ 设置 SELinux 上下文并标记绕过
    if (sctx && sctx[0]) {
        ext->sel_allow = !!set_security_override_from_ctx(new, sctx);
    }
    
    // 4. 提交新凭证
    commit_creds(new);
    
    return 0;
}

关键操作

  1. 禁用 Seccomp(防止被拦截)
  2. 提升 UID 和 Capabilities
  3. 切换 SELinux 上下文
  4. 设置 sel_allow 标志

步骤 4: 访问控制决策

当进程尝试访问受保护资源时,SELinux 会调用访问控制函数:

进程操作
  ↓
系统调用(如 open、read、write)
  ↓
SELinux Hook(security_file_open 等)
  ↓
avc_has_perm() / avc_has_perm_noaudit()
  ├─ 查找 AVC 缓存
  ├─ 如果未命中,调用 security_compute_av()
  └─ 调用 avc_denied() 检查是否拒绝
       ↓
      avc_denied_replace() ⭐
       ├─ 检查豁免条件
       │     ├─ all_allow_sid 匹配?
       │     ├─ task_ext->sel_allow 设置?
       │     └─ task_ext->priv_sel_allow 设置?
       │
       ├─ 如果匹配 → 强制允许
       │     avd->allowed = 0xffffffff
       │     return 0
       │
       └─ 如果不匹配 → 正常检查
            return avc_denied_backup(...)

代码实现深度解析

1. 全局豁免上下文

全局豁免上下文允许所有使用特定 SELinux 上下文的进程自动绕过 SELinux。

// kernel/patch/common/accctl.c:29-30
char all_allow_sctx[SUPERCALL_SCONTEXT_LEN] = { '\0' };
uint32_t all_allow_sid = SECSID_NULL;

设置全局豁免上下文

// kernel/patch/common/accctl.c:51-69
int set_all_allow_sctx(const char *sctx)
{
    // 清除豁免
    if (!sctx || !sctx[0]) {
        all_allow_sctx[0] = 0;
        all_allow_sid = SECSID_NULL;
        dsb(ish);  // 内存屏障,确保其他 CPU 看到更改
        return 0;
    }
    
    // 转换上下文字符串为 SID
    int rc = security_secctx_to_secid(sctx, strlen(sctx), &all_allow_sid);
    if (!rc && all_allow_sid != SECSID_NULL) {
        strncpy(all_allow_sctx, sctx, sizeof(all_allow_sctx) - 1);
        all_allow_sctx[sizeof(all_allow_sctx) - 1] = '\0';
        dsb(ish);  // 内存屏障
        logkfd("set all allow sconetxt: %s, sid: %d\n", 
               all_allow_sctx, all_allow_sid);
    }
    
    return rc;
}

使用场景

# 设置全局豁免上下文
echo 'u:r:su:s0' > /sys/kernel/kp/all_allow_sctx

# 现在所有切换到 u:r:su:s0 的进程都会自动绕过 SELinux

优势

  • 无需为每个进程单独设置
  • 基于 SELinux 上下文的统一管理
  • 与 Android 的 SELinux 管理兼容

2. SU 权限获取流程

完整的 SU 权限获取流程:

// kernel/patch/common/accctl.c:118-125
int commit_su(uid_t to_uid, const char *sctx)
{
    // 如果设置了全局豁免且切换到 root,使用快速路径
    if (all_allow_sid != SECSID_NULL && !to_uid) {
        return commit_kernel_su();
    } else {
        return commit_common_su(to_uid, sctx);
    }
}

commit_kernel_su() - 快速路径

// kernel/patch/common/accctl.c:71-76
int commit_kernel_su()
{
    struct cred *new = prepare_kernel_cred(0);  // 创建 root 凭证
    set_security_override(new, all_allow_sid);  // 设置全局豁免 SID
    return commit_creds(new);                   // 提交凭证
}

commit_common_su() - 通用路径

// kernel/patch/common/accctl.c:78-116
int commit_common_su(uid_t to_uid, const char *sctx)
{
    struct task_ext *ext = get_task_ext(current);
    
    // 1. 禁用 Seccomp
    struct thread_info *thi = current_thread_info();
    thi->flags &= ~(_TIF_SECCOMP);
    
    // 2. 准备新凭证
    struct cred *new = prepare_creds();
    
    // 3. 设置 UID 和 Capabilities
    su_cred(new, to_uid);
    
    // 4. 设置 SELinux 上下文
    if (sctx && sctx[0]) {
        ext->sel_allow = !!set_security_override_from_ctx(new, sctx);
    }
    
    // 5. 提交凭证
    commit_creds(new);
    
    return 0;
}

su_cred() - 设置凭证

// kernel/patch/common/accctl.c:32-49
static void su_cred(struct cred *cred, uid_t uid)
{
    // 设置所有 Capabilities 为最大权限
    *(kernel_cap_t *)((uintptr_t)cred + cred_offset.cap_inheritable_offset) = full_cap;
    *(kernel_cap_t *)((uintptr_t)cred + cred_offset.cap_permitted_offset) = full_cap;
    *(kernel_cap_t *)((uintptr_t)cred + cred_offset.cap_effective_offset) = full_cap;
    *(kernel_cap_t *)((uintptr_t)cred + cred_offset.cap_bset_offset) = full_cap;
    *(kernel_cap_t *)((uintptr_t)cred + cred_offset.cap_ambient_offset) = full_cap;
    
    // 设置所有 UID/GID 为指定值
    *(uid_t *)((uintptr_t)cred + cred_offset.uid_offset) = uid;
    *(uid_t *)((uintptr_t)cred + cred_offset.euid_offset) = uid;
    *(uid_t *)((uintptr_t)cred + cred_offset.fsuid_offset) = uid;
    *(uid_t *)((uintptr_t)cred + cred_offset.suid_offset) = uid;
    
    *(uid_t *)((uintptr_t)cred + cred_offset.gid_offset) = uid;
    *(uid_t *)((uintptr_t)cred + cred_offset.egid_offset) = uid;
    *(uid_t *)((uintptr_t)cred + cred_offset.fsgid_offset) = uid;
    *(uid_t *)((uintptr_t)cred + cred_offset.sgid_offset) = uid;
}

3. 外部进程权限提升

task_su() 允许外部提升指定进程的权限(通过 PID):

// kernel/patch/common/accctl.c:128-171
int task_su(pid_t pid, uid_t to_uid, const char *sctx)
{
    // 1. 查找目标进程
    struct task_struct *task = find_get_task_by_vpid(pid);
    if (!task) {
        return -ESRCH;
    }
    
    struct task_ext *ext = get_task_ext(task);
    
    // 2. 禁用 Seccomp
    struct thread_info *thi = get_task_thread_info(task);
    thi->flags &= ~(_TIF_SECCOMP);
    
    // 3. 直接修改进程的凭证(危险操作)
    struct cred *cred = *(struct cred **)((uintptr_t)task + 
                                          task_struct_offset.cred_offset);
    su_cred(cred, to_uid);
    
    // 4. 设置 SELinux 上下文
    int scontext_changed = 0;
    if (sctx && sctx[0]) {
        scontext_changed = !set_security_override_from_ctx(cred, sctx);
    }
    
    // 5. 同时修改 real_cred(如果存在)
    struct cred *real_cred = *(struct cred **)((uintptr_t)task + 
                                               task_struct_offset.real_cred_offset);
    if (cred != real_cred) {
        su_cred(real_cred, to_uid);
        if (sctx && sctx[0]) {
            scontext_changed = scontext_changed && 
                !set_security_override_from_ctx(real_cred, sctx);
        }
    }
    
    // 6. 设置私有 SELinux 绕过标志
    ext->priv_sel_allow = !scontext_changed;
    
    return 0;
}

与 commit_su() 的区别

特性 commit_su() task_su()
目标 当前进程 指定进程(通过 PID)
方式 创建新凭证并提交 直接修改现有凭证
标志 sel_allow priv_sel_allow
使用场景 SU 命令 外部权限管理

Android 特定功能

SELinux 策略标志修复

Android 13+ 引入了新的 SELinux 策略标志,如果策略文件缺少这些标志,某些网络功能会失败。KernelPatch 通过 Hook policydb_write() 自动修复。

// kernel/patch/android/sepolicy_flags.c:50-64
int android_sepolicy_flags_fix()
{
    // 查找 policydb_write 函数地址
    unsigned long policydb_write_addr = kallsyms_lookup_name("policydb_write");
    
    if (likely(policydb_write_addr)) {
        // Hook policydb_write,在策略写入时修复标志
        hook_err_t err = hook_wrap2((void *)policydb_write_addr, 
                                    before_policydb_write, 
                                    after_policydb_write, 0);
        
        if (unlikely(err != HOOK_NO_ERR)) {
            log_boot("hook policydb_write_addr: %llx, error: %d\n", 
                     policydb_write_addr, err);
            return -1;
        }
    }
    
    return 0;
}

Hook 实现

// kernel/patch/android/sepolicy_flags.c:25-48
static void before_policydb_write(hook_fargs2_t *args, void *udata)
{
    // 保存策略文件数据指针
    struct _policy_file *fp = (struct _policy_file *)args->arg1;
    args->local.data0 = (uint64_t)fp->data;
}

static void after_policydb_write(hook_fargs2_t *args, void *udata)
{
    struct _policydb *p = (struct _policydb *)args->arg0;
    char *data = (char *)args->local.data0;
    
    if (!args->ret) {  // 写入成功
        // 计算配置标志的偏移
        __le32 *config = (__le32 *)(data + POLICYDB_CONFIG_OFFSET);
        __le32 before_config = *config;
        
        // 检查标志是否已存在
        bool android_netlink_route_exists = 
            before_config & POLICYDB_CONFIG_ANDROID_NETLINK_ROUTE;
        bool android_netlink_getneigh_exists = 
            before_config & POLICYDB_CONFIG_ANDROID_NETLINK_GETNEIGH;
        
        // 如果需要但不存在,添加标志
        if (p->android_netlink_route == 1 && !android_netlink_route_exists) {
            *config |= POLICYDB_CONFIG_ANDROID_NETLINK_ROUTE;
        }
        if (p->android_netlink_getneigh == 1 && !android_netlink_getneigh_exists) {
            *config |= POLICYDB_CONFIG_ANDROID_NETLINK_GETNEIGH;
        }
    }
}

策略标志定义

// kernel/patch/include/sepolicy_flags.h:16-18
#define POLICYDB_CONFIG_ANDROID_NETLINK_ROUTE (1 << 31)
#define POLICYDB_CONFIG_ANDROID_NETLINK_GETNEIGH (1 << 30)

配置偏移计算

// kernel/patch/include/sepolicy_flags.h:20-25
/*
 * config offset:
 *   __le32(POLICYDB_MAGIC) + __le32(POLICYDB_STRING_LEN) +
 *   char[POLICYDB_STRING_LEN] + __le32(policyvers)
 */
#define POLICYDB_CONFIG_OFFSET \
    (2 * sizeof(__le32) + strlen(POLICYDB_STRING) + sizeof(__le32))

问题背景

Android 13+ 引入了两个新的策略标志:

  • POLICYDB_CONFIG_ANDROID_NETLINK_ROUTE: 允许 netlink 路由操作
  • POLICYDB_CONFIG_ANDROID_NETLINK_GETNEIGH: 允许获取邻居表

如果策略文件在编译时没有包含这些标志,即使策略允许,网络功能也会失败。KernelPatch 在策略加载时自动添加缺失的标志,确保功能正常。

SU 兼容层

Android 上的 SU 兼容层允许传统的 SU 命令正常工作:

// kernel/patch/common/sucompat.c:358-407
int su_compat_init()
{
    current_su_path = default_su_path;
    
    // 初始化 UID 白名单存储
    su_kstorage_gid = try_alloc_kstroage_group();
    exclude_kstorage_gid = try_alloc_kstroage_group();
    
#ifdef ANDROID
    // Android 默认配置
    if (!all_allow_sctx[0]) {
        strcpy(all_allow_sctx, ALL_ALLOW_SCONTEXT_MAGISK);
    }
    // 默认允许 shell 和 root
    su_add_allow_uid(2000, 0, all_allow_sctx);  // shell
    su_add_allow_uid(0, 0, all_allow_sctx);     // root
#endif
    
    // Hook execve 系统调用
    hook_syscalln(__NR_execve, 3, before_execve, 0, (void *)0);
    
    // Hook 文件系统调用,用于检测 su 文件访问
    hook_syscalln(__NR3264_fstatat, 4, su_handler_arg1_ufilename_before, 0, (void *)0);
    hook_syscalln(__NR_faccessat, 3, su_handler_arg1_ufilename_before, 0, (void *)0);
    
    return 0;
}

execve Hook

// kernel/patch/common/sucompat.c:266-271
static void before_execve(hook_fargs3_t *args, void *udata)
{
    void *arg0p = syscall_argn_p(args, 0);  // 文件名参数
    void *arg1p = syscall_argn_p(args, 1);  // argv 参数
    handle_before_execve((char **)arg0p, (char **)arg1p, udata);
}

SU 检测和处理

// kernel/patch/common/sucompat.c:191-255
static void handle_before_execve(char **__user u_filename_p, 
                                  char **__user uargv, 
                                  void *udata)
{
    char filename[SU_PATH_MAX_LEN];
    int flen = compat_strncpy_from_user(*u_filename_p, filename, sizeof(filename));
    if (flen <= 0) return;
    
    // 检测是否是 SU 路径
    if (!strcmp(current_su_path, filename)) {
        uid_t uid = current_uid();
        struct su_profile profile;
        
        // 检查 UID 是否在白名单中
        if (su_allow_uid_profile(0, uid, &profile)) return;
        
        // 获取 SU 配置
        uid_t to_uid = profile.to_uid;
        const char *sctx = profile.scontext;
        
        // 执行权限提升
        commit_su(to_uid, sctx);
        
        // 替换执行的命令(通常是 shell)
        void *uptr = copy_to_user_stack(sh_path, sizeof(sh_path));
        if (uptr && !IS_ERR(uptr)) {
            *u_filename_p = (char *__user)uptr;
        }
    }
}

使用场景与示例

场景 1: 获取 Root Shell

# 1. 授权当前 UID 的 SU 权限
kpatch key sumgr grant $(id -u) 0 u:r:su:s0

# 2. 执行 SU 命令
su

# 3. 验证权限
id
# uid=0(root) gid=0(root) groups=0(root)

# 4. 验证 SELinux 上下文
id -Z
# u:r:su:s0

# 5. 测试 SELinux 绕过
# 即使 SELinux 是 Enforcing 模式,也可以访问受保护资源
cat /proc/kallsyms  # 通常被 SELinux 阻止,现在可以访问

场景 2: 全局豁免上下文

# 1. 设置全局豁免上下文
kpatch key su -c "echo 'u:r:magisk:s0' > /sys/kernel/kp/all_allow_sctx"

# 2. 切换上下文
su -c "runcon u:r:magisk:s0 sh"

# 3. 验证:所有使用该上下文的进程都绕过 SELinux
id -Z
# u:r:magisk:s0

# 现在所有操作都不会被 SELinux 阻止

场景 3: 程序化使用

// C 代码示例
#include <unistd.h>

void get_root_shell() {
    // 通过 KernelPatch SuperCall 获取 root
    // (实际 API 可能不同)
    kpatch_su(0, "u:r:su:s0");
    
    // 现在进程有 root 权限且绕过 SELinux
    system("/system/bin/sh");
}

场景 4: 修复网络功能

Android 13+ 设备上,如果网络功能异常,可能是策略标志问题:

# KernelPatch 自动修复,无需手动操作
# 检查修复是否生效:
dmesg | grep "policydb_write"
# KP hook policydb_write: success

# 验证网络功能
ping 8.8.8.8

安全考虑

安全特性

  1. 细粒度控制

    • 只对授权的进程绕过 SELinux
    • 其他进程仍受 SELinux 保护
  2. 审计日志

    • 豁免进程的操作不产生审计日志
    • 保持日志的清洁
  3. 上下文管理

    • 基于 SELinux 上下文的管理
    • 与 Android 安全模型兼容

潜在风险

  1. 权限提升

    • 绕过 SELinux 意味着进程可以访问受保护资源
    • 需要严格控制哪些进程可以绕过
  2. 持久化问题

    • task_ext 存储在进程的内核栈上
    • 如果栈被污染,可能影响绕过机制
  3. Hook 稳定性

    • Hook 机制依赖内核符号
    • 内核更新可能导致 Hook 失效

最佳实践

  1. 最小权限原则

    • 只为必要的进程授权
    • 使用 UID 白名单控制
  2. 监控与审计

    • 定期检查授权的 UID
    • 监控异常行为
  3. 定期更新

    • 保持 KernelPatch 更新
    • 适配新内核版本

总结

KernelPatch 的 SELinux 绕过机制通过以下方式实现:

  1. Hook 关键函数:拦截 avc_denied()slow_avc_audit()
  2. 任务扩展:通过 task_ext 标记豁免进程
  3. 全局豁免:支持基于 SELinux 上下文的全局豁免
  4. 策略修复:自动修复 Android 策略标志问题

这套机制实现了对授权进程的透明 SELinux 绕过,同时保持对其他进程的保护,是一个优雅的内核级解决方案。


参考资料


文档版本: 2.0
最后更新: 2026-06-26
相关 KernelPatch 版本: 0.13.2+

评论

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

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