白彩恋
总主编2026 年 2 月 19 日 星期四
前言
SSU 在最开始的设想是先基于内核模块实现一个 root 方案,然后逐步演化为修补内核 不过经过更切实地思考,基于内核实现并非普遍需要,大部分人的需求实际上 Magisk 完全就可以解决 做成内核模块即便是LKM,也会因为内核大版本更新而需要更多编译、更多适配,
更何况一个内核大版本放一个 ko 实在是有点烦人
所以 SSU 在大约去年年末就砍掉了内核的开发方向,打算基于 ramdisk 实现用户空间的 root 方案
不过由于 Linso 的时间问题,就算转开发方向了也还是没有把 SSU 写出来,于是我便花几个小时把整套思路打通了
实现
Magisk 作为老牌 root 方案,通过ramdisk 完成 root 权能 与 模块 的实现
既然 SSU 也打算通过 ramdisk 实现,那么肯定要做出一套不一样的方案
Magisk 是通过把原来的 ramdisk init 应该干的活,自己完全实现了一套,
然后执行 系统 init 的过程就完全可控了,注入 SELinux、init rc 就非常方便了
那么 SSU 是不可能实现这么一套复杂逻辑的,这么些挂载逻辑让我复制粘贴都嫌麻烦,
最理想的方案就是跳过系统挂载逻辑,只在执行 系统 init 前处理注入逻辑,
先前的尝试
在 SSU 立项之前,我便研究过ramdisk,那时只是随意尝试,
试了一下在 ramdisk init fork 个子进程看挂载状态,主进程执行原 init
在尝试过程中,发现进程总会在 系统 init 加载后卡死,应该是 SELinux 发力了
这就在开发过程中提了个醒:进程绝对要在执行 系统 init 前处理好所有事情,否则就来不及了
ptrace
在一开始,我和 Linso 都采用的是ptrace 实现,不过结果不尽人意,
init 总是会在被拦截系统调用时崩溃,我也并没有深究原由,把目标转向了另一个方案:seccomp
seccomp
通过seccomp 的 用户通知 可以直接过滤系统调用,依此拦截原 init 执行下一层 init 的 execve 操作
不过使用 用户通知 必须有一个进程来响应,但是就像前面所说的,到最后不能存留额外进程,
使用它会使得后面的 execve 一调用就崩溃,那这该怎么办呢?
seccomp 的设计是只增不减,无法卸载过滤器,并且 用户通知 的优先级比纯 允许 的优先级高,无法通过新增过滤器来覆盖
不过虽然过滤器中无法判断具体的路径,但是可以判断指针地址来实现
判断地址流程
init 中执行下一层的 /system/bin/init 的代码所使用的路径一定是常量,理论上并不会出现其他的骚操作
所以可以直接分析 init 二进制,获取 /system/bin/init 这个字符串的地址,然后在执行时禁用 ASLR,过滤器就能精准判断了
虽然这样没办法移除过滤器,但是过滤器不会被使用第二次,所以也不会再出现后续执行 execve 就被卡死的情况了
左脚踩右脚
所以整体思路就是在运行时静态分析二进制,获取路径地址并添加过滤器来拦截 不过现代设备都是boot/init_boot -> vendor_boot -> system,这样用户空间里面会有三个 init
这自然也不是问题,只需要在拦截到 execve 时判断环境,如果还没有到系统,
那么直接通过 mount 把自己挂载上去,执行时把自己 umount 后再来一次静态分析然后添加过滤器
这样就算有无数 init,也都能正常跳过到 系统 init 执行前
并且这样完全没有像 Magisk 那样处理复杂的挂载逻辑,只需要拦截 execve 执行,其他的原厂 ramdisk init 爱怎么跑怎么跑
优劣
使用seccomp 有一个好处那就是 Android 默认就会开启过滤模式,普通软件不能通过判断 seccomp 模式来检测 root
我个人认为过滤器数量并没有什么参考性,大不了就再拦截一下?不过
seccomp 的 用户通知 需要 Linux 5+,这是一个比较遗憾的点了,我一开始还想着给我的随身 WiFi 试试看的
禁用 ASLR 了在最后开回去也没什么问题
设想
一个最根本的 root 实现逻辑已经完成,后续就是 root 的使用逻辑了网页化
SSU 打算以一个纯网页的形式实现使用 root 的全过程(更实用就要同时搭配软件了,不过重心将会是网页) 首先是修补部分,可以通过wasm 来直接注入 ramdisk,然后获取注入后的镜像并自行刷入
然后在刷入后,SSU 会在设备本地开一个最小化后端,可在网页激活并下载完整依赖,依此在网页中进行权能管理、模块管理等
密码机制等详细内容就不再细讲啦
