AndroidRoot环境隐藏:SELinux查询探测与对抗

admin 2026-06-02 04:05:30 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档介绍LSPosed团队开源的DirtySepolicy检测方案,该方案通过AppZygote机制在Android环境中查询SELinux策略以检测Root环境。文章详细分析了检测原理,包括利用isolatedProcess触发私有Zygote执行敏感策略查询,并提出了对抗方案selinux-query-guard内核模块,通过hookSELinux相关函数过滤敏感查询结果。内容涵盖技术实现细节、内核hook方法和版本兼容性处理。 综合评分: 85 文章分类: 移动安全,二进制安全,内核安全,逆向分析,安全工具


cover_image

Android Root 环境隐藏:SELinux 查询探测与对抗

用户名null 用户名null

看雪学苑

2026年6月1日 17:59 上海

在小说阅读器读本章

去阅读

LSPosed 团队公布的一个新的 root 检测方案,把查询SELinux policy这种检测方式进行了开源。

如果系统里加载过额外 sepolicy,可能会多出一些 domain、type 或 allow 规则。普通应用虽然不能直接读很多 root 相关文件,但它可以通过系统接口查询某些 SELinux 结果。只要返回值和正常环境不一样,就能变成检测点。

DirtySepolicy 的实现可以简单理解为:用 isolatedProcess 配合 useAppZygote 触发应用私有 Zygote,再通过 zygotePreloadName 把检测代码放到 app_zygote 上下文里执行。只要这个载体能调用 SELinux.checkSELinuxAccess() 向内核查询当前策略,额外 sepolicy 规则就会变成普通应用能读到的信号。

1. DirtySepolicy介绍

DirtySepolicy 通过 AppZygote 这条链路检测。

配置上大概是这样:

<application
&nbsp; &nbsp; android:zygotePreloadName="org.lsposed.dirtysepolicy.AppZygote">
&nbsp; &nbsp; <service
&nbsp; &nbsp; &nbsp; &nbsp; android:name=".DirtySepolicyService"
&nbsp; &nbsp; &nbsp; &nbsp; android:isolatedProcess="true"
&nbsp; &nbsp; &nbsp; &nbsp; android:useAppZygote="true" />
</application>

isolatedProcess 配合 useAppZygote 会触发应用私有 Zygote,zygotePreloadName 指向的类会在 preload 阶段执行。DirtySepolicy 在正式查询前还会做一些自检,比如确认 UID 没被换掉、当前 context 是 app_zygote,并比对 getContext()getPidContext() 和 /proc/self 文件上下文。

自检通过以后,检测点主要落在两个地方。

1.1 SELinux.checkSELinuxAccess

Java 层可以通过类似下面的接口查询 SELinux 访问结果:

SELinux.checkSELinuxAccess(source, target, clazz, perm)

DirtySepolicy 里查询面比较宽,类似这些:

u:r:system_server:s0 -> u:r:system_server:s0 process execmem
u:r:fsck_untrusted:s0 -> u:r:fsck_untrusted:s0 capability sys_admin
u:r:shell:s0 -> u:r:su:s0 process transition
u:r:adbd:s0 -> u:r:adbroot:s0 binder call
u:r:untrusted_app:s0 -> u:r:magisk:s0 binder call
u:r:untrusted_app:s0 -> u:object_r:ksu_file:s0 file read
u:r:untrusted_app:s0 -> u:object_r:lsposed_file:s0 file read
u:r:untrusted_app:s0 -> u:object_r:xposed_data:s0 file read
u:r:zygote:s0 -> u:object_r:adb_data_file:s0 dir search

如果这些查询返回 true,应用就能判断 live sepolicy 里可能存在额外规则。

底层会走到 SELinux 的 context 转换和 AV 计算:

context string -> sid
sid + class + perm -> av_decision

对应到内核函数,大概就是:

security_context_to_sid
security_context_str_to_sid
security_compute_av_user

1.2 /proc/self/attr/current

另一种方式是写:

/proc/self/attr/current

写入一个目标 context 后,内核会走 SELinux procattr 和 transition 检查。如果返回值不是预期的 EINVAL,也可能被拿来做判断。

对应路径大概是:

selinux_setprocattr
security_bounded_transition
security_validate_transition

#

2. selinux-query-guard

检测出来了,那就产生了对抗,最近写了对抗的KPM 模块: selinux-query-guard,用来处理这类 SELinux 查询探测。

做法是在内核 SELinux 相关函数上挂 hook,应用 UID 走到查询入口时,对一些敏感结果做过滤。实现还比较粗,更多是一次针对 DirtySepolicy 思路的 KPM 侧尝试。

selinux-query-guard 加载后会直接安装 hook。

入口:

KPM_INIT(selinux_query_guard_init);
KPM_CTL0(selinux_query_guard_control0);
KPM_EXIT(selinux_query_guard_exit);

初始化时调用:

return install_hooks();

install_hooks() 里通过 kallsyms_lookup_name() 找符号,再用 KernelPatch 的 hook_wrap() 挂回调。

3. hook的函数说明

按用途分几类。

3.1 context 转 SID

security_context_to_sid
security_context_str_to_sid
security_context_to_sid_force
security_secctx_to_secid
security_secid_to_secctx

这里主要用来观察应用在查什么 context。

如果普通 app 或 isolated app 解析了下面这类 context,就先把这个来源记下来:

u:r:magisk:s0
u:r:ksu:s0
u:object_r:ksu_file:s0
u:object_r:lsposed_file:s0
u:object_r:xposed_data:s0

3.2 AV 查询

security_compute_av_user
security_compute_av

SELinux.checkSELinuxAccess() 最后关心的是 av_decision.allowed

命中敏感来源后,模块会把:

avd->allowed

改成:

0

这样应用看到的就是 deny。

这里没有把所有查询都改掉。代码里保留了一个基础查询能力:

if (avd->allowed == SELINUX_ACCESS_QUERY_ALLOWED) {
&nbsp; &nbsp; p->reason = "preserve-selinux-access-query-capability";
&nbsp; &nbsp; return;
}

如果把 SELinux 查询能力本身破坏掉,检测样本可能直接报环境异常,所以这块不能粗暴处理。

3.3 procattr

security_setprocattr
selinux_setprocattr

这里处理 /proc/self/attr/current

before hook 里记录写入的 name 和 value。

如果是 current,并且 value 是敏感 context,after hook 里再判断是否需要改返回值。

命中后返回:

-EINVAL

3.4 transition

security_bounded_transition
security_validate_transition
security_validate_transition_user

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

4. 如何过滤敏感查询

模块里放了一个列表:

static const char *interesting_contexts[] = {
&nbsp; &nbsp; "u:r:app_zygote:s0",
&nbsp; &nbsp; "u:r:isolated_app:s0",
&nbsp; &nbsp; "u:r:ksu:s0",
&nbsp; &nbsp; "u:r:ksu_file:s0",
&nbsp; &nbsp; "u:r:magisk:s0",
&nbsp; &nbsp; "u:r:magisk_file:s0",
&nbsp; &nbsp; "u:r:lsposed_file:s0",
&nbsp; &nbsp; "u:r:xposed_data:s0",
&nbsp; &nbsp; "u:r:system_server:s0",
&nbsp; &nbsp; "u:r:fsck_untrusted:s0",
&nbsp; &nbsp; "u:r:shell:s0",
&nbsp; &nbsp; "u:r:su:s0",
&nbsp; &nbsp; "u:r:adbd:s0",
&nbsp; &nbsp; "u:r:adbroot:s0",
&nbsp; &nbsp; "u:r:untrusted_app:s0",
&nbsp; &nbsp; "u:r:zygote:s0",
&nbsp; &nbsp; "u:object_r:ksu_file:s0",
&nbsp; &nbsp; "u:object_r:lsposed_file:s0",
&nbsp; &nbsp; "u:object_r:xposed_data:s0",
&nbsp; &nbsp; "u:object_r:adb_data_file:s0",
};

另外还做了前缀判断:

u:r:ksu*
u:r:magisk*
u:r:lsposed*
u:r:xposed*
u:object_r:ksu*
u:object_r:magisk*
u:object_r:lsposed*
u:object_r:xposed*

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

5. 怎么区分是否需要hook

不能只看context进行区分。因为系统进程、shell、root 管理器自己也可能查这些东西。直接拦会误伤。

所以模块会记录当前 task 的信息:

struct source_info
{
&nbsp; &nbsp; pid_t pid;
&nbsp; &nbsp; pid_t tgid;
&nbsp; &nbsp; uid_t uid;
&nbsp; &nbsp; uid_t euid;
&nbsp; &nbsp; uid_t fsuid;
&nbsp; &nbsp; const char *comm;
&nbsp; &nbsp; const char *source_class;
&nbsp; &nbsp; bool is_app_uid;
&nbsp; &nbsp; bool is_isolated_uid;
&nbsp; &nbsp; bool is_privileged_uid;
&nbsp; &nbsp; bool is_root_manager_comm;
&nbsp; &nbsp; bool is_sniffer;
};

分类时先放行 root manager 和 system 这类来源:

if (src->is_root_manager_comm) return "root-manager";
if (src->is_privileged_uid) return "privileged/system";

然后再判断是否是已经记录过的探测来源:

if (source_matches_learned_probe(src)) {
&nbsp; &nbsp; src->is_sniffer = true;
&nbsp; &nbsp; return "sensitive-selinux-prober";
}

如果普通 app 或 isolated uid 命中了敏感 context,就记录 UID 和 TGID:

if (sensitive_probe && (src->is_app_uid || src->is_isolated_uid)) {
&nbsp; &nbsp; if (src->is_isolated_uid) learned_probe_isolated_uid = src->uid;
&nbsp; &nbsp; if (src->is_app_uid) learned_probe_uid = src->uid;
&nbsp; &nbsp; learned_probe_tgid = src->tgid;
&nbsp; &nbsp; src->is_sniffer = true;
&nbsp; &nbsp; return "sensitive-selinux-prober";
}

这样做是为了兼容 AppZygote 和 isolatedProcess。检测逻辑可能不在主进程里跑,前一次 context 解析和后一次 AV 查询也不一定完全在同一个进程状态里。

6. 实际修改位置

实际修改只有两处。

第一处是返回值:

p->proposed_ret = -ERRNO_EINVAL;
args->ret = (uint64_t)(int64_t)p->proposed_ret;

用于 procattr 和 transition。

第二处是 AV decision:

p->proposed_allowed = 0;
avd->allowed = p->proposed_allowed;

用于 security_compute_av_user

这两个修改都要先判断来源。不是 app UID,或者没有命中过敏感 context,不会触发改动。

7. 内核版本处理

不同内核版本里,SELinux 函数参数位置不一样。这里用了一个简单判断:

static bool need_selinux_compat_signature(void)
{
&nbsp; &nbsp; return kver >= VERSION(4, 17, 0) && kver < VERSION(6, 4, 0);
}

如果命中这个区间,就走 *_compat 回调。主要是取参数的位置不同:

context 可能是 arg0,也可能是 arg1
avd 可能是 arg3,也可能是 arg4
oldsid/newsid 的位置也可能不同

#

8. 编译

目录:

KernelPatch/kpms/selinux-query-guard

编译:

make

产物:

selinux_query_guard.kpm

如果工具链路径不同,可以自己传:

make KP_DIR=/path/to/KernelPatch TARGET_COMPILE=/path/to/aarch64-none-elf-

#

9. 不足

这个版本能用,但是没有完全自适应root的selinux policy

目前的问题:

敏感 context 是硬编码
UID/TGID 没有过期机制
isolated UID 有复用风险
没有读取 current task 的 SELinux context
AV 查询里没有还原完整 source/target/class/perm
不同设备上符号可能不全

如果继续做,比较值得改的是白名单来源。现在是看到敏感 context 就学习来源,后面可以在 SELinux 初始化前后找一个更早的时机,保存一份干净 policy。应用查询时,优先用这份干净策略判断:干净策略里没有的 context 或 allow,就按普通失败处理。再加上 UID/TGID 过期机制,误伤会少一些。

特别感谢 LSPosed 和 DirtySepolicy 项目的开源分享。

#

看雪ID:用户名null

https://bbs.kanxue.com/user-home-807294.htm

*本文为看雪论坛优秀文章,由 用户名null 原创,转载请注明来自看雪社区

第十届安全开发者峰会【议题征集】-欢迎投稿

# 往期推荐

安卓逆向基础知识之frida Hook

2025 强网杯和强网拟态部分题解

在逆向分析方面-unidbg真的适合 MCP 吗?

AI静态分析,内核模块隐藏 Frida 特征,绕过linker私有结构遍历崩溃链

某安全so库深度解析

球分享

球点赞

球在看

戳“阅读原文”一起来充电吧!


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:看雪学苑 用户名null 用户名null《Android Root 环境隐藏:SELinux 查询探测与对抗》

评论:0   参与:  0