一加 15 锁 Bootloader root:GhostLock 与 CVE-2026-43499

本页分析 CVE-2026-43499——Linux 内核 futex 优先级继承(Priority Inheritance, PI) 的 use-after-free,以及基于它的 Android 提权利用链,聚焦一加 15(OnePlus 15)。 围绕这个漏洞目前有多个开源实现:

  • [p2p3p/GhostLock-for-OnePlus][1]:上游通用版本,支持一加 Ace 6T / 一加 15, 完整利用链 + KernelSU 持久化;
  • [cuteaplane/GhostLock-for-OnePlus15T][2]:在[1]基础上适配一加 15T(PLZ110), 作为同机型族的对照实现;
  • [bartixxx32/CVE-2026-43499-OnePlus15][3]:另一支 fork(源自 x-spy 的 CVE-2026-43499-popsicle),面向一加 15 的 LD_PRELOAD 版 PoC,附带偏移自动检测 与 target.h 生成工具;
  • [d1667018881/myroot][22]:把"拿 shell"也交给浏览器的纯网页版——先用 Firefox JIT 漏洞(CVE-2026-10702)拿到 shell,再用同一套 GhostLock 内核提权,做到免安装、 重启即恢复。

这些实现的核心漏洞与利用原语相同,差异在适配机型、投放方式与工具链。

⚠️ 上游仓库都明确声明仅限授权的安全研究与教育用途,且不提供预编译二进制。

本页仅作安全研究记录,所有源码引用 pin 到具体 commit。

❗ 受影响范围速查(先看这段):CVE-2026-43499 影响 Linux 2.6.39 ~ 7.0.x 之间的内核;在 Android 上换算成 Kernel 5.x / 6.x 的 GKI 分支——即搭载 Android 12 ~ Android 16 的主流设备基本都在窗口内。判断某台机器是否可利用, 不看 Android 版本号,只看它的内核版本 + 补丁级(见下表 0.0)。

表 0.0|内核分支的修复状态[29]

内核分支 已修复起(unaffected) Android 侧对应
< 2.6.39 —(漏洞尚未引入) 极老设备
2.6.39 ~ 5.10.260 5.10.261 起 Android 6~10(少见)
5.15.x 5.15.212 起 Android 12~13(GKI 5.15)
6.1.x 6.1.175 起 Android 14(GKI 6.1)
6.6.x 6.6.140 起 Android 15(GKI 6.6)
6.12.x 6.12.86 起 Android 16(GKI 6.12,一加 15 所在分支)
6.18.x 6.18.27 起 新一代 GKI
7.0.x 7.0.4 起 —(主流修复已入 7.1)

关键含义:只要设备的内核版本低于表中"已修复起"那一列,且在运行期满足偏移表/ do_futex 调用链条件,就理论上可被利用;反之内核一旦升到修复版(Android 侧随 OEM 月度安全公告下发),此路即封死。注意 GKI 6.12.x 到 2026 年年中仍普遍未修[25], 所以运行 Android 16(GKI 6.12)的一加 15 反而正是受影响的重灾区。

0. 核心问题:一加 15 现在能做什么?

本章直接回答两个问题——(1)一加 15 现在能不能做临时 root?(2)临时 root 之后能否 正确解锁 Bootloader 再永久 root?这两个阶段各自是否完整? 结论先给:

表 0|一加 15 两个阶段的当前状态

阶段 目标 现状 是否完整
阶段 1:临时 root 不解锁 BL,拿到本次开机有效的 uid=0 ✅ 可行(内核/机型/补丁级满足条件时) 链条完整,但概率性、不持久
阶段 2:永久 root 解锁 BL → 刷入受信 init_boot/内核 ⚠️ 需要独立的 BL 解锁,GhostLock 帮不上忙 两阶段不衔接,存在结构性断层

0.1 阶段 1:临时 root —— 现在能做,但不是"万能"

一加 15 确实在 GhostLock 的支持列表里,多个来源给出已验证的内核:

表 0.1|一加 15 已被收录的内核版本

来源 机型标识 内核版本 备注
[p2p3p/GhostLock-for-OnePlus][1] OnePlus 15(PLK110) 6.12.23-android16-5-…-ab14541642-4k ✅ 已验证
[JoinChang/ghostlock-oneplus][25] OnePlus 15(CPH2745/2747/2749) 6.12.23(android16-5) 上游可用报告
[bartixxx32/CVE-2026-43499-OnePlus15][3] OnePlus 15 有已验证 profile_oneplus15.json LD_PRELOAD 路线
[d1667018881/myroot][22] OnePlus 15(a8f88 / b2a87) 6.12.23-android16-5-ga8f88ad96df3-… / …gb2a876903b49-ab14541642-4k 网页版设备表

但"能"有严格前提,任何一条不满足就失败:

  1. 内核版本必须在偏移表内,且该内核的 do_futex 调用链未被 PGO 内联破坏。 [JoinChang/ghostlock-oneplus][25] 明确列出:一加 15 的部分内核(如 PLK110 的 6.12.69、6.12.58)因 PGO 把 do_futex 内联,"waiter word"偏移变化, 被标记为对该路线不可行(infeasible)。也就是说同一机型不同固件版本,结论可能相反。
  2. 补丁级必须早于修复。CVE-2026-43499 在主线 7.1(commit 3bfdc63936dd)修复, 稳定分支在 2026 年 5 月回合;Android 侧要等 OEM 的月度安全公告。 GKI 6.12.x 到 2026 年年中仍普遍未修[25]。
  3. 投放方式决定是否需要额外条件:ADB shell 路线要能开 adb tcpip;App 自举路线要 能装/运行 exploit App;网页路线要求 Firefox for Android ≤ 151.0[22]。
  4. 它是"每次开机重跑"的临时 root:没有任何持久化,重启即失效; 重跑成功与否还受 LMK、后台 cgroup、绑核时序影响(见 §3.3.3 的踩坑表)。
📝 还有一条与本漏洞无关的一加 15 临时 root 路线:安全研究者 Rasmus Moorats

用 OxygenOS 的 AtlasService.setEvent 命令注入 + olc2 HAL doShell 组成的 untrusted_app → uid 0 链[30],普通 App 无特殊权限即可触发。 但它已在一加 15 的 16.0.10.500(EX01) 固件中修复(OnePlus 已于 2026-08-18 推送), 且它拿到的是 vendor_qti_init_shell 域(全 capability),仍属临时 root。 列在此处是为了说明:一加 15 的"临时 root"不止 GhostLock 一条路,但都受补丁级约束。

0.2 阶段 2:临时 root → 解锁 BL → 永久 root —— 断层,不衔接

这是最关键的一点,也是两张"阶段"不完整的地方:

① 临时 root 不能用于解锁 Bootloader。 GhostLock 类的漏洞只改内核内存,不碰 Bootloader、不碰 verified boot 链。 第三方权威整理明确写道,GhostLock "is not a bootloader unlock"、 "cannot flash anything",自定义 ROM / recovery / 内核都需要解锁 BL[26]。 换句话说,阶段 1 拿到的 root 无法绕过 fastboot flashing unlock; OnePlus 的 BL 解锁仍走官方流程:

设置 → 关于本机 → 连点版本号 → 开发者选项 → 打开 OEM 解锁
adb reboot bootloader
fastboot flashing unlock    # 会清空数据
⚠️ 关键约束(版本差异):

② "永久 root"的两种含义要分清。 平台上流传的"一加 15 不解锁 BL 也能(root 并)锁着 BL"的说法(如 DroidWin 的标题), 实质是 GhostLock 临时 root + 一个软重启(soft reboot)保活的技巧, 不是真正的永久 root:

  • 它靠 ReSukiSU 的 Soft Reboot 在"不冷启动"的前提下让模块生效; 一旦冷重启(normal reboot),root 立即消失,必须重跑 exploit[26]。
  • 它不能挂载持久化的自定义内核/recovery,也不能改系统分区 (锁 BL + AVB 会阻止),否则可能变砖[26]。

真正意义上的永久 root,仍是"解锁 BL → 打补丁的 init_boot / 自定义内核 → 刷入" 的传统路线(KernelSU / Magisk / APatch)[26][28]。这条路线与 GhostLock 无关—— GhostLock 只是让你在还没解锁时先体验一次 root。

③ 两阶段之间没有官方桥接。 当前没有任何公开工具把"临时 root"当作"解锁 BL"的跳板:临时 root 的 uid=0 权限 不足以写入 efisp / ABL 分区去伪造 Fastboot(这也是锁 BL 的意义)。 一加 15 上确实存在 GBL / superfastboot(efisp 分区刷入 *.efi) 这类 "锁 BL 下获得 Fastboot 能力"的玩法[28],但那需要先解锁 BL 或用其它漏洞取得的 刷写能力,不是从 GhostLock 临时 root 直接过渡过去的。

0.3 小结:完整性判断

  • 阶段 1(临时 root):链条完整、现在可用,但受"内核版本 + 补丁级 + 投放条件" 三重约束,且天生不持久(每次开机重跑)。它适用于"只想偶尔用 root、 又不想丢 BL 锁 / 不想被应用检测到已解锁"的场景[26]。
  • 阶段 2(永久 root):解锁 BL 是独立步骤,GhostLock 无法替代。 因此"临时 root → 永久 root"不是一条连续可利用的链条,两个阶段之间存在 结构性断层。想永久 root,请直接走官方解锁 + 刷机路线;想保留 BL 锁, 就只有临时 root,二者不可兼得。
  • 随时间推移,阶段 1 的窗口在收窄(OEM 持续打补丁),而阶段 2 一直依赖 BL 解锁政策(国行受 Deep Testing 约束)。不要把 GhostLock 当成永久方案[26]。

1. 漏洞成因:futex PI 的 waiter UAF

1.1 futex 与优先级继承

futex(fast userspace mutex)是 Linux 上实现用户态锁的内核原语:无竞争时锁在用户态 自旋解决,只有需要睡眠/唤醒时才陷入内核。它有一个 PI 变体(FUTEX_LOCK_PI / FUTEX_WAIT_REQUEUE_PI 等),用于优先级继承:当高优先级线程需要在低优先级线程 持有的锁上等待时,内核临时把持有者的优先级抬到等待者水平,避免优先级翻转。

PI 机制的实现依赖两个内核结构:

  • rt_mutex:PI 化的互斥锁本体;
  • rt_mutex_waiter:挂在锁的等待队列(内部是红黑树,rb-tree)上的"等待者"节点, 每个在 PI 锁上睡眠的任务对应一个 waiter。

当锁的 owner 变化、任务被 requeue 时,内核会遍历这条 PI 链,并对 waiter 所在的 rb-tree 做插入 / 删除 / 重平衡(rebalance)——重平衡过程会写入节点指针字段 (如 __rb_parent_color)。CVE-2026-43499 的问题就在于:这个被遍历/改写的 waiter 对象,其内存可能已经被释放并复用。

1.2 pselect6 把 fd_set 放到内核栈上

pselect6 / select 系统调用需要把用户传入的 fd_set(fd_set 是定长位图, FD_SETSIZE = 1024,即 128 字节)拷贝到内核栈上处理。关键点在于:

  • 这份栈上数据的大小与布局是用户可控的(fd_set 的每一位对应一个 fd,用户可任意设置);
  • 内核栈是每线程一份、复用的内存;一段栈帧被使用后,同一线程的下一次系统调用 可能在同一片地址上重新分配栈空间。

把这两点合起来:如果先让内核在某段栈地址上放好一份可控的 fd_set,随后该栈区域被 重新分配为 rt_mutex_waiter 结构体,那么攻击者就实现了对 waiter 对象的内容控制—— 即用 fd_set 的位图"拼"出一个伪造的 waiter。这正是上游 README 的"漏洞原理"描述[1][2]:

pselect6 系统调用会将 fd_set 拷贝到内核栈上。当与 futex PI waiter 机制结合时, 释放后的栈帧可以被重新分配为 rt_mutex_waiter 结构体。在 PI 链遍历过程中, 红黑树(rb-tree)重平衡操作会向任意内核地址写入可控数据。

1.3 从 UAF 到"任意内核地址写"

一旦伪造的 waiter 被内核当作真实节点挂进 PI 链,rb-tree 的重平衡写入就变成了一次 受控的任意地址写:攻击者通过伪造节点里的父/子指针字段,让内核把某个值写到 攻击者指定的地址上。这个原语在 exploit 中集中体现在 fops.c——fdset_put_word() / fdset_get_word() 用 unsigned long 数组按"字(word,ARM64 上 8 字节)"为单位精确 设置 fd_set 的每一位[4],把栈布局当作"内存写"来编排。

📝 需要强调的是:这里拿到的不是一个通用 write(addr, val),而是只能写特定值、

且时机与目标节点强相关的一次性原语。所以后面每一步(关 SELinux、改 cred)都要 单独构造一次 PI 写,而不是搭一个读写链后随意操作。

1.4 漏洞时间线

项目 内容
CVE CVE-2026-43499
类型 Futex PI(优先级继承)Use-After-Free
影响范围 Linux 内核 2.6.39 ~ 7.1
修复版本 主线内核 7.1(commit 3bfdc63936dd)
Android 状态 GKI 6.12.x 仍未修复

2. 利用原语:从任意写到物理内存读写

2.1 KASLR 绕过

在写内核地址之前,必须先知道内核基址(kaslr_base)。kptr_restrict 打开时无法直接 读内核指针,exploit 提供了两条互补路径,实现在 fops.c 与 slide.c:

  • SLIDE 模式(slide.c)— 用 boot_id 泄露:内核把 sysctl_bootid 对应数据 (/proc/sys/kernel/random/boot_id 的内容)与 nfulnl_logger 等符号地址混在 同一片区域。攻击者先用一次 PI 写把某个已知偏移的内核符号地址写进 boot_id 的 存储区,再从用户态读回该 UUID,即可反推 KASLR slide,得到 kaslr_base。 slide.c 里 SLIDE_MAX_ATTEMPTS = 20、SLIDE_WAIT_SECONDS = 30 等宏控制重试节奏[5]。
  • FOPS/CFI 模式(fops.c)— 用 ashmem fops 表泄露:当设备存在可访问的 ashmem 节点时,先拿到 ashmem 的 file_operations 表,读出 open / ioctl / mmap / release / show_fdinfo 等函数指针,与 image 内的已知偏移对齐即可算出基址。 fops.c 里 kaslr_base / kaslr_slide / kaslr_expected_* 一整套变量就是为这条 路径准备的[4]。

2.2 内核堆喷射:SKB + KernelSnitch

要在正确地址上"摆好"伪造对象,需要堆风水(heap spray)。exploit 的策略是:

  • 在 order-3(MM_ORDER = 3,即 32KB)大页上布置伪造对象(common.h 定义 MM_STRUCT_SZ 0x500、MM_ORDER 3、FAKE_* 一套字段偏移)[6];伪造对象包括 fake file_operations(劫持 ashmem 的 miscdevice fops)、fake rt_mutex_waiter、 fake task_struct、fake rt_mutex。
  • 用 SKB(socket buffer)喷射填充内核堆:sendmsg 发送 SKB_SEND_SIZE(order-3 × 2) 大小的数据、SKB_RECLAIM_SENDS 控制回收[6]。
  • 用 KernelSnitch 泄露 mm_struct 地址做辅助定位:kernelsnitch.h 通过 futex 哈希碰撞(FUTEX_SZ = 64 GB 的映射区间、KSNITCH_COLLISIONS 组碰撞) 判断某个 futex 桶是否落到目标对象所在页,从而推断地址[7];util.c 里的 setup_kernelsnitch() / run_kernelsnitch_bruteforce() / current_kernelsnitch_mm_struct() 是对它的封装。

2.3 物理内存读写(Pipe)

拿到 kaslr_base 之后,exploit 用管道缓冲区(pipe_buffer)做物理地址级读写 (pipe.c)[8]:

  1. 在 physmap 中定位 pipe buffer;
  2. 伪造 pipe_buffer 的 ops 指向已知的 anon_pipe_buf_ops;
  3. 劫持 pipe_buffer.page 指向目标物理地址;
  4. 通过正常的 pipe read / write 实现任意物理地址读写。

pipe.c 对外暴露 pipe_read64 / pipe_write64 / pipe_phys_read_data / pipe_phys_write_data,并用 PHYS_READ_TAG / PHYS_WRITE_TAG 之类的哨兵字符串做 读回校验(physrw_readback / physrw_after_write)[8]。

3. 完整利用链:两阶段写

有了"任意内核写"+"物理读写"+"KASLR 基址",剩下的就是沿特权边界逐步推进。exploit 把 提权拆成两次 PI 写(main.c 的头部注释即写明这一结构)[9]。

表 1|GhostLock 的两次 PI 写

阶段 目标 手段 效果
Write 1 selinux_state.enforcing child-node PI 写,伪造 waiter 的 __rb_parent_color 指向 selinux_enforcing - 8 rb-tree 重平衡把 0x00 写进去 → SELinux 关闭
Write 2 子进程 cred 指针 fork 子进程 → perf_find_task() 定位 task_struct → 算出 cred 偏移 → PI 写 cred = init_cred uid=0 且拥有完整 capabilities

3.1 Write 1:关闭 SELinux

selinux_state.enforcing 位于 selinux_state 结构体偏移 0x00(target.h 里 SELINUX_ENFORCING_OFF 指向 selinux_state 符号本身[10])。因为要写入的值是 0x00, exploit 巧妙地选择"child-node"模式:伪造 waiter 时把它当作已有父节点的右/左子节点, 令 rb-tree 在重平衡时把子节点指针(此时为 0)写到 selinux_enforcing - 8 的位置。 util.c 里 pselect_child_node 这个开关(1 = 写 page+0x100,0 = 写零)正是区分 "写零"与"写非零"两种写模式的[6]。

关闭 SELinux 后,原本被 SELinux 拦截的操作(例如 setprop)在 policy 层面不再被阻止—— 这就是 bootstrap 模式能继续往下走的先决条件。

3.2 Write 2:提权到 root

Write 2 需要先把"要改谁"定位出来(main.c 注释:Phase 2: Write 2 — cred = init_cred (child-node PI write via perf task leak))[9]:

  1. fork 子进程:目标是改子进程的 cred,父进程改完后再由子进程继续执行 root 逻辑, 避免把自身 cred 改坏导致控制流异常。
  2. perf_find_task() 定位 task_struct:利用 perf_event_open 的可观测能力 在任务链中定位该子进程的 task_struct 地址(这也是为什么完整利用需要 perf 可用)。
  3. 算出 cred 字段偏移:target.h 给出 TASK_CRED_OFF = 0x900、 TASK_REAL_CRED_OFF = 0x8f8(BTF 校验过的字段偏移)[10]。
  4. PI 写 cred = init_cred:init_cred 是内核最初的凭据对象,uid=0 且带完整 capability 集合;把子进程 cred 指针直接指向它即可"借"到 root 身份。
  5. 清除 seccomp:连同 TIF_SECCOMP 标志与 seccomp 结构体一起清零 (TASK_SECCOMP_OFF = 0x9c8)[10],解除系统调用过滤。

改完后还会回读校验:root.c 里 root_uid_before/after、 cred_sid_before/after、capable_head_before/after 等变量用来确认 uid、SELinux SID、 capability 链头确实变了,而不是"以为写成功了"[11]。

3.3 四种投放方式

同一套内核利用链,各仓库用了不同的"落地点":

表 2|各实现的投放方式对比

仓库 目标机型 投放方式 持久化
[p2p3p/GhostLock-for-OnePlus][1] 一加 Ace 6T / 一加 15 独立 ELF ghostlock,ADB shell 或 --bootstrap 自举 KernelSU(需 ksud)
[cuteaplane/…15T][2] 一加 15T 同上,适配 15T 偏移与时序 KernelSU(需 ksud)
[bartixxx32/…OnePlus15][3] 一加 15 LD_PRELOAD 共享库 preload.so 独立 su 守护进程
[d1667018881/myroot][22] 55+10 台(多厂商) 纯网页:Firefox JIT 拿 shell + LD_PRELOAD 加载 .so KernelSU 或 su daemon

3.3.1 独立 ELF + --bootstrap 自举(p2p3p / cuteaplane)

App 上下文通常带 seccomp 过滤,会挡住 perf_event_open,且没有 ADB shell 的完整权限。 exploit 为此设计了 --bootstrap 模式,其"自举"思路是[1][2]:

  1. 先只做 Write 1(关 SELinux);
  2. 借 SELinux 关闭后的权限执行 setprop,打开 ADB TCP 端口 5555;
  3. 用内置的 Mini ADB 客户端连回 127.0.0.1:5555,用预推送的 RSA 私钥认证;
  4. 在 ADB shell 上下文(无 seccomp 限制)里再跑一次完整利用。

miniadb.c 就是这套逻辑:它实现了精简的 ADB 协议(A_CNXN / A_AUTH / A_OPEN 等命令字),通过 dlopen("libcrypto.so") + PEM_read_bio_RSAPrivateKey + RSA_sign 对 ADB 的 token 挑战签名,支持 SHA-1(NID_SHA1)与 SHA-256(NID_SHA256)[12]。

3.3.2 LD_PRELOAD 注入(bartixxx32)

bartixxx32 的版本把 exploit 编成共享库 preload.so,靠 LD_PRELOAD 注入到任意 系统进程里执行[3]:

adb push source/build/bin/preload.so /data/local/tmp/preload.so
adb shell chmod 0644 /data/local/tmp/preload.so
adb shell LD_PRELOAD=/data/local/tmp/preload.so /system/bin/true
adb shell /data/local/tmp/su -c id

这条路径不依赖 ADB 自举,而是直接把提权逻辑塞进一个普通进程的地址空间;提权成功后落到 一个独立的 su 守护进程(源码里的 su_daemon.c 与 su_blob.S),无需安装 KernelSU。 它源自 [x-spy/CVE-2026-43499-popsicle][13],主要改动是修了一处 kallsyms 对齐 bug 并补了一份已验证的一加 15 profile[3]。

3.3.3 纯网页(Firefox JIT + LD_PRELOAD):myroot

[d1667018881/myroot][22] 把"拿 shell"和"提权"两层漏洞都塞进一个网页里,做到 免安装、纯网页、重启即恢复的临时 root。它不自己实现 CVE-2026-43499 的利用, 而是复用上游 GhostLock 的 src/core/*.c(YuKongA/ghostlock-app), 只是把投送方式换成浏览器:

① Firefox JIT 漏洞 (CVE-2026-10702)
   Firefox for Android ≤ 151.0 的 JIT 类型混淆
   → 泄漏 typed array 元数据 → 任意读写 → mprotect shellcode
   → 在 untrusted_app 沙箱内执行 shell(uid 10478)
② 上传 ghostlock.so 到 Firefox 私有目录
   /data/data/org.mozilla.firefox/files/res
③ LD_PRELOAD 触发(SELinux 禁止 untrusted_app execve app data 文件,
   但允许 dlopen / LD_PRELOAD)→ constructor 加载即自动执行
④ GhostLock 提权链:W1 破 SELinux → W2 改 cred → W3 绕 seccomp
   → root shell → ksud late-load → KernelSU 激活

这套设计里最值得注意的是为什么必须用 LD_PRELOAD 而不是直接 execve:Android 的 SELinux 策略禁止 untrusted_app 域 execve 应用私有目录下的文件,但允许 dlopen / LD_PRELOAD。于是 exploit 被编译成带 constructor 的共享库, 靠 __attribute__((constructor)) ghostlock_preload_init() 在加载时自执行[22]。 这也解释了为何它不能直接拿 YuKongA 原版(PIE 可执行文件)的产物——原版的 main() 不会被 LD_PRELOAD 触发。myroot 内核提权代码的源头是 [YuKongA/ghostlock-app][24] 的 src/core/*.c,本地只保留三处覆盖上游后必须重打的定制 (common.h 的 MM_PARTIALS、util.c 的 mm 喷溅数量、main.c 的 constructor + unsetenv)[23]。

myroot 的 README 还固化了一张"关键修复记录"表,记录六个只在真机复现的坑[22], 其中两条对理解这套利用链特别有价值:

表 3|myroot 真机踩坑摘录[22]

# 现象 根因 修复
3 W1 "route success" 但 SELinux 不变 写入源 *(base+0x100) 的内容环境相关(0 或 0x41) util.c 把 skb 偏移 0xf80 显式写 0
4 ghostlock 进程被 LMK 杀 fork ~400 子进程触发后台 cgroup 回收 MM_PARTIALS 5→2、prepare 8×→5×、每 8 次 fork usleep
5 KernelSU 永不激活(.ghostlock_ksu.log 不存在) root shell exec 时继承 LD_PRELOAD,constructor 在 root 上下文无限重跑 main.c exec root script 前 unsetenv("LD_PRELOAD")

第 5 条最隐蔽:现象是 W1/W2 都过、日志显示 child is root!,但 KernelSU 一直显示 "未安装",且 .ghostlock_ksu.log 不存在——根因是 root shell 链继承了 LD_PRELOAD, 导致 constructor 递归重跑,真正的 root script 永不执行。

除了 GhostLock 方案,myroot 还内置了一组 IonStack 方案:用的是另一支 NebuSec IonStack(经 hexo141/Rootme 搬运),提权后落到裸 su daemon 而非 KernelSU,覆盖约 10 台设备。两组共用同一条 Firefox JIT 入口,区别只在投放的 .so[22]。

💡 myroot 的价值不在漏洞本身,而在于它展示了 "浏览器 JIT 漏洞 + 内核 LPE"

两层组合的完整投送工程:manifest.json 按 uname -r 为 55 台 GhostLock 设备 路由到不同的 .so(其中小米 17 钉死在一份真机实测过的 v19 字节上), 并把构建 pin 到上游冻结 ref(CORE_REF / KERNELS_REF)以保证可复现[22][23]。

4. 机型适配:偏移表与内核匹配

4.1 运行时内核匹配(p2p3p / cuteaplane)

这套实现的入口是运行时内核匹配:启动时读 uname -r,在查找表里逐条比对,匹配不上 就直接拒绝运行。src/devices/offsets.h 定义 struct kernel_offsets 与 known_offsets[] 查找表(以 uname_r 为键、NULL 为哨兵)[14], 具体机型的偏移放在 src/devices/<机型>/offsets.h[15][16]。

main.c 里 select_offsets() 完成 uname 匹配,并用一整串 #define 把 active_offsets->off_* 动态绑到编译期宏上——这样同一份代码能适配多个内核版本, 而无需为每个版本重新编译[9]。

表 4|p2p3p/GhostLock-for-OnePlus 支持设备[1]

设备 代号 SoC 内核版本 固件 状态
一加 Ace 6T PLR110 SM8845(骁龙 8s Elite) 6.12.38-android16-5-…-ab14275539-4k ColorOS 16.0.2.403 ✅ 已验证
一加 Ace 6T PLR110 SM8845(骁龙 8s Elite) 6.12.38-android16-5-…-ab14552068-4k ColorOS 16.0.8.301 ✅ 已验证
一加 15 PLK110 SM8845(骁龙 8s Elite) 6.12.23-android16-5-…-ab14541642-4k — ✅ 已验证

表 5|cuteaplane/GhostLock-for-OnePlus15T 适配信息[2]

项目 值
设备 / 代号 一加 15T / PLZ110
SoC SM8850(骁龙 8 Elite)
内核版本 6.12.38-android16-5-g844001fb8721-ab14552068-4k
固件 ColorOS 16.0.9.400
状态 ✅ 已验证

offset 的来源分四类:kallsyms 全局符号 28 项由 tools/extract_target.py 提取、 BTF 结构体字段 57 项由 tools/extract_btf.py 提取、推导值 9 项自动计算、 固定常量 12 项硬编码[1]。

  • extract_target.py 从 boot.img 内嵌的 kallsyms 表里解析符号地址, 例如 SELINUX_ENFORCING_OFF = "selinux_state"、INIT_CRED_OFF = "init_cred", 并对 loggers[0][1](loggers + 0x10)、Rust-mangled 的 ashmem_fops 等做特殊处理[17]。
  • extract_btf.py 直接在 vmlinux 的 BTF 段里暴力搜索 STRUCT 类型, 按成员名取 bit_offset // 8 得到字段偏移——task_struct(17 字段)、 rt_mutex_waiter(6 字段)、cred(4 字段)等就是这么来的[18]。

4.2 适配一加 15T 的改动(cuteaplane)

一加 15T 的物理内存布局与 CPU 时序和上游机型不同,所以 target.h 里有一组机型相关的 常量:KIMAGE_TEXT_BASE = 0xffffffc080000000、P0_PAGE_OFFSET (VABITS=39 的内核地址空间)、KERNELSNITCH_IDENTITY_START/END (覆盖该 SoC 约 48GB 的 physmap)等[10];common.h 里的 CORE 7(绑核)与 `PSELECT*时序参数则对应 15T 的 CPU 特性<sup class="cite"><a href="#ref-6">[6]</a></sup>。仓库最近一次提交3ea45303` ("load_policy 修复策略")还处理了 bootstrap 模式下策略文件完整性修复[2]。

4.3 自动检测与 target.h 生成(bartixxx32)

bartixxx32 的版本把适配工具链做成两个脚本,并用一份 JSON profile 描述机型[3]:

  • detect_offset.py:从已 root 的同固件设备上读 /proc/iomem,自动推导 target.h 需要的两个物理地址字段。其派生逻辑(源码注释明确写出了不变量校验[19]):
    • p0_phys_offset = /proc/iomem 里 System RAM 的最低地址向下取整到 1 GiB (ARM64_MEMSTART_ALIGN = 1 << 30,对应 arm64 memstart_addr 的取值方式);
    • p0_kernel_phys_load = Kernel code 段起始地址减去 ARM64_STEXT_OFFSET (0x10000,即 arm64 Image 里 _stext 相对 _text 的偏移);
    • 之后还会反向闭合校验(phys_offset 是否 1 GiB 对齐、kernel 载入是否落在 System RAM 内、 kernel_phys_load + 0x10000 == code.start 等),避免"只是打印两个猜出来的值"。
  • generate_target.py:解析 boot.img(校验 ANDROID! magic、boot header v3/v4 与 header_size、arm64 Image 的 ARM\x64 magic 与 image_size),结合 JSON profile 生成 source/src/target.h[20]。
  • profile_oneplus15.json:一加 15 的 profile,只含两个字段[21]:

    {
      "p0_phys_offset": "0x80000000",
      "p0_kernel_phys_load": "0xc7800000"
    }
    
💡 这套"detect → profile → generate"流程把手工填 target.h 变成了可复现的

三步操作,是三个实现里适配工具链最完整的一个。

4.4 跨设备调整项(上游总结)

上游 README 给出了一张"换机型要动哪里"的清单[1]:

表 6|跨设备调整项

调整项 说明
VA_BITS 48 vs 39 → 更新 target.h 内存布局
时序参数 调整 common.h 中的 PSELECT_* 参数
ashmem 实现 C vs Rust → 更新 extract_target.py 符号匹配规则
secureguard 无 OnePlus secureguard 可简化部署流程

5. 构建与使用

5.1 构建(p2p3p / cuteaplane)

依赖 Android NDK(r25+),并设置 ANDROID_NDK_HOME 或 ANDROID_NDK_ROOT[1][2]:

make                    # 默认 API 35
make API=34             # 指定 API 级别
NDK=/path/to/android-ndk make   # 指定 NDK 路径

产物 ghostlock 是静态链接的 ARM64 ELF。

5.2 一次性设置与运行

# 1. 启用 ADB TCP 模式
adb tcpip 5555
# 2. 推送 ADB 密钥(bootstrap 模式需要)
adb push ~/.android/adbkey /data/local/tmp/a/adbkey
# 3. 推送利用程序
adb push ghostlock /data/local/tmp/a/e
adb shell chmod 755 /data/local/tmp/a/e

三种运行模式[1][2]:

模式 命令 说明
完整利用(ADB shell) /data/local/tmp/a/e perf 可用,精确泄露 task_struct;W1→W2→加载 KernelSU
Bootstrap(App 上下文) /data/local/tmp/a/e --bootstrap 先关 SELinux,再自举 ADB 进入无 seccomp 环境跑完整利用
仅 Write 1 /data/local/tmp/a/e --write1 最多尝试 20 次;用于调试或临时关 SELinux

首次成功后,resetprop 会自动设置 persist.adb.tcp.port=5555,后续重启可全自动运行[1][2]。

5.3 KernelSU 的依赖:ksud

GhostLock 只负责提权,KernelSU 的安装依赖 ksud(内含各 KMI 版本的 kernelsu.ko)[1][2]:

获取方式 说明
ReSukiSU APK(推荐) APK 捆绑了 libksud.so
CI 发行版 从 ReSukiSU CI 下载 ksud-aarch64-linux-android.zip

没有 ksud 时,利用仍可获得 uid=0 的 root shell,但 KernelSU 不会被安装, su 不会持久化。bartixxx32 的 LD_PRELOAD 版本则改用自带 su 守护进程[3], 不依赖 ksud。

6. 文件结构与模块职责

以 cuteaplane 版为例(与上游结构一致)[2]:

ghostlock-oneplus/
├── Makefile                # NDK 交叉编译
├── src/core/
│   ├── main.c              # 入口:两阶段写入 + root shell
│   ├── fops.c              # FOPS/CFI 模式:pselect 路由、PI 写入、KASLR 泄露
│   ├── util.c              # 堆喷射、KASLR 计算、内核 R/W 原语
│   ├── slide.c             # SLIDE 模式:boot_id KASLR 泄露 + pselect 路由
│   ├── pipe.c              # 管道缓冲区物理内存读写 (physrw)
│   ├── root.c              # cred 覆盖、seccomp 清除、root 子进程管理
│   ├── miniadb.c           # 内置 ADB 客户端 (TCP + RSA 认证)
│   ├── common.h            # 全局宏、结构体、声明、常量
│   ├── target.h            # 目标内存布局 / 结构体偏移 / KASLR 参数
│   ├── offset.h            # 编译期 target config 桥接
│   └── kernelsnitch/       # KernelSnitch — mm_struct 地址泄露
└── tools/
    ├── extract_target.py   # kallsyms 全局符号偏移 (28)
    └── extract_btf.py      # BTF 结构体字段偏移 (57)

表 7|核心模块职责[1]

模块 文件 职责
入口 main.c 命令行解析、W1/W2 调度、bootstrap 流程
PI 路由 fops.c / slide.c pselect/select 栈布局、futex PI 链操控、竞争触发
KASLR util.c / fops.c / slide.c 双模式绕过:boot_id 泄露 + fops 表泄露
堆喷射 util.c order-3 大页分配、SKB 喷射、伪造对象布局
物理 R/W pipe.c pipe_buffer 劫持、任意物理内存读写
提权 root.c cred 覆盖、capability 验证、seccomp 清除
ADB miniadb.c 精简 ADB 协议客户端、RSA 认证
泄露引擎 kernelsnitch/ futex 哈希碰撞定位 mm_struct

7. 常见问题(上游 FAQ)

  • 运行后无输出:确认在 ADB shell 环境运行;App 上下文要用 --bootstrap[1][2]。
  • no offsets for this kernel:内核版本未收录,需按 §4 提取偏移后重新编译。
  • Write 1 老是失败:确认绑核正确(CORE 宏)、调整 PSELECT_ENTER_DELAY_USEC 时序、排查是否有其他进程干扰堆状态。
  • Write 2 的 perf 返回 0:检查 seccomp 是否挡住 perf_event_open(App 上下文用 --bootstrap);确认 KASLR 绕过已拿到 kaslr_base。
  • KernelSU 未加载:确认 ksud 存在且可执行;检查网络策略文件完整性 (load_policy 修复)。

8. 各版本关系小结

  • 漏洞与利用原语相同:都是 futex PI waiter UAF → pselect 栈布局 → rb-tree 任意写 → 关 SELinux + cred 提权。后两者都直接或间接继承自 x-spy 的 CVE-2026-43499-popsicle 谱系[13]。
  • p2p3p/GhostLock-for-OnePlus 是最完整的"整机方案":多机型偏移表、ADB 自举、 KernelSU 持久化、extract_target.py + extract_btf.py 工具链[1]。
  • cuteaplane/GhostLock-for-OnePlus15T 在[1]基础上做机型适配,是新机型移植的 最小改动范例:新增 src/devices/plz110t/offsets.h + 调整 target.h / common.h 常量[2]。
  • bartixxx32/CVE-2026-43499-OnePlus15 走了另一条路:LD_PRELOAD 注入 + 独立 su 守护进程,并提供了 detect_offset.py + generate_target.py + JSON profile 的自动化适配工具链[3]。
  • d1667018881/myroot 把投送层也做进了浏览器:前置用 Firefox JIT 漏洞 (CVE-2026-10702)拿 shell,内核提权仍复用 GhostLock,做成纯网页、临时 root、 重启即恢复的形态;同时内置 NebuSec IonStack 作为第二方案[22]。

参考文献

[1]: https://github.com/p2p3p/GhostLock-for-OnePlus [2]: https://github.com/cuteaplane/GhostLock-for-OnePlus15T [3]: https://github.com/bartixxx32/CVE-2026-43499-OnePlus15 [13]: https://github.com/x-spy/CVE-2026-43499-popsicle [22]: https://github.com/d1667018881/myroot [25]: https://github.com/JoinChang/ghostlock-oneplus [26]: https://github.com/awesome-android-root/awesome-android-root [27]: https://xdaforums.com/t/how-to-unlock-oneplus-15-fastboot-eu-version-at-least.4768792/ [28]: https://xdaforums.com/t/root-with-locked-bootloader-oneplus-15.4783161/ [29]: https://www.cve.org/CVERecord?id=CVE-2026-43499 [30]: https://blog.nns.ee/2026/09/24/oneplus-root/

1. p2p3p. GhostLock-for-OnePlus: Kernel exploit targeting locked-bootloader OnePlus devices (CVE-2026-43499)[EB/OL]. GitHub. https://github.com/p2p3p/GhostLock-for-OnePlus (分支 main,README_CN.md). ↩
2. cuteaplane. GhostLock-for-OnePlus15T: Kernel exploit targeting locked-bootloader OnePlus 15T devices (CVE-2026-43499)[EB/OL]. GitHub, commit 3ea45303bfdbc017a67c1c8583795e1a5bb7f33d. https://github.com/cuteaplane/GhostLock-for-OnePlus15T ↩
3. bartixxx32. CVE-2026-43499-OnePlus15: Android 16 local privilege escalation for OnePlus 15 (Educational PoC)[EB/OL]. GitHub, commit dfdbca7e71a4318e7b4d2c78181506d76e79db16. https://github.com/bartixxx32/CVE-2026-43499-OnePlus15 ↩
4. cuteaplane. src/core/fops.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/fops.c ↩
5. cuteaplane. src/core/slide.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/slide.c ↩
6. cuteaplane. src/core/common.h, src/core/util.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/common.h ↩
7. cuteaplane. src/core/kernelsnitch/kernelsnitch.h[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/kernelsnitch/kernelsnitch.h ↩
8. cuteaplane. src/core/pipe.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/pipe.c ↩
9. cuteaplane. src/core/main.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/main.c ↩
10. cuteaplane. src/core/target.h[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/target.h ↩
11. cuteaplane. src/core/root.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/root.c ↩
12. cuteaplane. src/core/miniadb.c[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/core/miniadb.c ↩
13. x-spy. CVE-2026-43499-popsicle: Original CVE-2026-43499 exploit[EB/OL]. GitHub. https://github.com/x-spy/CVE-2026-43499-popsicle ↩
14. cuteaplane. src/devices/offsets.h[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/devices/offsets.h ↩
15. cuteaplane. src/devices/plz110t/offsets.h[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/src/devices/plz110t/offsets.h ↩
16. p2p3p. src/devices/ace6t/offsets.h, src/devices/op15/offsets.h[EB/OL]. GhostLock-for-OnePlus, 分支 main. https://github.com/p2p3p/GhostLock-for-OnePlus/tree/main/src/devices ↩
17. cuteaplane. tools/extract_target.py[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/tools/extract_target.py ↩
18. cuteaplane. tools/extract_btf.py[EB/OL]. GhostLock-for-OnePlus15T, commit 3ea45303. https://github.com/cuteaplane/GhostLock-for-OnePlus15T/blob/3ea45303bfdbc017a67c1c8583795e1a5bb7f33d/tools/extract_btf.py ↩
19. bartixxx32. detect_offset.py[EB/OL]. CVE-2026-43499-OnePlus15, commit dfdbca7. https://github.com/bartixxx32/CVE-2026-43499-OnePlus15/blob/dfdbca7e71a4318e7b4d2c78181506d76e79db16/detect_offset.py ↩
20. bartixxx32. generate_target.py[EB/OL]. CVE-2026-43499-OnePlus15, commit dfdbca7. https://github.com/bartixxx32/CVE-2026-43499-OnePlus15/blob/dfdbca7e71a4318e7b4d2c78181506d76e79db16/generate_target.py ↩
21. bartixxx32. profile_oneplus15.json[EB/OL]. CVE-2026-43499-OnePlus15, commit dfdbca7. https://github.com/bartixxx32/CVE-2026-43499-OnePlus15/blob/dfdbca7e71a4318e7b4d2c78181506d76e79db16/profile_oneplus15.json ↩
22. d1667018881. myroot: 自用 Android 临时 Root 网页(CVE-2026-10702 + CVE-2026-43499)[EB/OL]. GitHub, commit 16b6015558a2fb9a5d3be9e961b2f7f9a9d6b942. https://github.com/d1667018881/myroot ↩
23. d1667018881. AI_HANDOFF.md, manifest.json, scripts/build-ghostlock.sh[EB/OL]. myroot, commit 16b60155. https://github.com/d1667018881/myroot/blob/16b6015558a2fb9a5d3be9e961b2f7f9a9d6b942/AI_HANDOFF.md ↩
24. YuKongA. ghostlock-app: CVE-2026-43499 exploit app[EB/OL]. GitHub. https://github.com/YuKongA/ghostlock-app ↩
25. JoinChang. ghostlock-oneplus: GhostLock (CVE-2026-43499) kernel exploit for Android devices with locked bootloader[EB/OL]. GitHub. https://github.com/JoinChang/ghostlock-oneplus ↩
26. awesome-android-root. root-without-unlocking-bootloader.md: GhostLock (CVE-2026-43499) 的临时 root 边界与 BL 解锁关系[EB/OL]. GitHub. https://github.com/awesome-android-root/awesome-android-root/blob/main/docs/rooting-guides/root-without-unlocking-bootloader.md ↩
27. XDA Forums. How to unlock OnePlus 15 fastboot (EU version at least)[EB/OL]. 2026. https://xdaforums.com/t/how-to-unlock-oneplus-15-fastboot-eu-version-at-least.4768792/ ↩
28. XDA Forums. Root with locked bootloader — OnePlus 15[EB/OL]. 2026. https://xdaforums.com/t/root-with-locked-bootloader-oneplus-15.4783161/ ↩
29. CVE Program. CVE-2026-43499: rtmutex: Use waiter::task instead of current in remove_waiter()[EB/OL]. 2026. https://www.cve.org/CVERecord?id=CVE-2026-43499 ↩
30. Rasmus Moorats (nns). Getting root on OnePlus 15 from an untrusted app, via an audio debug service and a vendor HAL[EB/OL]. 2026-09-24. https://blog.nns.ee/2026/09/24/oneplus-root/ ↩

© 2026 Yang Huan · yanghuan9812@qq.com

results matching ""

    No results matching ""