Skip to content
KitploitKITPLOIT
ToolsExploitsBlog
Log in
Submit
ToolsExploitsBlog
Submit

Hacking, PenTest, and Cybersecurity Tools for Your Security Arsenal!

Kitploit is a directory of hacking, cybersecurity, and pentesting tools. Discover the latest project updates to find vulnerabilities, analyze systems, automate testing, and strengthen your security.

FeedsContactPrivacy© 2026 Kitploit

Tool Directory

Categories

View all categories
Loading categories
CVE-2026-43499-warhol-root — Local privilege escalation exploit targeting CVE-2026-43499 for the Xiaomi 17T Pro (warhol) on Android 16 with MediaTek MT6993 SoC. | Kitploit
Tools/GitHubGitHub/soralis0912/cve-2026-43499-warhol-root
Android SecurityPrivilege EscalationVulnerability AnalysisExploitationBinary Exploitation
GitHubsoralis0912/cve-2026-43499-warhol-root

CVE-2026-43499-warhol-root

Local privilege escalation exploit targeting CVE-2026-43499 for the Xiaomi 17T Pro (warhol) on Android 16 with MediaTek MT6993 SoC.

View Repository

Most Popular

View all →

Discover the most used tools by our community.

Explore all tools

Browse our collection of tools

View all tools →
Share
11842 months agoNot yet reviewed

warhol-root

CVE-2026-43499 (IonStack) local privilege escalation, ported to the Xiaomi 17T Pro (warhol) — MediaTek MT6993, Android 16.

GKI 6.12 / android16 only. The waiter overlay, the pselect stack layout and every struct offset are pinned to that branch, so nothing here transfers to 6.6, 6.1 or 5.10 — those need a base built for them. generate_target.py refuses any other banner rather than emit a header that would fault on device.

Status: working. Verified on device 2026-07-26 from a clean boot, under both preloader variants — uid=0(root) context=u:r:kernel:s0, SELinux permissive, su installed. Root is not persistent: re-run the LD_PRELOAD line after each boot. The pselect race is not 100%: a failed run can panic the phone, and a retry after the reboot is normal. See WARHOL_PORT.md for the full ledger.

This device needs the kernel-MTE fix. Stock upstream popsicle cannot root it — it spins in kernel page retry N/12 forever. See Kernel MTE.

Builds come in two preloader flavours, PRELOADER=retail (default) and PRELOADER=eng. They select different target directories so neither can overwrite the other's addresses — see Preloader variant.


Target device

Facts below were read out of the retail full-OTA package — metadata, the A/B payload manifest, and the boot/vendor_boot/dtbo images extracted from payload.bin.

ItemValueSource
Codenamewarholpre-device=warhol
Build fingerprintXiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keyspost-build
IncrementalOS3.0.304.0.WPSJPXM (JP global)post-build-incremental
Android16, SDK 36post-sdk-level=36
Security patch2026-05-01post-security-patch-level
OTA typeA/B (payload.bin, CrAU, 39 partitions)ota-type=AB
SoCMediaTek MT6993 (Dimensity 9500)mediatek,mt6993-* compatibles in the vendor_boot DTB; cmdline bootopt=64S3,32N2,64N2
Kernel6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1banner read from the extracted boot.img
boot imageheader v4, kernel 18,898,125 B, LZ4-legacy compressed, no ramdisktools/bootinfo.py

The kernel matters more than the SoC here: warhol sits on the same GKI branch as upstream popsicle (android16-5, 4K pages), one patchlevel apart — 6.12.38 vs popsicle's verified 6.12.23. Struct offsets still have to be regenerated per build; only the exploit shape carries over. A different OS3.0.x build needs its own target.h.


Why this base

The exploit chain is version-locked to the GKI branch, not to the SoC. The rt_mutex waiter layout, the pselect stack overlay and the pipe_buffer struct offsets all track the kernel, so a 6.12/android16 base beats a same-vendor base on an older branch.

x-spy/CVE-2026-43499-popsicle is the only public implementation verified on the 6.12/android16 GKI line (Xiaomi 17 / Pro / Ultra, kernel 6.12.23-android16-5), so source/ is taken from it and kept as close to upstream as possible — two lines aside (see Kernel MTE, which this device needs to work at all).

Most of the MediaTek delta is confined to build-time target generation, in two pieces:

  • upstream reads p0_phys_offset and p0_kernel_phys_load out of a Qualcomm xbl_config partition, which warhol does not have. generate_target.py --dtb reads the DRAM base from the /memory node of the vendor_boot FDT instead, rounding it down the same way arm64_memblock_init does. The kernel's physical load address is read out of the preloader's mb_kernel reservation with --preloader; the delta comes out 0 for this device, and lk refuses to boot a kernel placed anywhere else.
  • MediaTek ships the kernel LZ4-legacy compressed inside boot.img rather than as a bare arm64 Image, so the generator decompresses it before analysis.

WARHOL_PORT.md §2 has the full derivation.


Preloader variant

MediaTek lk does not load the kernel to a compile-time constant — it looks up a DRAM reservation named mb_kernel and asserts it landed exactly on it. That reservation is made by the preloader, which is why the preloader build the phone is running is a build input here at all: it is the one thing outside boot.img that can move P0_KERNEL_PHYS_LOAD, and a wrong value there means a wrong linear-map alias and a dead phone rather than a failed exploit.

Hence two variants, kept as separate target directories:

PRELOADER=Preloader on the phoneTarget directoryStatus
retail (default)the shipping preloader_<device>.bin, byte-identical to the fastboot ROM's preloader_raw.imgtargets/warhol-OS3.0.304.0.WPSJPXM/✅ verified on device 2026-07-26
engthe engineering build, preloader_<device>_eng.bintargets/warhol-OS3.0.304.0.WPSJPXM-eng/✅ verified on device 2026-07-26
make preload                  # PRELOADER=retail -> out/preload-<device>.so
make PRELOADER=eng preload    #                  -> out/preload-<device>-eng.so
make both                     # both of the above, and print their sha256

Both artifacts are kept side by side under distinct names. On this firmware they come out byte-identical — that is the result of the analysis below, not a shortcut around it, so the two are still built and named separately.

Read the two preloaders' memory-layout tables yourself with:

python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>

For this firmware the two tables are identical, mb_kernel.start = 0x80000000 in both, so the eng target.h comes out byte-identical to the retail one and both builds produce the same preload.so.

What actually reaches the exploit is the difference P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, and its two terms do not rest on equally hard evidence:

  • P0_KERNEL_PHYS_LOAD — measured out of the eng image, as mb_kernel.start above.
  • P0_PHYS_OFFSET — was argued rather than measured, since it is memstart_addr, which the kernel takes from the /memory node lk emits at runtime from what the preloader reports after DRAM init, not from the static FDT the generator reads. The argument was that the two builds share the whole DRAM path — same dramc/emi/mblock/memory_layout sources, no memory-layout code among the eng-only strings — and that the eng preloader's one extra reservation, a 3 MiB dynamic security_fe_rsv with mapping=1, can neither displace a fixed-address entry nor carve a hole at the bottom of DRAM. The device run settled it: the linear-map aliases landed under eng, so the term is right.

Full derivation in targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.

Notes from the device run:

Download Tool