KernelPatch Android SELinux 详解
目录
概述
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)的安全系统:
安全上下文(Security Context)
- 格式:
user:role:type:level - 例如:
u:r:system_app:s0 - 每个进程和对象都有上下文
- 格式:
SID(Security ID)
- 上下文在内存中的整数标识符
- 用于提高性能,避免字符串比较
AVC(Access Vector Cache)
- SELinux 的访问控制决策缓存
- 加速权限检查
访问决策流程
用户操作
↓
系统调用
↓
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; // 外部设置 │ │
│ │ ... │ │
│ │ } │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────┘
核心组件
Hook 机制
- Hook
avc_denied()和slow_avc_audit() - 在访问控制决策前拦截
- Hook
任务扩展 (task_ext)
- 附加到每个进程的扩展数据
- 存储 SELinux 绕过标志
全局豁免上下文
all_allow_sctx: 全局豁免的 SELinux 上下文- 所有使用该上下文的进程自动绕过
策略修复
- 修复 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 表示允许
}
工作流程:
- 全局豁免检查:如果设置了
all_allow_sid,检查源 SID 是否匹配 - 任务豁免检查:检查当前进程的
task_ext是否标记为豁免 - 正常流程:如果都不匹配,调用原始的
avc_denied()进行正常检查 - 强制允许:如果匹配豁免条件,设置
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_process 或 cgroup_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;
}
关键操作:
- 禁用 Seccomp(防止被拦截)
- 提升 UID 和 Capabilities
- 切换 SELinux 上下文
- 设置
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
安全考虑
安全特性
细粒度控制
- 只对授权的进程绕过 SELinux
- 其他进程仍受 SELinux 保护
审计日志
- 豁免进程的操作不产生审计日志
- 保持日志的清洁
上下文管理
- 基于 SELinux 上下文的管理
- 与 Android 安全模型兼容
潜在风险
权限提升
- 绕过 SELinux 意味着进程可以访问受保护资源
- 需要严格控制哪些进程可以绕过
持久化问题
task_ext存储在进程的内核栈上- 如果栈被污染,可能影响绕过机制
Hook 稳定性
- Hook 机制依赖内核符号
- 内核更新可能导致 Hook 失效
最佳实践
最小权限原则
- 只为必要的进程授权
- 使用 UID 白名单控制
监控与审计
- 定期检查授权的 UID
- 监控异常行为
定期更新
- 保持 KernelPatch 更新
- 适配新内核版本
总结
KernelPatch 的 SELinux 绕过机制通过以下方式实现:
- Hook 关键函数:拦截
avc_denied()和slow_avc_audit() - 任务扩展:通过
task_ext标记豁免进程 - 全局豁免:支持基于 SELinux 上下文的全局豁免
- 策略修复:自动修复 Android 策略标志问题
这套机制实现了对授权进程的透明 SELinux 绕过,同时保持对其他进程的保护,是一个优雅的内核级解决方案。
参考资料
- SELinux 官方文档
- Android SELinux 文档
- KernelPatch 源码:
kernel/patch/common/accctl.c - KernelPatch 源码:
kernel/patch/android/sepolicy_flags.c
文档版本: 2.0
最后更新: 2026-06-26
相关 KernelPatch 版本: 0.13.2+
评论
- 还没有评论,来说点什么吧。