Skip to content
KitploitKITPLOIT
工具漏洞利用博客
Log in
提交
工具漏洞利用博客
提交

黑客、渗透测试和网络安全工具,武装您的安全武器库!

Kitploit 是一个黑客、网络安全和渗透测试工具的目录。发现最新的项目更新,查找漏洞、分析系统、自动化测试并加强你的安全。

订阅源联系隐私© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
ghostlock-pfem10 — GhostLock (CVE-2026-43499) for OPPO Find X5 Pro (PFEM10) — OPlus watchdog & heap-spray detector reverse engineering | Kitploit
工具/GitHubGitHub/imeiplus/ghostlock-pfem10
Android SecurityPrivilege EscalationMemory ForensicsVulnerability AnalysisExploitationReverse EngineeringMobile SecurityPayload DevelopmentBinary Exploitation
GitHubimeiplus/ghostlock-pfem10

ghostlock-pfem10

GhostLock (CVE-2026-43499) for OPPO Find X5 Pro (PFEM10) — OPlus watchdog & heap-spray detector reverse engineering

4622天前尚未审核

最受欢迎

查看全部 →

发现我们社区最常用的工具。

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
查看仓库

GhostLock — OPPO Find X5 Pro (PFEM10)

English · 中文

build

GhostLock(CVE-2026-43499)针对 ColorOS 16 上 OPPO Find X5 Pro 的移植。已做到 uid=0 子进程 + kernelsu.ko 载入;root 进程被拦截。

漏洞

CVE-2026-43499 —— futex PI 栈 UAF。rt_mutex_start_proxy_lock() 的 -EDEADLK 回滚路径上,当 current 是 requeuer 而非 waiter 时,remove_waiter() 会清掉 current->pi_blocked_on,waiter 被留在已弹出的栈帧上。

remove_waiter @ 0xffffffc0081ed254 —— 修复前形态。

设备

机型OPPO Find X5 Pro (PFEM10)
SoCSM8450 / Adreno 730
系统ColorOS 16.0.3.520 (CN01)
内核5.10.236-android12-9-o-gaf2075ad2c06
BL锁定,green
VA_BITS39 —— KIMAGE_TEXT_BASE = 0xffffffc008000000

进度

阶段
compact waiter 触发(CMP_REQUEUE_PI → EDEADLK)成功
task_struct 泄漏(perf)成功
PI 写原语(8 字节;值 = 0 或合法内核地址)成功
task+0x778 或 task+0x780 单独落地 → Uid=root成功 —— 但单字段落地会让任务进入分歧态,而这是一条潜伏的硬 BUG_ON。见分歧态那一节
两字段写成同一个值(一致对)❌ 用喷页从未产生过。 只在全局 init_cred 别名上出现过(09-14,CONTROL=1)。runner 现在强制它(SAME_VALUE=1);未上机
cred 洗白(setresgid + setresuid)已实现(V12_LAUNDER=1);未上机
kernelsu.ko 载入成功
root 进程存活⚠ 未定论 —— 见下
重启机制❌ 未建立。 一个候选(分歧态)现已被排除;见下
把 probe_state 当落地判据❌ 错的 —— 不要用。 三个反例;见下表
pstore/ramoops panic 通道⚠ 仪器已有;通道从未验证(还没做 null test)
「受害者在纯用户态自旋」⚠ 还没有读数 —— uid.stream 现在记 utime/stime/nvcsw,所以它可被检验
pi 侧单 pass 双写⚠ 未建立;fdset_map.h 里 pi.pc/pi.left 被硬编码为 0
Path A(UMH / modprobe_path)STATIC_USERMODEHELPER_PATH=""

关于「root 进程存活」:evidence/kill.log 里的几次 run 都走到了 uid=0 并载入了 kernelsu.ko;其中真正做了轮询的那次,KernelSU 管理器进程存活了 120 秒, /proc/modules 里 kernelsu 全程 Live。另一次同样链路跑完后,Android framework 的服务 不可达(Can't find service: package/power/input/phone/wifi),而模块仍然 Live。 从未抓到过 [ROOTCHECK-*] 内核行,也从未抓到 $$sys_call_number@@ 载荷, 所以后一次的状态归因不明。详见 evidence/notes.md §2.3、§2.4、§7。

偏移

task_struct

字段偏移
real_cred / cred0x778 / 0x780
缓存的 syscallno0xdf8
缓存的 uid / euid / gid / egid0xe00 / 0xe08 / 0xe10 / 0xe18

thread_info

字段偏移
flags0x0
addr_limit0x8
ttbr00x10
preempt_count0x18

cred

字段偏移字段偏移
uid0x4cap_inheritable0x28
gid0x8cap_permitted0x30
suid0xccap_effective0x38
sgid0x10cap_bset0x40
euid0x14cap_ambient0x48
egid0x18
fsuid / fsgid0x1c / 0x20

利用链

LT          perf 泄漏目标 task_struct → 落文件
W7 阶段 1   task+0x778 = V(real_cred → 喷页内的私有页;V 是**观测到的**值)
W7 阶段 2   task+0x780 = V(cred)   ★ V12_W7_VALUE=V —— **同一个值**,不是新喷一页
W7 阶段 3   V+8 = 0(对**真正被装上**的那张页做**局部**修复,第二个进程,ZERO 形状)
LT 子进程   fexecve(loader 的 memfd) —— 不对 /data 路径做 execve
loader      ksud late-load                    → kernelsu ... Live

阶段 2 必须显式拿到阶段 1 的值。 阶段 1 与阶段 2 是两个独立进程、各自喷各自的页, 所以「把 cred 页写进两个槽」是个陷阱:照字面读会产出 (pageA, pageB), 而因为 commit_creds 比的是指针,即便两枪都落地那也是分歧对。 这不是假设 —— run 3 和 run 9 干的就是这件事:

run 9   0x778 那一枪  write value = 0xffffff88679bade0
        0x780 那一枪  write value = 0xffffff8785d6ade0     <- 另一张页
run 3   0x778 那一枪  write value = 0xffffff8787b5ade0
        0x780 那一枪  write value = 0xffffff881bad2de0     <- 另一张页

因此 run_bootA.sh 用 V12_W7_VALUE=<阶段 1 观测到的值> 开阶段 2 那一枪, 抽不到值就干脆不发。HOLD 必须活过阶段 2,否则阶段 1 的页被释放并重新分配, 「同值」就变成悬垂指针。见同值规则那一节。

一次 boot 只修一张页。 阶段 3 清零 V+8。若两张页不同,把两页的 +8 都清零 会抹掉 gid/suid 那枚戳(见下),让分歧看起来一致,所以 runner 只修真正被装上的那张页, 且两枪值不一致时直接停。

cred 页由 payload.c 构造:8 个 id 字段全 0、5 组 caps 全满,user / user_ns / group_info 指向 root_user / init_user_ns / init_groups。阶段 3 之所以存在,是因为写的副作用 必然把它装入的那张 cred 的 +8(gid/suid)打坏。

关于 init_cred —— 一个显式的二分

本节与另一处曾经互相打脸(「从来不是全局 init_cred」 vs 「CONTROL=1 复现 cell 2」, 而 cell 2 就是 init_cred)。两句话对不同角色都是真的:

  • 作为目标 —— 禁止。 写入 init_cred 指针会让副作用全局写坏 init_cred+8 —— init_cred 被所有内核线程共用,Uid: 0 0 4294967176 0 正是那次损坏。 除非显式设 V12_ALLOW_INIT_CRED=1,代码拒绝这条路。
  • 作为唯一被证实的一致对 —— 保留。 09-14 走到 ksud 的那条链把一个固定地址 (0xffffff802a7e0be0)写进两个槽,所以 real_cred == cred 是构造出来的 —— 这正是它能活到 execve 的原因。CONTROL=1 复现它。它是对照,不是可以依赖的配置。

perf 泄漏:PERF_TYPE_SOFTWARE / PERF_COUNT_SW_CPU_CLOCK,PERF_SAMPLE_REGS_INTR,exclude_user=1。 取值范围 [0xffffff8400000000, 0xffffff90000000),票数 ≥ 15%。

写原语及其副作用

本漏洞经 rb_erase_cached Case 1-left 触发,产生的是两次写、不是一次:

*(write_target)        = write_value      // 你要写的那一次
*(write_value + 0x08)  = write_target     // 躲不掉的副作用

write_value 必须 8 字节对齐、bit0 = 0 —— 所以它只能是 0 或合法内核地址。 这就是 8 字节写设不了 g_boot_state 的原因:你需要变成 1 的那个字节, 低位被对齐要求强制成 0;而 write_value 与副作用落点是同一个量。

副作用会写进 write_value 指向的对象

write_value 既是被写入的值,也是副作用写入的地址(+8)。把它指向内核全局对象, 就会把那对象写坏。

W7 以前就是这么干的 —— 把 write_value 指向 init_cred 别名 —— 回读里看得见。 来自 out/t5_w7_778.txt:

shape shift=0 wps=5: in[0]=0xffffff802a7e0be0 (write_value) in[2]=0xffffff8800cdd178 (write_target)
W7[W7] write_target= 0xffffff8800cdd178
Uid:	0	0	4294967176	0

当时 write_value 是 init_cred 别名,write_target 是 child_task+0x778。 init_cred+8 是 gid/suid,于是副作用把 0xffffff8800cdd178 写在那儿: init_cred.gid = 0x00cdd178,而 init_cred.suid = 0xffffff88 = 4294967176 —— 正好就是上面 Uid: 行的第 3 个字段。把 init_cred+8 清零即可修复 (out/t5_repair.txt:Uid: 0 0 4294967176 0 → Uid: 0 0 0 0)—— 这就是"W7 阶段 3"的全部含义。

这条路径现在被代码拒绝。 V12_W7_INIT_CRED=1 会直接中止并说明原因,除非同时显式 V12_ALLOW_INIT_CRED=1;W2 / W6 / LTC 三条路径在私有 cred 页缺失时不再回落到 init_cred, 而是中止。默认(也是唯一合理的)路径就是喷页内的 cred 副本。

副作用本身躲不掉:write_value 必须就是那个 cred 指针,所以 cred+8 必然被写入 write_target。能选的只有它的落点 —— 而修复现在是对 cred_page+8 的局部清零 (阶段 3),不再是写进全局对象。

groups= 读出垃圾是另一个症状,不是这个。它出现在一次 gid/egid 回读正常的 run 里, 所以不可能来自 init_cred+8 副作用;它指向假 cred 自己的 group_info 字段。见 evidence/notes.md §10.6。

★ 单字段落地会让任务进入分歧态 —— 而这是一条硬 BUG_ON

本原语每趟只写一个地址。task+0x778(real_cred)与 task+0x780(cred)是两个不同地址, 所以任何一次落地的 0x778-only 或 0x780-only 写入,都会让任务留下 cred != real_cred —— 分歧态。

在本镜像上,这个状态是硬 panic,不是警告。commit_creds 开头就是 BUG_ON(task->cred != task->real_cred):

commit_creds @0xffffffc008186784
  0x1867a4  ldr  x19, [x20, #0x778]      ; old = task->real_cred
  0x1867a8  ldr  x8,  [x20, #0x780]      ;        task->cred
  0x1867ac  cmp  x8, x19
  0x1867b0  b.ne #0xffffffc008186b68
  0x186b68  brk #0x800                   ; == BUG()

而本内核编了 CONFIG_PANIC_ON_OOPS=y(CONFIG_PANIC_ON_OOPS_VALUE=1)。 __put_cred @0xffffffc008185530 还有同族断言(usage != 0 → BUG; cred == current->cred / current->real_cred → BUG)。

所以分歧态是潜伏的 —— 受害者进程只是自旋时什么都不会发生 —— 直到该任务上发生任何 commit_creds:setresuid / setresgid / setuid / setgid / capset, 或者 execve(经 install_exec_creds)。

下载工具