
Lokaler Privilege-Escalation-Exploit, der CVE-2026-43499 für das Xiaomi 17T Pro (warhol) unter Android 16 mit MediaTek-MT6993-SoC ausnutzt.
CVE-2026-43499 (IonStack) lokale Privilegienerweiterung, portiert auf das Xiaomi 17T Pro
(warhol) — MediaTek MT6993, Android 16.
Nur GKI 6.12 / android16. Das Waiter-Overlay, das
pselect-Stack-Layout und jeder Struct-Offset sind an diesen Branch gebunden, daher lässt sich hier nichts auf 6.6, 6.1 oder 5.10 übertragen — dafür braucht es eine eigens dafür gebaute Basis.generate_target.pylehnt jeden anderen Banner ab, anstatt einen Header zu erzeugen, der auf dem Gerät einen Fehler auslösen würde.
Status: funktionsfähig. Am 2026-07-26 auf dem Gerät ab einem sauberen Boot verifiziert, unter beiden Preloader-Varianten —
uid=0(root) context=u:r:kernel:s0, SELinux permissiv,suinstalliert. Root ist nicht persistent: DieLD_PRELOAD-Zeile muss nach jedem Boot erneut ausgeführt werden. Die pselect-Race ist nicht 100 %: Ein fehlgeschlagener Lauf kann das Telefon in den Kernel-Panic treiben, und ein erneuter Versuch nach dem Neustart ist normal. SieheWARHOL_PORT.mdfür die vollständige Aufstellung.
Dieses Gerät benötigt den Kernel-MTE-Fix. Der unveränderte Upstream-Popsicle kann es nicht rooten — er dreht endlos in
kernel page retry N/12. Siehe Kernel-MTE.
Die Builds gibt es in zwei Preloader-Varianten, PRELOADER=retail (Standard) und
PRELOADER=eng. Sie wählen unterschiedliche Zielverzeichnisse, sodass keiner die Adressen des
anderen überschreiben kann — siehe Preloader-Variante.
Die folgenden Fakten wurden aus dem Retail-Full-OTA-Paket ausgelesen — Metadaten, das
A/B-Payload-Manifest und die aus payload.bin extrahierten boot/vendor_boot/dtbo-Images.
| Punkt | Wert | Quelle |
|---|---|---|
| Codename | warhol | pre-device=warhol |
| Build-Fingerabdruck | Xiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keys | post-build |
| Inkrement | OS3.0.304.0.WPSJPXM (JP global) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| Sicherheitspatch | 2026-05-01 | post-security-patch-level |
| OTA-Typ | A/B (payload.bin, CrAU, 39 Partitionen) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | mediatek,mt6993-*-Compatibles im vendor_boot-DTB; cmdline bootopt=64S3,32N2,64N2 |
| Kernel | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | Banner aus dem extrahierten boot.img gelesen |
| Boot-Image | Header v4, Kernel 18,898,125 B, LZ4-Legacy-komprimiert, kein Ramdisk | tools/bootinfo.py |
Der Kernel ist hier wichtiger als das SoC: warhol liegt auf dem gleichen GKI-Branch wie
Upstream-Popsicle (android16-5, 4K-Seiten), einen Patchlevel entfernt — 6.12.38
gegenüber dem verifizierten 6.12.23 von Popsicle. Struktur-Offsets müssen dennoch pro Build
neu erzeugt werden; nur die Form des Exploits überträgt sich. Ein anderes OS3.0.x-Build
benötigt sein eigenes target.h.
Die Exploit-Kette ist versionsmäßig an den GKI-Branch gebunden, nicht an das SoC. Das
rt_mutex-Waiter-Layout, das pselect-Stack-Overlay und die pipe_buffer-Struktur-Offsets
folgen alle dem Kernel, daher schlägt eine 6.12/android16-Basis eine Basis desselben
Herstellers auf einem älteren Branch.
x-spy/CVE-2026-43499-popsicle ist die einzige öffentliche Implementierung, die auf der
6.12/android16-GKI-Linie verifiziert wurde (Xiaomi 17 / Pro / Ultra, Kernel
6.12.23-android16-5), daher stammt source/ daraus und wird so nah wie möglich am Upstream
gehalten — abgesehen von zwei Zeilen (siehe Kernel-MTE, das dieses Gerät
überhaupt erst zum Funktionieren braucht).
Der Großteil der MediaTek-Abweichungen beschränkt sich auf die Zielgenerierung zur Buildzeit, in zwei Teilen:
p0_phys_offset und p0_kernel_phys_load aus einer Qualcomm-xbl_config-Partition, die warhol nicht hat. generate_target.py --dtb liest stattdessen die DRAM-Basis aus dem /memory-Knoten des vendor_boot-FDT und rundet sie genauso ab, wie arm64_memblock_init es tut. Die physische Ladeadresse des Kernels wird mit --preloader aus der mb_kernel-Reservierung des Preloaders ausgelesen; die Differenz ergibt für dieses Gerät 0, und lk weigert sich, einen an einer anderen Stelle platzierten Kernel zu booten.boot.img aus und nicht als nacktes arm64-Image, daher dekomprimiert der Generator es vor der Analyse.WARHOL_PORT.md §2 enthält die vollständige Herleitung.
MediaTek-lk lädt den Kernel nicht an eine Compile-Zeit-Konstante — es sucht eine
DRAM-Reservierung namens mb_kernel und stellt sicher, dass er exakt dort gelandet ist. Diese
Reservierung nimmt der Preloader vor, weshalb die auf dem Telefon laufende
Preloader-Version hier überhaupt ein Build-Eingang ist: Sie ist das einzige Element außerhalb
von boot.img, das P0_KERNEL_PHYS_LOAD verschieben kann, und ein falscher Wert dort bedeutet
einen falschen Linear-Map-Alias und ein totes Telefon statt eines fehlgeschlagenen Exploits.
Daher zwei Varianten, die als getrennte Zielverzeichnisse geführt werden:
PRELOADER= | Preloader auf dem Telefon | Zielverzeichnis | Status |
|---|---|---|---|
retail (Standard) | das ausgelieferte preloader_<device>.bin, byteidentisch mit dem preloader_raw.img der Fastboot-ROM | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ am 2026-07-26 auf dem Gerät verifiziert |
eng | das Engineering-Build, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ am 2026-07-26 auf dem Gerät verifiziert |
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
Beide Artefakte werden unter verschiedenen Namen nebeneinander abgelegt. Bei dieser Firmware fallen sie byteidentisch aus — das ist das Ergebnis der folgenden Analyse und keine Abkürzung daran vorbei, daher werden die beiden weiterhin getrennt gebaut und benannt.
Lies die Speicherlayout-Tabellen der beiden Preloader selbst mit:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
Bei dieser Firmware sind die beiden Tabellen identisch, mb_kernel.start = 0x80000000 in
beiden, daher ist das target.h des eng-Builds byteidentisch mit dem des Retail-Builds und
beide Builds erzeugen dasselbe preload.so.
Was tatsächlich im Exploit ankommt, ist die Differenz
P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET, und ihre beiden Terme stützen sich nicht auf gleich
belastbare Belege:
P0_KERNEL_PHYS_LOAD — gemessen aus dem eng-Image, als mb_kernel.start oben.P0_PHYS_OFFSET — wurde argumentiert statt gemessen, da es memstart_addr ist, das der Kernel aus dem /memory-Knoten übernimmt, den lk zur Laufzeit aus dem erzeugt, was der Preloader nach der DRAM-Initialisierung meldet, nicht aus dem statischen FDT, den der Generator liest. Das Argument war, dass beide Builds den gesamten DRAM-Pfad teilen — dieselben dramc/emi/mblock/memory_layout-Quellen, kein Speicherlayout-Code unter den Nur-eng-Strings — und dass die eine zusätzliche Reservierung des eng-Preloaders, ein dynamisches 3-MiB-security_fe_rsv mit mapping=1, weder einen Eintrag mit fester Adresse verschieben noch ein Loch am unteren Ende des DRAM reißen kann. Der Lauf auf dem Gerät hat es entschieden: Die Linear-Map-Aliase landeten unter eng, also stimmt der Term.Vollständige Herleitung in
targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
Notizen vom Lauf auf dem Gerät: