首页
社区
课程
招聘
[原创]一台"锁死"的 Pixel 7 自救记:把 GhostLock 内核漏洞移植到真机 root
发表于: 21小时前 1411

[原创]一台"锁死"的 Pixel 7 自救记:把 GhostLock 内核漏洞移植到真机 root

21小时前
1411

关键词:CVE-2026-43499 / GhostLock / futex / 内核提权 / Pixel 7 / 无 root 侦察 / KASLR
适用读者:手里有一台 bootloader 回不去、想救回来的测试机的人;或者想把一个内核 exploit 移植到自己设备上的人。
前置要求:会 adb,看得懂一点 C,愿意接受手机重启几十次。


0. 事情的起因

我有一台 Pixel 7 测试机(欧版)。它之前是解锁 + 自刷 AOSP + Magisk root 的状态。某次我用谷歌官方网页刷机工具刷了个安卓 17 测试版,刷完之后:开发者选项里的"OEM 解锁"开关彻底变灰,回不去了

没有 root 的测试机,对我来说就是一块好看的砖头。而且因为是测试机,里面什么都没有——随便折腾,大不了重启。

正好看到 Nebula Security 公开的 IonStack 利用链里有一个 2026 年 7 月披露的内核漏洞 GhostLock(CVE-2026-43499),以及吾爱破解上一篇把它适配到一加 13T 的文章。这个漏洞理论上存在于几乎所有 2011 年之后的 Linux 内核(安卓当然在内),而且利用它不需要任何权限——只要能跑一段自己编译的程序(adb shell 就够)。

于是就有了这个项目。最终成果:在这台 bootloader 锁死的 Pixel 7 上,通过漏洞拿到了 uid=0 + kernel 域 + 全局 SELinux permissive 的真 root,虽然重启会掉(tethered),但测试机够用了。

这篇文章记录完整过程,重点写我踩过的、前面两篇文章里都没有写到的坑。如果你想把一个内核 exploit 移植到自己的设备,这些坑大概率你也会遇到。


先解释几个词:

GhostLock 的 bug 出在内核 rtmutex.cremove_waiter() 函数:它清理一个"排队者"时,认错了人——把发起 requeue 的线程的标记清了,真正排队的那个线程的标记没清。于是一个本该死掉的 rt_mutex_waiter 结构体,就这么"幽灵"一样地挂在优先级树上,指向一块已经作废的内核栈。

怎么触发(只要三个 futex 变量 + 三个线程,全是普通权限):

内核一查:waiter 等 target,target 在 owner 手里,owner 等 chain,chain 在 waiter 手里——死锁! 于是内核走"回滚"流程,回滚里就踩中了这个 15 年的 bug,waiter 的标记没被清掉。

之后,waiter 线程回到用户态,它那页作废的内核栈就可以被我们"重印":随便挑一个带大块栈缓冲区的系统调用(pselect、prctl、TCP zerocopy 都行),把伪造的 waiter 结构写回去。然后另一个线程对 waiter 调一下 sched_setattr(改个 nice 值,普通权限),内核就会沿着这个幽灵指针走一遍优先级链——读到的全是我们伪造的内容。伪造得当,红黑树的一次删除操作就能变成往内核任意地址写一个指针

后面的事就顺理成章了:先改掉 boot_id 系统参数的指针,读回来就泄露了内核基址(破 KASLR);再改掉 ashmem 设备的函数表(配合 configfs 的合法函数绕过 CFI),拿到内核任意读写;最后 patch 自己进程的 cred,uid 变 0。

理论上是完美的。难的是落地。 原利用只支持 Pixel 10(安卓 17);吾爱破解那位作者适配了一加 13T(6.6 内核,但他有 root);我的 Pixel 7 是 6.1.145 内核,没有 root、bootloader 锁死、调试通道全被封。以下是真正的移植实战。

适配 exploit 需要三类数据:内核符号地址(相对 _text)、结构体字段偏移(task_struct/cred/rt_mutex_waiter 这些)、内存布局常数(物理加载基址之类)。

一加作者是真机 root 后直接读 /proc/kallsyms 和 BTF。我连 root 都没有,shell 下这些文件全部 Permission denied。

但 Pixel 是谷歌亲儿子:官方工厂镜像公开下载,镜像里就有完整内核。 流程:

到这里,全部偏移在没有 root 的情况下拿齐了,而且因为是同一份镜像,偏移天然精确。

dl.google.com 的工厂镜像路径对我的出口 IP 直接 429(反爬)。另外谷歌中国镜像 googledownloads.cn 同样 429,别浪费时间,直接浏览器搜 panther-bp4a.251205.006-factory-4455f800.zip 最终在印度佬的网站下到了 废了好大力气!

漏洞 2026 年 5 月修复,6/7 月进安卓补丁。我的机器停在 2025 年 12 月补丁,理论上在。但"理论上"不作数,要实测。

社区有个现成的检测 App(CakesTwix/Android-CVE-2026-43499),装上一跑:手机当场内核 panic 重启

听起来吓人,其实是好消息:panic 说明漏洞路径可达且存在。测试机重启而已,无所谓。

⚠️ 这一步开始,你的手机会经历很多次 panic 重启,这是内核 exploit 开发的日常。别把重要数据放上面。

NebuSec 的仓库里,通用链是给 Pixel 10(6.6/6.12 内核)的;但 targets 目录里藏着一个 oriole(Pixel 6)的独立完整版——Pixel 6 和我的 Pixel 7 同属 android14-6.1 GKI 分支,结构体布局几乎一样,而且它是"从 adb shell 直接跑"的独立可执行文件,正好符合我的处境。

所以路线定为:以 oriole 版为模板,只换 panther 的偏移和布局常数

接下来的五个坑,一个比一个隐蔽,全部是真机实测 + 大量推理才定位的。

exploit 里有个叫 KernelSnitch 的时序侧信道,用来在内核堆里找到我们自己的"工作区页面"(伪造 waiter 要放在一个地址已知的内核页面上)。原理很巧:内核 futex 哈希表的桶下标混入了 mm_struct 的地址,通过测量 futex 唤醒的耗时差异,可以反推出这个地址。

它暴力反推时按 sizeof(mm_struct) 在 slab 页里步进。我从 BTF 里读到的结构体大小是 960 字节,填进去——找不到。

原因:slab 里的对象步长不等于结构体大小mm_struct 的缓存带 SLAB_HWCACHE_ALIGN,要按 CPU 的 cache 写回粒度(CWG)对齐,ARM64 上通常是 128 字节。960 补齐到 128 的倍数 = 1024。oriole 模板里写的本来就是 1024,我"自作聪明"改成 960 反而错了。

教训:BTF 告诉你结构体逻辑大小,slab 步进是对齐后的物理大小。内核里这两者经常不一样。

修好步进后,kernelsnitch 能找到工作区了,漏洞触发也成功了(每次 requeue 都稳定返回 EDEADLK),栈喷洒的伪造 waiter 也被内核读了(故意写个垃圾指针进去,手机立刻 panic——说明读到了)。但红黑树那次关键的"写"就是不发生。

没有任何内核日志通道(dmesg/pstore 全部要 root),怎么办?我的办法是把"会不会崩"当成探针:

没有 kgdb 的时候,"崩不崩 + 崩在哪一步"就是你唯一的调试器。

这是全文最绕的一个坑,我尽量简单说。

那次关键写入发生在内核调整优先级链的 [11] 分支:只有当被伪造的 waiter 成为锁上"最高优先级排队者"时,内核才会执行那次删除-写入。

问题在于:回滚时内核在旧 waiter 的红黑树节点上留了个"已删除"的自闭环标记(RB_EMPTY_NODE)。oriole 上,栈喷洒会把这个标记恰好盖掉;在我的内核构建上,TCP zerocopy 的栈帧错位了 8 字节,这个标记盖不掉,于是一个清理路径直接跳过,伪造 waiter 永远当不上"最高优先级",写入永不发生。

解法简单粗暴但有效:把伪造树里另一个节点(w0)的优先级写成 1000(超级大、超级不优先),这样我们的伪造 waiter 怎么排都能赢,[11] 分支必然触发,写入落地。

教训:同一个栈喷洒原语,换个编译器版本/小版本号,栈帧布局就错位几字节。这类"隐性偏移"不在任何头文件里,只能真机试。

破 KASLR 的第一步(slide 泄漏)用的是线性映射别名(物理内存的固定虚址映射,不受 KASLR 影响),它依赖"内核镜像被加载到哪个物理地址"。业内惯例是 DRAM 起点 + 0x10000,一加那台实测也是这个值,我就照抄了。

结果:所有前置条件都对、写入也触发了——然后直接 panic。因为那一步写操作的落点错了 0x10000,戳进了内核代码段(只读),一写就崩。

真值是多少?0x80000000——Pixel 7 的 bootloader 严格按内核 Image 头的 text_offset=0 把内核放在了 DRAM 起点。修正后,泄漏一次成功:从 /proc/sys/kernel/random/boot_id 读出一串"假的 UUID",解码就是内核基址。

教训:物理布局三件套(DRAM 基址、内核物理加载点、线性映射基址)必须按本机核实,不能照抄。验证手段:设备树节点名(/sys/firmware/devicetree/base/memory@80000000)+ 镜像头 text_offset + 真机试跑。

最气人的一个:链路第二阶段的"栈喷洒 vs sched_setattr 链走"是个亚毫秒级竞态。我为了调试,在 consumer 线程每次触发时加了一行 printf——结果 100% 不成功了。因为这一行 print 的几毫秒延迟,刚好让竞态窗口错开。

去掉热路径 print,立刻恢复成功。

教训:竞态型 exploit 的调试,只能加"一次性"日志(初始化、阶段切换),绝不能在每次循环里打印。观察行为本身会改变被观察对象——内核 exploit 界的测不准原理。

安卓 16 起,ashmem 设备节点名变成了 /dev/ashmem<本次开机的boot_id>。而 slide 泄漏的第一步恰恰会临时污染 boot_id……于是第二轮运行时,按 boot_id 拼出来的设备路径就不存在了,直接"no usable ashmem device"。

解法:别拼名字,直接扫 /dev/ashmem*

修完所有坑,exploit 全流程跑通:

我在 exploit 末尾加了个小守护进程(rootd),监听 127.0.0.1:7777,谁连上就给谁一个 root shell。又写了个 127.0.0.1 的本地网页终端,浏览器里直接敲命令。重启后跑一遍 reroot.sh,两分钟恢复 root。

诚实交代:这是 tethered root,重启即失。想要持久,得解锁 bootloader 刷 Magisk;而我的 bootloader 翻不回来。

root 之后我做了这些尝试:

所以永久解锁这条路目前卡在一颗安全芯片的签名上。剩下的思路(留给有兴趣继续的人):

如果你要把一个内核 exploit 移植到自己的设备,这是我的清单:

本文只针对我自己的测试设备进行研究。漏洞信息均已公开多时并已有官方修复。请不要把这套方法用于任何不属于你的设备。



冰与火的战歌:Windows内核攻防实战高级班!从零到实战,融合AI与Windows内核攻防全技术栈,打造具备自动化能力的内核开发高手。

收藏
免费 46
打赏
分享
最新回复 (11)
雪    币: 7237
活跃值: (7395)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
2
66
19小时前
0
雪    币: 109
活跃值: (690)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
3
666
18小时前
0
雪    币: 1560
活跃值: (5678)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
4
牛逼啊
17小时前
0
雪    币: 200
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
5
tql
16小时前
0
雪    币: 10990
活跃值: (7193)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
6
这个安卓的提权漏洞,直接把小米的BL锁干碎了,但是这CVE 对X86设备的exp 极少极少,是利用比较难吗?
12小时前
0
雪    币: 0
活跃值: (5816)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
7
学到了.点个 赞
12小时前
0
雪    币: 1219
活跃值: (1555)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
8
666
4小时前
0
雪    币: 1598
活跃值: (2225)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
9
666
3小时前
0
雪    币: 2005
活跃值: (570)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
10
残废小菜比 这个安卓的提权漏洞,直接把小米的BL锁干碎了,但是这CVE 对X86设备的exp 极少极少,是利用比较难吗?
说不定是小米漏出来的,不能解锁可玩性就很低了
3小时前
0
雪    币: 5042
活跃值: (5741)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
11
太黑客了
2小时前
0
雪    币: 277
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
12
666
42分钟前
0
游客
登录 | 注册 方可回帖
返回