Hide procfs related audit messages from appdomain
这个链接是一个 AOSP 的修复变更, 旨在修复一个 Audit 日志泄露的问题
在三方 App 受限沙箱中, 即使不能直接读取其他进程的 /proc/<pid> 信息, 通过访问 procfs 触发 SELinux Audit 日志, 也可能可以从 logcat 里的 tcontext 反推出目标进程的安全域
也可以说这是一个基于 audit 日志的侧信道检测思路.
基于这个思路, 先尝试了以下三类场景:
依旧是使用 Mira 开源框架进行运行时风险分析. 介绍在 https://bbs.kanxue.com/thread-291041.htm 这篇文章
相对于不断打包安装和触发, Mira 只需 AI 自行对 shell 脚本不断进行微调和执行即可. 比如这次的改 PID 范围, 改 logcat 匹配规则, 改扫描窗口, 很快, 很省精力.
文末为原理, 修复, 以及绕过方案.
让 AI 调用 Mira 的 MCP 功能, AI 这里使用分块扫描, 从 900 开始按窗口触碰 /proc/<pid>, 每个窗口 25 个 PID, 发现命中后停止.

可以直接看到, App 沙箱侧触碰 procfs 后, audit 日志暴露了 tcontext=u:r:magisk:s0.
AVD 即 Android Studio 自带模拟器, 这里使用 M 芯片 Mac android studio 的 Android 13 镜像作为演示样本
Magisk检测 中使用 AI 调用 MCP 用于展示 AI 快速微调的效率, 实际弄懂原理之后, 手动使用该受控三方权限 shell 会更快
先 adb 查看有什么进程, 进程名是什么
试探一下, 发现了 qemu_props 特征

同样, 再找点其他模拟器特征, 比如
这些就不如 qemu 的特征明显, 但也可以被触发

从 android 9 往后, 对 scrcpy 的检测就一直没有什么好方案, 因为特征隐藏的足够好, 而 audit 可以提供一种新的思路
分析 scrcpy 的源码, 发现新版本的 scrcpy 基于 adb shell 拉起 app_process, 启动 scrcpy 自己的 jar 包运行, 运行起来会删除 /data/local/tmp/scrcpy-server.jar 文件, 没有文件特征
参考 avd 的研究方法, 先 adb shell 查看, 可以看到投屏服务对应 sh -> app_process -> app_process 三个 pid 很相近的进程
audit 日志不能直接读出 app_process, 但能看到高 PID 中有三个离得很近的 u:r:shell:s0 特征:

启停对比也符合预期: 关闭投屏后, 相同高 PID 范围扫描结果为:
正常用户即使开启 adb, 不用 scrcpy 投屏也不会有这个特征, 因此连续 3 个 u:r:shell:s0 可以作为判断正疑似使用 scrcpy 投屏的策略, 具体需要线上环境验证, 这里只是提供一个思路
若想隐藏, 要么使用修复后系统, 要么就是得 root 设备然后 hook 系统框架将这部改掉, 但就会引入新的特征进入 root 对抗的范畴. 提高攻击方的成本.
该侧信道不适合无脑大范围扫描, 实验中出现过单点命中, 大窗口扫描反而漏检的情况
原因主要有两个:
推荐使用小窗口或重叠窗口:
如果 CHUNK=50 漏检, 优先降低到 CHUNK=10, 或使用 CHUNK=50 STEP=25。不要只增加 sleep, 因为失败原因通常不是日志延迟, 而是 audit 限流和窗口噪声。
当前 shell 触碰 /proc/<pid> 和 sh script.sh 新开子 shell 触碰, 不等价.
推荐方式:
问题核心在 system/logging/logd/LogAudit.cpp 的 LogAudit::logPrint
logd 收到 audit 消息后, 先格式化成字符串:
此时 str 中已经包含原始 audit 信息, 例如 dev="proc", scontext, tcontext, tclass, comm, path 等字段。对本文场景来说, 关键是 dev="proc" 和 tcontext 同时存在
随后代码从 audit 字符串中查找 pid= 字段, 并用 pidToUid 找到触发 audit 的 UID:
这个 UID 后续会传给日志系统. 对于 App 触发的 procfs audit, 这里解析出的 UID 会落在 AID_APP_START 之后.
原代码会调用 auditParse(str, uid):
auditParse 会解析 scontext, tcontext, tclass, 并尝试匹配 bug metadata. 更重要的是, 当 UID 属于 App 范围时, 它还会追加 App 包名:
这说明 logd 不只是原样转发 audit 字符串, 还会把它加工成更适合定位问题的日志. 对系统调试来说这是正向能力, 但对 App 可见日志来说, 它强化了侧信道可读性.
原代码随后把 audit 字符串和追加元数据写入 events buffer:
这里使用的 uid, pid, tid 来自前面的解析结果. 也就是说, 这条 audit 日志会以触发方相关身份进入日志系统.
后续代码还会构造 main buffer 日志. 它会从 audit 字符串中解析 comm="...", 构造新的日志内容, 再把 denial metadata 拼进去.
结果是, 同一条 procfs denial 可能进入 main buffer 和 events buffer. 只要 App 侧能够读取到对应日志面, tcontext 就会暴露.
根因可以概括为一句话:
hidepid=2 保护的是 procfs 正常读取面, 但原 logd 路径把 procfs 访问失败后的 audit 诊断信息转发到了 App 可见日志面.
因此 App 不需要直接读取 /proc/<pid>. 它只需要触碰 /proc/<pid> 触发 getattr denial, 再从 logcat 中读取 tcontext, 就能推断目标进程属于哪个 SELinux 安全域.
Android 上 audit 消息会通过 netlink socket 转发给 logd, logd 又可能把这些消息转发到 App 自身可见的日志面. 这样就形成了下面的信息流:
这条链路绕过的不是 SELinux 权限检查本身, 而是利用权限检查失败后的诊断信息.
3725346 补丁新增的逻辑如下:
这段过滤有两个条件。
第一, uid >= AID_APP_START. 说明过滤对象是 App UID 触发的 audit, 而不是所有系统 audit. 这样可以避免影响系统服务和原生系统进程的正常诊断.
第二, strstr(str, "dev=\"proc\""). 说明过滤对象是 procfs 相关 audit. 补丁没有泛化过滤所有 SELinux denial, 而是精准阻断这条通过 /proc/<pid> 泄露其他进程安全域的路径.
修的是 logd 的转发边界. 访问失败可以继续失败, 系统也可以继续审计, 但 App 无法通过日志缓冲看到其他进程的 tcontext.
一条典型的 SELinux 拒绝记录如下:
关键字段如下:
[培训]《冰与火的战歌:Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。
最后于 1天前
被vwvw编辑
,原因: 更新绕过方案细节