Lazy loaded image
【看雪转载】Android Root 检测之 SELinux 查询探测的对抗
Words 1806Read Time 5 min
2026-7-14
2026-7-14
type
Post
status
Published
date
Jul 14, 2026
slug
summary
LSPosed 团队公布的一个新的 root 检测方案,把查询SELinux policy这种检测方式进行了开源。 如果系统里加载过额外 sepolicy,可能会多出一些 domain、type 或 allow 规则。普通应用虽然不能直接读很多 root 相关文件,但它可以通过系统接口查询某些 SELinux 结果。只要返回值和正常环境不一样,就能变成检测点。
tags
SELinux
看雪
category
转载
icon
password
网址
上次编辑时间
Jul 14, 2026 07:07 AM
comment
AI 总结
LSPosed 团队公布的一个新的 root 检测方案,把查询SELinux policy这种检测方式进行了开源。
如果系统里加载过额外 sepolicy,可能会多出一些 domain、type 或 allow 规则。普通应用虽然不能直接读很多 root 相关文件,但它可以通过系统接口查询某些 SELinux 结果。只要返回值和正常环境不一样,就能变成检测点。
DirtySepolicy 的实现可以简单理解为:用 isolatedProcess 配合 useAppZygote 触发应用私有 Zygote,再通过 zygotePreloadName 把检测代码放到 app_zygote 上下文里执行。只要这个载体能调用 SELinux.checkSELinuxAccess() 向内核查询当前策略,额外 sepolicy 规则就会变成普通应用能读到的信号。

1. DirtySepolicy介绍

DirtySepolicy 通过 AppZygote 这条链路检测。
配置上大概是这样:
isolatedProcess 配合 useAppZygote 会触发应用私有 Zygote, zygotePreloadName 指向的类会在 preload 阶段执行。DirtySepolicy 在正式查询前还会做一些自检,比如确认 UID 没被换掉、当前 context 是 app_zygote ,并比对 getContext()getPidContext()/proc/self 文件上下文。
自检通过以后,检测点主要落在两个地方。

1.1 SELinux.checkSELinuxAccess

Java 层可以通过类似下面的接口查询 SELinux 访问结果:
DirtySepolicy 里查询面比较宽,类似这些:
如果这些查询返回 true,应用就能判断 live sepolicy 里可能存在额外规则。
底层会走到 SELinux 的 context 转换和 AV 计算:
对应到内核函数,大概就是:

1.2 /proc/self/attr/current

另一种方式是写:
写入一个目标 context 后,内核会走 SELinux procattr 和 transition 检查。如果返回值不是预期的 EINVAL ,也可能被拿来做判断。
对应路径大概是:

2. selinux-query-guard

检测出来了,那就产生了对抗,最近写了对抗的KPM 模块: selinux-query-guard9J5c8Y4y4W2L8r3W2F1N6i4S2Q4x3X3c8I4N6h3g2J5P5g2\)9J5k6r3N6#2j5i4u0V1) ,用来处理这类 SELinux 查询探测。
做法是在内核 SELinux 相关函数上挂 hook,应用 UID 走到查询入口时,对一些敏感结果做过滤。实现还比较粗,更多是一次针对 DirtySepolicy 思路的 KPM 侧尝试。
selinux-query-guard 加载后会直接安装 hook。 入口:
初始化时调用:
install_hooks() 里通过 kallsyms_lookup_name() 找符号,再用 KernelPatch 的 hook_wrap() 挂回调。

3. hook的函数说明

按用途分几类。

3.1 context 转 SID

这里主要用来观察应用在查什么 context。
如果普通 app 或 isolated app 解析了下面这类 context,就先把这个来源记下来:

3.2 AV 查询

SELinux.checkSELinuxAccess() 最后关心的是 av_decision.allowed
命中敏感来源后,模块会把:
改成:
这样应用看到的就是 deny。
这里没有把所有查询都改掉。代码里保留了一个基础查询能力:
如果把 SELinux 查询能力本身破坏掉,检测样本可能直接报环境异常,所以这块不能粗暴处理。

3.3 procattr

这里处理 /proc/self/attr/current
before hook 里记录写入的 name 和 value。 如果是 current ,并且 value 是敏感 context,after hook 里再判断是否需要改返回值。
命中后返回:

3.4 transition

写 attr current 后,有些内核会继续走 transition 检查。这里也做了返回值过滤,避免从另一条路径露出差异。

4. 如何过滤敏感查询

模块里放了一个列表:
另外还做了前缀判断:
这块现在写得比较直接。好处是简单,坏处是后续要维护。后面可以考虑把模块加载时机再提前,在 SELinux 初始化完成但脏规则加载之前,先抓一份干净 policy,后续把这份干净策略当白名单来判断查询结果。

5. 怎么区分是否需要hook

不能只看context进行区分。因为系统进程、shell、root 管理器自己也可能查这些东西。直接拦会误伤。
所以模块会记录当前 task 的信息:
分类时先放行 root manager 和 system 这类来源:
然后再判断是否是已经记录过的探测来源:
如果普通 app 或 isolated uid 命中了敏感 context,就记录 UID 和 TGID:
这样做是为了兼容 AppZygote 和 isolatedProcess。检测逻辑可能不在主进程里跑,前一次 context 解析和后一次 AV 查询也不一定完全在同一个进程状态里。

6. 实际修改位置

实际修改只有两处。
第一处是返回值:
用于 procattr 和 transition。
第二处是 AV decision:
用于 security_compute_av_user
这两个修改都要先判断来源。不是 app UID,或者没有命中过敏感 context,不会触发改动。

7. 内核版本处理

不同内核版本里,SELinux 函数参数位置不一样。这里用了一个简单判断:
如果命中这个区间,就走 *_compat 回调。主要是取参数的位置不同:

8. 编译

目录:
编译:
产物:
如果工具链路径不同,可以自己传:

9. 不足

这个版本能用,但是没有完全自适应root的selinux policy
目前的问题:
如果继续做,比较值得改的是白名单来源。现在是看到敏感 context 就学习来源,后面可以在 SELinux 初始化前后找一个更早的时机,保存一份干净 policy。应用查询时,优先用这份干净策略判断:干净策略里没有的 context 或 allow,就按普通失败处理。再加上 UID/TGID 过期机制,误伤会少一些。
上一篇
【转载吾爱破解】Android7.0以上命名空间详解(dlopen限制)附上010editor模块
下一篇
【看雪转载】基于最新版 Magisk Zygisk,移植实现一个带 WebUI、支持 Android 16 的 Zygisk 等效模块r0zygisk(一)

Comments
Loading...