首页
社区
课程
招聘
[原创]一加ACE5至尊版 kernel 6.6.89尝试利用GhostLock / IonStack 进行root提权
发表于: 7小时前 288

[原创]一加ACE5至尊版 kernel 6.6.89尝试利用GhostLock / IonStack 进行root提权

7小时前
288

本文记录 Nebula Security 公开的 IonStack 利用链中核心漏洞 CVE-2026-43499(GhostLock)的利用原理,以及在 OnePlus 13T(PLC110,kernel 6.6.89 GKI)上对其进行开发适配的完整过程。所有偏移数据均来自真机实测(root kallsyms + BTF + IDA),非照抄原 Pixel 10 target。

IonStack 是 Nebula Security 公布的一条 Android 完整利用链,从 Firefox 渲染器(CVE-2026-10702,SpiderMonkey JIT function.name 类型混淆)切入,衔接到 Android 内核提权(CVE-2026-43499,GhostLock),最终拿到 root 并植入 su daemon。

CVE-2026-43499 被 README 称为 "GhostLock —— 一个在所有 Linux 发行版中存在了 15 年的栈 UAF"。它位于内核 futex PI(优先级继承)requeue 路径,是核心 futex 代码,因此几乎所有 Linux 系统都受影响。

原利用仅针对 Pixel 10 系列(Android 17)提供了 39 个固件的 target 配置。本文的目标是把它适配到一台 OnePlus 13T(PLC110) 上:

适配的核心工作是:重新提取全部内核符号/结构体偏移,并验证各子技术在本机内核上的兼容性

Linux futex 的 PI requeue 用于把一个等待在 futex_A 上的 waiter 原子地转移到 futex_B 的 PI 等待链上,典型场景是 pthread_cond_broadcast 底层。涉及两个操作:

由于 B 是 PI futex,waiter 会被挂入 B 的 rt_mutex PI 等待树,参与优先级继承。

PoC(IonStack/CVE-2026-43499/poc/poc.c)用三个 futex 字和三个线程构造一个 PI 死锁:

requeue 后,waiter 被挂入 f_pi_target 的 PI 等待树。但 owner 持有 f_pi_target 且在等 f_pi_chain,而 waiter 持有 f_pi_chain 且在等 f_pi_target —— 死锁

waiter 的 WAIT_REQUEUE_PI 带了 5 秒超时。超时到期时,内核必须把 waiter 从 f_pi_target 的 PI 树上摘除并回滚 PI 链。这段超时清理路径引用了已经无效的内核栈内存:waiter 在等待期间其 task 栈可能被释放/复用,而清理代码仍按栈上保存的指针去解引用、去操作 PI 树,形成 内核栈 UAF

关键性质:

PoC 的 stamp_* 系列函数演示了多种栈控制途径:

完整利用实际走的是 pselect6 路由(do_pselect_fake_lock_route):pselect 的 fd_set 参数直接在内核栈上,攻击者把伪造的指针铺进 fd_set,超时清理路径读到这些指针就完成了"栈 UAF 可控化"。

Pixel 10 内核开启了 CONFIG_CFI_CLANG,间接调用会校验目标函数签名,不能跳任意 gadget。作者用"伪造结构体里每个函数指针都指向同签名合法内核函数"绕过:

有了 configfs 任意读写后,通过 kmalloc_caches 定位 pipe 对象所在的 kmalloc-cg-2k slab,做 slab 风水:释放 pipe_buffer、用受控页回收、伪造 pipe_buffer->ops,把 page 字段指向任意物理页的 struct page,从而得到任意物理地址读写(pipe_phys_read_data / pipe_phys_write_data)。后续的 cred/seccomp/selinux patch 全部基于这个物理读写原语。

这一点在适配时非常关键。最初我以为 slide 是暴力枚举 KASLR 范围(那对高位 0xffffffe7 镜像区会是麻烦),但读 slide.c 后发现它是直接泄漏:

校验仅要求 (leaked >> 48) == 0xffff(是内核指针),与 KASLR 滑到多高无关。这决定了适配时高位镜像区不是障碍。

IonStack 的偏移是编译期注入,通过 per-固件头 src/targets/<project>/target.h 参数化:

offset.h 只是个壳(#include TARGET_CONFIG_H)。make PROJECT=<project> 选定 target。futex GhostLock 触发本身不需要偏移;从 KASLR 泄漏开始的每一步都依赖 target.h

适配就是为本机生成一份正确的 target.h。偏移分三类:

设备已 Magisk root,这极大简化了侦察。一条条来:

kallsyms(需 root,否则空):

关键认知:_OFF 偏移 = kallsyms 运行时地址 - _text 运行时地址,KASLR slide 会抵消。所以从 kallsyms 直接就能算出 target.h 里的 A 类偏移,不需要链接时基址。

config.gz: 确认 CONFIG_ARM64_VA_BITS=39CONFIG_CFI_CLANG=yCONFIG_RANDOMIZE_BASE=yCONFIG_BPF_JIT_ALWAYS_ON=yCONFIG_USERFAULTFD=y、4K 页。这些与 Pixel 10 的 target 假设基本一致 —— 利好。

BTF: /sys/kernel/btf/vmlinux 可读(6MB),拉到本地用 bpftool btf dump file vmlinux.btf 解析,得到所有结构体字段偏移(B 类)。

iomem: Kernel code: 80010000-... → 内核物理加载在 0x80010000(Pixel 是 0x80000000)。

用一个 python 脚本批量计算(相对 _text = 0xffffffe71b800000):

ashmem 完整存在是重要利好 —— 现代 Android 在弃用 ashmem,但本机仍保留 fops + 全部成员函数。两个"地址是字段而非符号"的特例:

bpftool btf dump file vmlinux.btf + python 解析,得到:

task_struct(只列关键字段):

cred: uid +0x8 securebits +0x28 caps +0x30 security +0x80
pipe_inode_info / pipe_buffer / file_operations / seccomp / struct page / miscdevice / selinux_state: 全部按本机 6.6 布局确认。

对比 comet target.h,发现两处必须换本机值的差异:

file_operations 那 5 个偏移若用 Pixel 的值,leak_kernel_base 会读错函数指针、伪造 fops 表也会填错槽位 —— 直接崩或过不了 CFI。这是适配中最危险的一处,也证明了"照搬 target.h 不可行"。

39-bit VA 的线性映射基址由公式确定:PAGE_OFFSET = -(1UL << 39) = 0xffffff8000000000(与 Pixel 一致),VMEMMAP_START = 0xfffffffe00000000。结合 iomem:

SLIDE_RANDOM_BOOT_ID_DATA(slide KASLR 泄漏的着陆点)是 random.c 里的 static 变量,不在 kallsyms(flat Image 也没有独立符号表)。这是适配中唯一的真缺口。

解法:用 IDA 读 random_table IDA 加载了 flat Image(/home/zzz/AI/CyberMeowfia/split_img/kernel.i64,base 0,_text@0x0,_stext@0x10000)。flat Image 由 boot.img 解包还原,解包与内核符号/地址还原方法来自笔者博客《解包小米11的bootimg还原内核符号以及地址》。关键映射:IDA 地址 = kallsyms 偏移(相对 _text)

random_table(off 0x2229400)是 struct ctl_table 数组(每项 0x40 字节,data 字段在 +0x8)。读 512 字节解析 8 个表项,找到 proc_handler = proc_do_uuid(0xffffffc0808b83b0)的项:

data 字段 = 0xffffffc082236a0d8,减去链接基址 0xffffffc080000000 = 0x236a0d8 —— 这正是 kallsyms 里 sysctl_bootid 符号的偏移!即 boot_id 数据缓冲区就是 sysctl_bootid 变量,SLIDE_RANDOM_BOOT_ID_DATA_OFF = 0x236a0d8。读 procname 字符串(在 IDA 0x15bcffc)确认为 "boot_id",闭环。

顺带推出链接时 _text = 0xffffffc080000000(由 proc_handler 指针 0xffffffc0808b83b0 - proc_do_uuid 偏移 0x8b83b0 得到,非标准 0xffffffc008000000,OnePlus 改过)。

关于 KIMAGE_TEXT_BASE 的抵消:这个值在 exploit 的 image/slide 数学中其实会抵消 —— 因为所有"image 地址"都定义为 KIMAGE_TEXT_BASE + OFF,而运行时通过 text_addr(x) = x + slideslide = kaslr_base - KIMAGE_TEXT_BASE 转换,KIMAGE_TEXT_BASE 在加减中消去,p0_data_alias(image_addr - KIMAGE_TEXT_BASE) 也恒等于 OFF。所以理论上任何一致值都可用,但用真值最干净。

这是适配中最关键的正确性判断。读 slide.c + BTF 对比:

rt_mutex_waiter 本机布局(BTF):

slide 的 pselect 伪造按 "word i @ 偏移 i*8" 铺设:

与本机 rt_mutex_waiter 完全吻合 ✓。主路由用到的 FAKE_WAITER_PI_TREE_*(common.h:0x28/0x40/0x50/0x58/0x60/0x68)也匹配;而非 FAKE 的 WAITER_* 在代码里根本未被使用(dead code)。结论:slide KASLR 泄漏和主路由 fops 劫持的 waiter 伪造在本机 6.6 内核上布局兼容,无需改 common.h。

所有缺口解决,生成 src/targets/PLC110-BP2A.250605.015/target.h,无 TODO(仅 PSELECT_WAITER_WORD_SHIFT=1 标注运行时可调)。

target.h 偏移已全部验证正确,完整利用运行受阻于 OnePlus 13T hardened 内核配置(非偏移问题)。

运行现象:完整利用以 shell 用户(uid 2000)LD_PRELOAD 注入运行,~36–45s 后内核 UBSAN panic 重启。崩在 slide 阶段(slide_leak_kernel_base fork 的子进程 consumer 的 sched_setattr 触发 PI 链),没进 main route,slide 没成功泄露 KASLR

崩溃链:wake_up_state(init_task)try_to_wake_upinit_task->pi_lockqueued_spin_lock_slowpath UBSAN array bounds(lock 值 0x5512xxxx 非法)→ BRK #0x5512 → OnePlus mrdump ipanic_die 强制 panic → 重启。

根因(OnePlus 13T vs Pixel 内核配置差异):

Pixel 未开 UBSAN_TRAP,slide 的 PI 链即便触发 UBSAN 也只 warn 继续,故 slide 可用;OnePlus 直接触发 BRK → 必崩。echo 0 > /proc/sys/kernel/panic_on_oops 验证无效(mrdump 绕过)。

未解之谜:静态分析 init_task.pi_lock 静态值 = 0(IDA 确认,pid@0x618=0 验证是 init_task),地址推导全对(39-bit VA、无 KASAN、标准线性映射、phys 落在 Kernel data 段),但运行时 slowpath 读到非法 lock 值。无法静态解释,疑动态因素(线性映射运行时差异 / 竞争 / TLB),需 kgdb 确认 0xffffff800211e780 运行时实际映射的值。

可能的推进方向(待选):

本次适配沉淀出一套可复用的内核利用跨设备移植流程:

最值得记取的教训:结构体字段偏移在不同内核版本间会静默变化(file_operations 在 6.6 与 Pixel 的内核间 ioctl 偏移差 8 字节,task_struct.pi_lock 差 0x18),这类"隐性偏移"不会在 target.h 里显式标注(它们在 common.h 或代码逻辑里),却会直接导致崩溃或 CFI 失败。适配时必须用 BTF 逐一核对,而非只关注 target.h 里的符号偏移。

另一个同等重要的教训:hardened 内核配置会静默阻断利用链CONFIG_UBSAN_TRAP 把原本只 printk warn 的 UBSAN 变成 BRK 指令,配合 CONFIG_PANIC_ON_OOPS 与厂商 panic 模块(OnePlus 的 mrdumpipanic_die 强制 panic),会让某条子技术链路(本次是 slide 的 PI 链 wake_up_state -> try_to_wake_up -> slowpath)在 Pixel 上能跑、在本机一触即崩,且 echo 0 > /proc/sys/kernel/panic_on_oops 也绕不过。跨设备移植时,除偏移外必须核对 config.gz 里的 sanitizing / hardening 选项(UBSAN_TRAPBUG_ON_DATA_CORRUPTIONPANIC_ON_OOPS、厂商 mrdump/kip 等),它们决定哪些子技术可用,可能迫使改换泄露原语(如 slide -> KernelSnitch)。

⚠️ 本文所述技术仅用于授权安全研究与自有设备测试。GhostLock 是影响所有 Linux 发行版的内核栈 UAF,正确修复应从内核 futex PI requeue 超时清理路径入手,确保不引用已释放/复用的 task 栈内存。

原目标 (Pixel 10 / comet) 适配目标 (OnePlus 13T)
机型 Pixel 10 OnePlus 13T (PLC110)
Android 17 16
内核 android16-6.x 6.6.89 GKI (android15-6.6)
VA_BITS 39 39
页大小 4K 4K
CFI
镜像区 VA 0xffffffc0... 0xffffffe7...(KASLR 滑到高位)
root 无需 Magisk root
途径 原理
stamp_prctl PR_SET_MM_MAPauxv 指向受控共享内存(memfd+fallocate punch hole 制造抖动)
stamp_socket MCAST_JOIN_SOURCE_GROUP
stamp_pselect pselect6 的 fd_set 直接铺在栈上
stamp_process_vm process_vm_readv/writev 的 iovec 数组
stamp_keyctl KEYCTL_DH_COMPUTE / KEYCTL_INSTANTIATE_IOV
stamp_tcp TCP_ZEROCOPY_RECEIVE
stamp_futex futex PI 本身的栈使用
类别 内容 获取方式
A 内核符号偏移(相对 _text) root /proc/kallsyms
B 结构体字段偏移 BTF(/sys/kernel/btf/vmlinux)+ bpftool
C 架构/内存布局常数 /proc/config.gz + /proc/iomem + 推理
缺口 boot_id 静态变量(不在 kallsyms) IDArandom_table
字段 Pixel (comet) 本机 6.6 影响
file_operations.unlocked_ioctl 0x50 0x48 leak_kernel_base 读错函数指针
.open 0x70 0x68 伪造 fops 表填错槽
.release 0x80 0x78 同上
.splice_read 0xc0 0xb8 同上
.show_fdinfo 0xe0 0xd8 同上
task_struct.pi_lock 0x924 0x90c 伪造 task PI 字段错位
pi_waiters/top_task/blocked_on 0x938/0x948/0x950 0x920/0x930/0x938 同上
P0_KERNEL_PHYS_LOAD 0x80000000 0x80010000 物理别名算错
f_wait, f_pi_target, f_pi_chain

owner 线程:   lock(f_pi_target); lock(f_pi_chain);  // 持有 target,等 chain -> 阻塞
waiter 线程:  lock(f_pi_chain);                      // 持有 chain
              WAIT_REQUEUE_PI(f_wait -> f_pi_target, timeout=5s)  // 等 requeue 到 target
主线程:       CMP_REQUEUE_PI(f_wait -> f_pi_target)  // 把 waiter requeue 到 target 的 PI 链
slide_leak_kernel_base()                    # ① KASLR 泄漏
   └─ GhostLock 栈 UAF + pselect 伪造 rt_mutex_waiter
   └─ 内核把 nfulnl_logger 运行时地址写进 boot_id 缓冲区
   └─ 读 /proc/sys/kernel/random/boot_id 解析出地址 -> stext
prepare_good_kernel_page(PAGE_PAYLOAD_FOPS) # ② 布置伪造 fops 页
run_main_route_threads()                    # ③ 主路由:GhostLock + pselect -> fops 劫持
   └─ try_cfi_stage()
      └─ 改 ashmem_misc.fops 指向伪造 fops 表(CFI 友好)
      └─ 得到 configfs 一次性任意内核读写
      └─ leak_kernel_base:读 ashmem_fops 函数指针算内核基址
      └─ install_pipe_physrw:伪造 pipe_buffer->ops -> 任意物理地址 r/w
install_android_root()                      # ④ 提权
   └─ 遍历 init_task.tasks 找 root 子进程
   └─ patch cred: uid/gid->0, caps->full, securebits->0
   └─ patch SELinux SID -> kernel_sid
   └─ patch seccomp:清 TIF_SECCOMP / mode / filter(绕过 Android 应用 seccomp)
   └─ 写 selinux_enforcing = 0
   └─ root child: setuid(0) -> 装 su daemon -> 换壁纸
TARGET_CFLAGS := -DTARGET_CONFIG_H=\"targets/$(PROJECT)/target.h\"   # Makefile:84
# adb shell su -c cat /proc/kallsyms | head
ffffffe71b800000 T _text          # 运行时内核基址
ffffffe71b810000 T _stext
noop_llseek              0x3d19f4
copy_splice_read         0x41ec54
configfs_read_iter       0x49a434
configfs_bin_write_iter  0x49a960
ashmem_ioctl             0xc8de34
compat_ashmem_ioctl      0xc8e4f0
ashmem_mmap              0xc8e544
ashmem_open              0xc8e764
ashmem_release           0xc8e7ec
ashmem_show_fdinfo       0xc8e878
ashmem_fops              0x12dc4d8
anon_pipe_buf_ops        0x115c0c8
kmalloc_caches           0x1664e10
security_hook_heads      0x16652d0
selinux_blob_sizes       0x1665a08
nfulnl_logger            0x2102748
loggers (nf_loggers[])   0x2102690
init_task                0x210e780
init_uts_ns              0x2292ed0
empty_zero_page          0x22fe000
root_task_group          0x2306580
selinux_state            0x23490e0
ashmem_misc              0x226c118
sysctl_bootid            0x236a0d8
random_table             0x2229400

[内核课程]《Windows内核攻防实战》!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 0
打赏
分享
最新回复 (3)
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
2
6
7小时前
0
雪    币: 220
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
3
牛啊,看看怎么搞
7小时前
0
雪    币: 208
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
4
6
2小时前
0
游客
登录 | 注册 方可回帖
返回