
Target-specific Android CVE-2026-98260 research port for Xiaomi 17 (pudding)
针对小米 17(代号
pudding)一个精确内核构建的 Android AArch64 利用链:从普通 ADB shell 出发,依次把 SELinux 置为 Permissive、把本进程 UID/GID 归零、补齐完整 Linux capabilities,并在当前开机周期内提供 root 服务。
本项目参考并移植自 CVE-2026-98260-popsicle-android(Xiaomi 17 Pro Max / popsicle),不是从零实现的独立研究。
非主线程 execve() 会先执行 de_thread() 交换线程身份。若 exec 在这之后失败(例如 exec_mmap() 被 SIGKILL 打断而返回 -EINTR),任务会在清理 POSIX timer 之前退出:k_itimer 被释放,但它的 timerqueue 节点仍留在存活任务的树里。父进程之后回收该任务时,cleanup_timerqueue() 再次操作这个已释放节点,形成 use-after-free。
利用把这次 UAF 变成一次受限的八字节零写,再按 SELinux → UID/GID → 完整 capabilities 三步推进。面向入门的完整讲解见 docs/VULNERABILITY.md。
| 项目 | 实测值 |
|---|---|
| 设备 | Xiaomi 17,型号 25113PN0EC,代号 pudding |
| 系统 | 小米澎湃 OS 4 OS4.0.5.0.XPCCNXM,Android 17 |
| 内核 | 6.12.69-android16-6-g43b5ab7e247d-abogki561813157-4k |
| 页大小 | 4 KiB |
| 起点 | 已授权的普通 ADB shell,UID 2000,SELinux Enforcing,bootloader 锁定 |
| 成功结果 | UID/GID 0、SELinux Permissive、CapPrm/CapEff/CapBnd=000001ffffffffff |
| 生命周期 | root 服务可供本次开机后续的新 ADB shell 使用;重启后需要重新运行 |
成功后的独立验证(新开的 ADB shell):
uid=0(root) gid=0(root)
CapInh: 000001ffffffffff
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
getenforce: Permissive
这不是持久 root。 没有写入 boot/init_boot 或任何分区,bootloader 保持锁定;重启后 SELinux 回到 Enforcing,root 服务消失。
不承诺成功率。 实测在 9 次开机中凑齐一次完整的 A→B→C。三个阶段的通过条件彼此独立:
| 阶段 | 设备端程序 | 通过条件 |
|---|---|---|
| 1. SELinux | exploit-android-perf | getenforce 为 Permissive,状态字其余字段保持预期 |
| 2. 身份 | exploit-android-perf-chain | 同一进程 UID/GID 变为 0,随后 exec fullcap |
| 3. 完整权限 | exploit-android-key-ptr + pc98260_rootd.c | 完整 capabilities,root 服务可被新 shell 访问 |
任一步没命中都会输出 [PARKED] 并保留危险状态,必须重启,不能在本次开机内强行重试。
--aio 16384 是本机的调优值(成功那次在第 3903 个 perf 元数据页命中)。
| 位置 | popsicle | pudding |
|---|---|---|
LINK_SELINUX_STATE | 0xffffffc08289aba0 | 0xffffffc0827f9ba0 |
LINK_GETPID | 0xffffffc080074eac | 0xffffffc080102e10 |
| timer 地址泄漏 | alloc_posix_timer 返回探针 | 该符号被内联,改用 posix_cpu_timer_create 入口 $arg1 |
PERF_MAX | 8192 | 16384(提高页命中机会) |
依据与证据见 docs/ADAPTATION.md。
需要一个 Android NDK。仓库中的 bin/ 由 NDK 26.3.11579264 / API 34 构建:
powershell -NoProfile -ExecutionPolicy Bypass -File .\build.ps1 -NdkHome "$env:LOCALAPPDATA\Android\Sdk\ndk\26.3.11579264" -ApiLevel 34
-VerifyReference 会逐个比较新产物与 bin/ 的 SHA-256;只有使用与 bin/ 相同的工具链才会逐字节相同。换 NDK 版本仍可运行,但哈希会不同。
产物:
| 产物 | 输入源码 |
|---|---|
exploit-android-perf | src/exploit-android-perf.c |
exploit-android-perf-chain | src/exploit-android-perf-chain.c |
exploit-android-key-ptr | src/exploit-android-key-ptr.c + src/pc98260_rootd.c |
pc98260-su | src/pc98260_su.c |
构建不连接手机、不写设备。
连接一台已开启 USB 调试并已授权的设备,把 platform-tools 加入 PATH:
.\开始98260临时ROOT.bat
或显式指定设备:
powershell -NoProfile -ExecutionPolicy Bypass -File .\run_98260.ps1 -Serial <ADB序列号> -Reboot
主机脚本会核对机型、系统增量版本、完整内核字符串、boot ID 与四个二进制的哈希;它还会在启动每个阶段前把 /sys/kernel/tracing/tracing_on 置 0(部分 HyperOS 构建的 miuibpf 会把它保持在 1,利用链会因此拒绝运行)。出现 [PARKED] 时脚本停止,并提示重启。
成功后可这样使用 root:
adb shell -t /data/local/tmp/pc98260-su
adb shell /data/local/tmp/pc98260-su -c "id"
利用链要求精确回收“目标定时器所在的那一页”。如果观察不到同一目标页,或指针写入后的验证不一致,程序会保留相关 zombie/映射并停止,避免在悬空节点仍被内核引用时盲目回收。此时应以一次新的手机启动作为下一次尝试的边界。
本仓库不包含:设备备份、boot/init_boot 镜像、分区镜像、payload.bin、从固件解出的内核/ELF、原始运行日志、ADB 序列号或 boot ID。logs/ 与 build/ 已被 Git 忽略。bin/ 中只有四个临时提权链二进制。
/data/local/tmp/pc98260-root.sock;本次开机内整机完全暴露。