一个针对 Android GKI 6.12 系列(三星和 Pixel 设备)的 Linux 内核漏洞利用,目标是 CVE-2026-43499:rt_mutex_start_proxy_lock() 中 remove_waiter() 回滚存在缺陷,使用 current 而非 waiter::task,导致 waiter 的 pi_blocked_on 指向其(随后被弹出的)内核栈 rt_mutex_waiter。结合 pselect() fd_set 栈覆写和消费者线程 sched_setattr 驱动的伪红黑树遍历,可产生确定性的内核写原语并获得完整 root 权限。
该漏洞利用以 LD_PRELOAD 共享库(preload.so)形式运行,并在提权后安装 root su 守护进程及壁纸。
状态:活跃开发中。各目标的栈深度偏移(
PSELECT_*字移位)和 slide 行为因设备而异,仍在真机上验证。m1q(ZF1)目标是当前 bring-up 的重点。
当 FUTEX_CMP_REQUEUE_PI 重新排队一个 waiter,且重排队的死锁检测链遍历返回 -EDEADLK 时,__rt_mutex_start_proxy_lock() 通过 remove_waiter()(rtmutex.c:1535)回滚。该回滚正确地将 waiter 从等待树中出队,但清除的是重排队调用者的 pi_blocked_on,而非 waiter->task->pi_blocked_on。WAITER 的 pi_blocked_on 仍悬空指向其栈上的 rt_mutex_waiter,而该栈对象在 futex 超时后即被弹出。
超时后,waiter 的内核栈区域被复用:core_sys_select() 将三个 fd_set 拷贝到该栈缓冲区(ZF1 上 nfds < 344 的栈路径)。精心构造的 fd_set 字数组重新物化出一个伪 rt_mutex_waiter / 伪 task / 伪 rt_mutex(其 rb_tree 根指针受控制)。随后一个消费者线程调用 sched_setattr_tid(waiter) → rt_mutex_adjust_pi() → rb_erase_cached,产生对任意地址写入受控值的原语。
整个原语必需 PI 链环:owner 持有 f_pi_target,同时又阻塞在 f_pi_chain(由 waiter 持有)上,因此链遍历会命中 owner → chain → waiter → target → owner,并以 -EDEADLK 失败。移除该环会使重排队返回成功,但对内核零影响。
sched_blocked_reason(m1q 主路径):从环形缓冲区读取被阻塞 kworker 保存的返回 PC(stack_trace_save_tsk),并与编译进内核的 worker_thread 偏移进行比较。SLIDE_LOGGERS_0_1 植入 boot_id sysctl 数据;泄漏值可重构 stext。mm_struct 大小的对象,并利用 futex 哈希碰撞在堆上定位 mm_struct,泄漏一个内核堆页地址,作为伪对象喷射基址。configfs_bin_write_iter 写入静态 .data ashmem file_operations 的 write_iter 槽位(+0x30)。随后打开 ashmem 即可通过 configfs 二进制文件系统路径获得任意内核读写。preload.c 安装内置 守护进程(以 tmpfs 挂载到 ,另有 adbd 命名空间和本地变体)并替换壁纸。可选的 STAGE3=1 阶段:fork 一个桥接子进程,将其 mm->pgd 交换为分阶段的伪页表(3 级:PGD→L1→L2,RX 循环叶子 + RW 栈叶子),并让子进程运行一个仅寄存器的汇编 blob,通过 2MB 物理扫描窗口修补自身 cred — 无需 configfs/pipe 原语即可实现全 RAM 物理读写。目前仅接入 m1q 目标,且未在真机上运行过。
src/targets/<codename>-<build>/ 中有 41 个目标,每个至少需要一个 target.h(内核符号偏移、KIMAGE_TEXT_BASE、直接映射基址、结构体偏移)。Pixel 目标(comet、tokay、tegu、caiman、komodo、frankel、mustang、rango、stallion、blazer)覆盖共享源码;三星目标(m1q-*)添加设备特定逻辑。
make list-projects # 完整列表
不同设备的内核结构体布局不同 — 必须针对每个目标根据实际内核二进制/kallsyms 验证偏移。
输出:build/<PROJECT>/bin/preload.so(通过 LD_PRELOAD 加载的共享库)。
# 默认项目
CC=clang make
# 指定设备目标(默认:blazer-CP2A.260605.012)
CC=clang make PROJECT=m1q-BP4A.251205.006
# 不带 CC=clang:使用 NDK(若找到 $NDK_ROOT / $ANDROID_NDK_HOME /
# $ANDROID_NDK_ROOT),否则使用主机 clang + Android sysroot
make PROJECT=m1q-BP4A.251205.006
# 显示构建配置
make info
# 清理
make clean
构建过程会将 PIE su_daemon 二进制(src/su_daemon.c,构建到 build/embed/su_daemon_aarch64_pie)和 assets/wallpaper.webp 通过 src/su_blob.S / src/wallpaper_blob.S 嵌入 preload.so。
将构建输出推送到 /data/local/tmp,然后在 LD_PRELOAD 下运行漏洞利用。在 bring-up 真机上运行时不要使用 tee — tee 会缓冲自身 stdio,内核 panic 时日志尾部会丢失(漏洞利用 stdout 无缓冲,因此直接重定向到文件可保留每一行):
adb push build/m1q-BP4A.251205.006/bin/preload.so /data/local/tmp/preload.so
adb push build/embed/su_daemon_aarch64_pie /data/local/tmp/su_daemon_aarch64_pie
adb shell "chmod 755 /data/local/tmp/preload.so /data/local/tmp/su_daemon_aarch64_pie"
adb shell "LD_PRELOAD=/data/local/tmp/preload.so \
/data/local/tmp/su_daemon_aarch64_pie \
> /data/local/tmp/output.log 2>&1"
成功时守护进程监听 /data/local/tmp/temp_su.sock,并且 su 被安装到 /apex/com.android.virt/bin。
exploit/
Makefile
assets/ # wallpaper.webp(嵌入式提权后产物)
src/
main.c # run_exploit() 入口点(共享)
preload.c # LD_PRELOAD 构造函数,su/壁纸安装
util.c # 地址转换、页准备、configfs 辅助函数
pipe.c # 基于管道的物理读写
root.c # cred 修补 / 任务遍历
slide.c # KASLR slide(tracefs + boot_id 路径)
fops.c # pselect 损坏 + fops 操作
faketables.c # 阶段 3 伪页表构建器(3 级)
stage3.c # pgd 交换桥接子进程(未配置时为 no-op)
stage3_loop.S, stage3_poll.S # 桥接 blob + 进入跳板
su_daemon.c # root 守护进程(作为独立 PIE 编译)
su_blob.S, wallpaper_blob.S # 嵌入式二进制
offset.h # 通过 -DTARGET_CONFIG_H 引入各目标 target.h
kernelsnitch/ # mm_struct futex 哈希泄漏库
targets/<codename>-<build>/ # 每设备 target.h + 可选覆盖
poc/poc.c # 独立 POC(与漏洞利用分开)
目标通过 Makefile 的 pick_src 机制覆盖共享源码:若 src/targets/<PROJECT>/<file>.c 存在,则使用该文件替代共享的 src/<file>.c;否则使用共享版本。
-EDEADLK(errno 35)失败。缺少该环会表现为 success=1 但内核零效果。PSELECT_WAITER_WORD_SHIFT 必须为 0(ZF1 栈深度已根据 ELF 验证)。其他目标仍默认为 1 — 在信任前请逐设备验证。p0_data_alias() 使用 DIRECT_MAP_BASE(线性映射),这与 ZF1 上的 P0_PAGE_OFFSET 不同。被解引用的内核对象的数据别名必须使用线性映射。pr_error() 是致命的(exit(-1)),与警告/信息宏不同。SLIDE_LOGGERS_0_1,而非 SLIDE_NFULNL_LOGGER;该字是非随机化的线性映射别名,因此 boot_id 路径与 slide 无关(仅在 slide=0 时使用 — 先运行 tracefs 泄漏)。这是面向 Android 设备的漏洞利用代码,仅用于安全研究、经授权的漏洞测试,以及在你拥有或获得明确测试许可的硬件上进行防御工作。在非自有设备上滥用是违法的。作者不对变砖的设备、变砖的内核或失效的保修负责。
su/apex/com.android.virt/bin| 变量 | 默认值 | 用途 |
|---|
PSELECT_ROUTE_SHIFT | 编译时 | 主路径(m1q)的 A/B fd_set 字移位 |
SLIDE_SHIFT | 编译时 | slide 路径的 A/B fd_set 字移位 |
PSELECT_ROUTE_DELAY_USEC | 50000 | sched_setattr 前的消费者延迟(必须 > 0) |
SLIDE_CONSUME_USEC、SLIDE_ENTER_DELAY_USEC、SLIDE_CONSUMER_CORE | — | slide 路径时序/绑核 |
SKIP_SLIDE | 0 | 使用直接映射后备方案(slide = 0) |
SLIDE_ONLY | 0 | 仅运行 KASLR slide 后退出 |
SLIDE_P0_OFFSET、SLIDE_BOOTID_OFF | — | 覆盖 slide p0 别名 / boot_id 偏移 |
KSNITCH_COLLISIONS | 4 | KernelSnitch 碰撞计数 |
STAGE3 | 0 | 启用 pgd 交换桥接子进程阶段(m1q) |
STAGE3_DRYRUN | 0 | 暂存/验证页表后在交换前中止 |
PAGE_RECLAIM_SENDS、PSELECT_SIMPLE_LAYOUT | — | 喷射/fd-set 布局调整 |
TMP_PAGE_* / TMP_UNAME_* | — | tmp_page 实验路径(m1q) |