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

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

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

··订阅源·联系·隐私·© 2026 Kitploit

工具目录

分类

查看所有分类
Loading categories
CVE-2026-43499-pmg110-root — 针对 OPPO PMG110(内核 6.6)的 CVE-2026-43499 Android LPE 漏洞利用。利用 futex PI UAF 获取 root 权限,并通过 LD_PRELOAD 安装 su 守护进程。 | Kitploit
工具/GitHubGitHub/soralis0912/cve-2026-43499-pmg110-root
Android安全权限提升漏洞分析漏洞利用后渗透利用渗透测试移动安全红队Payload 开发

最受欢迎

查看全部 →

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

探索所有工具

浏览我们的工具集合

查看所有工具 →
分享
二进制利用
GitHubsoralis0912/cve-2026-43499-pmg110-root

CVE-2026-43499-pmg110-root

针对 OPPO PMG110(内核 6.6)的 CVE-2026-43499 Android LPE 漏洞利用。利用 futex PI UAF 获取 root 权限,并通过 LD_PRELOAD 安装 su 守护进程。

查看仓库
2302个月前尚未审核

pmg110-root

CVE-2026-43499(futex PI rt_mutex_waiter 释放后使用)本地权限提升,移植到 OPPO PMG110 / K15 Pro+ — MediaTek MT6991,ColorOS 16。

只需推送一个文件,通过 LD_PRELOAD 运行:

adb push out/preload-pmg110-16.0.9.400.so /data/local/tmp/preload.so
adb shell chmod 644 /data/local/tmp/preload.so
adb shell LD_PRELOAD=/data/local/tmp/preload.so /system/bin/true

成功后会在系统中留下一个持久的 su:

adb shell /data/local/tmp/su -c id      # uid=0(root)

已在真机验证(2026-07-27):在无任何环境变量覆盖的裸运行下,约 35 秒即可获得 uid=0,之后可从普通非特权 adb shell 使用 su:

$ adb shell "/data/local/tmp/su -c 'echo 0 > /proc/sys/kernel/kptr_restrict'"
$ adb shell "/data/local/tmp/su -c 'grep -w init_task /proc/kallsyms'"
ffffffe89033e780 D init_task

该漏洞利用和 su 安装均已在设备上验证。随后已 root 的 shell 读取回的内容也独立于该漏洞,证实了 P0_KERNEL_PHYS_LOAD、符号偏移量和 KS_MTE_TAGGED=0 — 参见 targets/pmg110-16.0.9.400/NOTES.md。

设备OPPO PMG110 / K15 Pro+ / OP61E5L1
SoCMediaTek MT6991(Dimensity 9500s)
内核6.6.118-android15-8-g93e223c276e7-abogki500782043-4k(GKI,4K 页)
构建ColorOS 16 / PMG110_16.0.9.400(CN01) — 与 16.0.8.300 的内核字节相同
漏洞CVE-2026-43499,此镜像中未修复(经反汇编证实,而非版本号)

它能做什么,不能做什么

它执行 Write 1(SELinux 宽容模式)和 Write 2(cred → init_cred),让一个子进程获得 uid=0,并从那里安装内置的 su 守护进程。

  • 仍然只推送一个文件。 su 并非第二个工件:su_daemon.c 被构建为独立的 aarch64 PIE,并通过 .incbin 嵌入到库的 .rodata 中,因此它搭载在 preload.so 内部,并在运行时被写回。沿用 warhol-root 的路线,未作改动。
  • 没有 root 脚本,没有 ksud,没有 KernelSU
  • 调用进程保持非特权 — 它通过请求守护进程来获得 root,这与之后你在 shell 中的操作相同
  • SELinux 保持宽容模式,与 warhol-root 的做法一致:守护进程需要通过其 套接字为非特权客户端提供服务。重启后恢复强制模式。

su 会安装到三个位置,因为其中有一个才是你实际能访问到的:

路径原因
/apex/com.android.virt/bin/su位于挂载到该目录的 tmpfs 上;在 root shell 的 PATH 中
/data/local/tmp/su可从普通 adb shell 直接访问,无需 PATH 技巧
/apex/com.android.virt/bin/su 在 adbd 的挂载命名空间中通过 setns 安装,因此新的 adb shell 也能看到

守护进程监听 /data/local/tmp/temp_su.sock,并将日志写入 /data/local/tmp/su_daemon.log。root 不会跨重启保留 — 每次开机后需重新运行 LD_PRELOAD 命令行。

如需 KernelSU 安装,请改用 ghostlock-oneplus 中的 /data/local/tmp/a/e 路线。

与 warhol-root 的关系

除漏洞利用核心外,其余部分均来自 warhol-root,是直接采用而非重新发明:

  • 布局 — targets/<device>/ 下按设备划分的头文件,在构建时暂存到 source/src/,因此切换 DEVICE 绝不会留下上一台设备的头文件
  • 构建 — source/Makefile 的工具链选择(有 NDK 则用 NDK,否则用主机 clang 配合 NDK sysroot),以及两阶段嵌入规则:在链接 .so 之前生成 build/embed/su_daemon_aarch64_pie
  • su 路线 — su_daemon.c 和 su_blob.S 与 warhol-root 的字节完全相同, su_install.c 则是它的 preload.c 安装器

漏洞利用核心并非 warhol-root 的。 warhol-root 就是 popsicle,固定于 GKI 6.12 / android16,其 generate_target.py 拒绝任何其他 banner。PMG110 是 6.6 / android15,因此这里的核心是 ghostlock 的 6.6 分支 — 它本身就是同一代码的后代(两个仓库的 kernelsnitch/utils.h 和 timeutils.h 字节完全相同),并在此基础上进一步开发。

文件关系
util.c slide.c fops.c pipe.c root.c miniadb.c common.h offset.h kernelsnitch/*ghostlock 的,字节完全相同
su_daemon.c su_blob.Swarhol-root 的,字节完全相同(su_blob.S 增加了两行 .hidden — 参见构建)
su_install.cwarhol-root 的 preload.c 安装器,移至自己的文件,因为本仓库的 preload.c 已有其他职责
main.cghostlock 的,外加已 root 子进程中的 su 调用和结果上报
preload.c仅本仓库独有 — 构造函数和双输出日志
offsets.h仅结构体定义;条目从 targets/<device>/device_offsets.h 暂存而来

漏洞利用本身的每一行 — Write 1、Write 2、KernelSnitch、pselect 路线 — 在两个仓库中都是相同的代码。

su 安装的调用位置

这是唯一的结构性差异,由两个仓库获得 root 的不同方式所决定。

warhol-root 直接让漏洞利用进程本身获得 root,因此从 run_direct_root() 直接调用 install_embedded_su()。而这里 Write 2 交换的是 fork 出的子进程的 cred 指针,父进程保持为非特权调用者,因此 child_main() 中的子进程是唯一能执行安装的上下文 — 它就在那里运行。

两个仓库都在 util.c 中带有相同的弱符号 install_embedded_su() 桩函数,返回 ENOSYS;提供强定义才会启用该路线。这一点值得了解,因为某个意外丢弃 su_install.c 的构建仍然能链接、仍能运行 — 只是会报告 su=0/38 且不安装任何东西。

构建

make                      # = make preload -> out/preload-<DEVICE>.so
make DEVICE=<name>        # use targets/<name>/
make devices              # list available DEVICE values
make info                 # show the selected target and the resolved toolchain

工具链会自动查找:先找 ANDROID_NDK_HOME / ANDROID_NDK_ROOT,然后是 Linux 和 macOS 的常规 NDK 安装位置;若均不存在,则使用主机 clang 并指向 NDK sysroot。仅在需要覆盖搜索路径时才设置 ANDROID_NDK_HOME。make info 会打印其选择。

构建分两个阶段,这是值得了解的部分:

  1. su_daemon.c → build/embed/su_daemon_aarch64_pie,一个独立的 aarch64 PIE
  2. su_blob.S 将该二进制通过 .incbin 嵌入 .rodata,然后整体链接成一个 preload.so

因此 make clean 后重新构建是更改内置 su 的唯一方式 — 仅编辑 su_daemon.c 就够了,依赖关系已声明,但该 blob 是构建产物,不受版本控制。

targets/<device>/{target.h,device_offsets.h} 会在每次构建时重新暂存到 source/src/,因此不会静默地误用到其他设备的过时头文件。

out/*.so 不受版本控制(与 warhol-root 相同的约定)— 克隆后直接 make。

该 .so 以 -fvisibility=hidden 构建,导出零个符号。LD_PRELOAD 库会在整个进程中赢得符号查找,因此它导出的任何符号都可能遮蔽宿主二进制或 libc 中的同名符号。该标志仅作用于 C 代码生成,因此 su_blob.S 手动将其两个符号标记为 .hidden — 没有这些行,blob 边界将是该库唯一仍会导出的东西。

环境变量

变量作用
GHOSTLOCK_LOG日志输出目标(默认 /data/local/tmp/.ghostlock.log);输出同时写入 stdout 和 该文件
GHOSTLOCK_KS_VERBOSE=1打印 KernelSnitch 的碰撞地址和扫描范围
GHOSTLOCK_KS_THRESHOLD=<n>覆盖碰撞阈值倍数
GHOSTLOCK_MTE=1同时扫描内核指针标签(慢 15 倍)
GHOSTLOCK_PHYS_LOAD=0x...覆盖内核物理加载地址
PSELECT_SHIFT=<n>覆盖栈叠加偏移(替换,而非叠加)

在已验证的那次运行中,这些均无需设置。

解读日志

[*] futex_hashsize 2048 (8 possible CPUs)
[*] ks collisions=3/3 baseline=8 threshold=10x (80) accepted=[1244..1597] slowest_rejected=N
[+] child uid = 0
[+] embedded su wrote 15304 bytes to /apex/com.android.virt/bin/su
[+] embedded su daemon ready pid=NNNN socket=/data/local/tmp/temp_su.sock daemon=/apex/com.android.virt/bin/su
[+] embedded su install ok=1 errno=0 daemon=NNNN
[+] su ready: /data/local/tmp/su and /apex/com.android.virt/bin/su
[+] ghostlock preload verdict: EXPLOIT OK

child uid = 0 表示漏洞利用成功;其后的所有内容都是安装过程。两者故意分开报告,结论(verdict)也是如此:

结论含义
EXPLOIT OK已获得 root,且 su 可用
EXPLOIT OK, SU INSTALL FAILEDWrite 1 和 Write 2 已成功;仅安装环节出错
EXPLOIT FAILED写入未成功
ABORTED运行在能报告之前就终止了 — 查看最后一行 [!]

中间那个结论值得特别关注:它表示 target.h 中的偏移量对这个构建是正确的,问题出在安装环节,而这完全是另一类需要调试的东西。其中的 su=0/38(ENOSYS)具体表示链接的是弱桩函数。

mm_struct leak failed 后跟 prepare_kernel_page retry N/24 并不是失败。 这是循环进度,成功的运行同样会显示。只有在 24 次尝试用尽并出现 prepare_kernel_page timeout 时才算失败。同理,probing cfi ... expected=9 伴随 child uid = 2000 只是十轮中漏掉了一轮。

不要根据截断的日志判断一次运行 — 这个错误曾在这里导致一整轮误诊。

整次运行失败也是正常的。 pselect 竞争并非 100% 必胜:一次运行可能连续输掉五次并以 Write 1 failed 结束,而下次运行第一次尝试就拿到 ret=9。这是在该设备上观察到的。ret=4 expected=9 是输掉竞争时的样子,而不是 target.h 有误 — 一次失败不足以去重新推导偏移量。重新运行即可。

文件

路径内容
source/src/preload.c构造函数:运行漏洞利用、上报结果、停止
source/src/main.c漏洞利用本身(Write 1 / Write 2)
source/src/su_daemon.csu 二进制 — 单独构建为 aarch64 PIE,不链接进 .so
source/src/su_blob.S将该 PIE 通过 .incbin 嵌入 .so 的 .rodata
source/src/su_install.c将 blob 写回、启动守护进程、探测它
source/src/target.h暂存目标(gitignored)
targets/<device>/target.h编译期布局:结构体偏移、physmap 常量、slab 和 futex 形态
targets/<device>/device_offsets.h来自 kallsyms 的全局符号偏移
tools/extract_device.pyboot.img → 偏移量、BTF 结构体字段、pselect 叠加结果
tools/preloader_memlayout.pyMediaTek preloader → P0_KERNEL_PHYS_LOAD
tools/qemu_verify.py在 QEMU 下启动内核:测量栈叠加、检查线性映射稳定性
tools/device_probe.sh从非特权 adb shell 执行的预检

许可证

仅限授权的安全研究和教育用途。

下载工具