
MediaTek MT6993 SoC के साथ Android 16 पर Xiaomi 17T Pro (warhol) के लिए CVE-2026-43499 को लक्षित करने वाला स्थानीय विशेषाधिकार वृद्धि शोषण।
CVE-2026-43499 (IonStack) स्थानीय विशेषाधिकार वृद्धि, Xiaomi 17T Pro (warhol) पर पोर्ट किया गया — MediaTek MT6993, Android 16.
केवल GKI 6.12 / android16। वेटर ओवरले,
pselectस्टैक लेआउट और हर स्ट्रक्ट ऑफ़सेट उसी ब्रांच से बंधे हैं, इसलिए यहाँ की कोई भी चीज़ 6.6, 6.1 या 5.10 पर स्थानांतरित नहीं होती — उनके लिए उन पर बनाया गया बेस चाहिए।generate_target.pyकिसी अन्य बैनर को अस्वीकार कर देता है, बजाय ऐसा हेडर निकालने के जो डिवाइस पर फॉल्ट करे।
स्थिति: कार्यशील। 2026-07-26 को क्लीन बूट से डिवाइस पर सत्यापित, दोनों प्रीलोडर वेरिएंट के तहत —
uid=0(root) context=u:r:kernel:s0, SELinux परमिसिव,suस्थापित। रूट स्थायी नहीं है: हर बूट के बादLD_PRELOADलाइन दोबारा चलाएँ। pselect रेस 100% नहीं है: असफल रन फ़ोन को पैनिक कर सकता है, और रीबूट के बाद पुनः प्रयास सामान्य है। पूरा विवरणWARHOL_PORT.mdमें देखें।
इस डिवाइस को kernel-MTE फिक्स चाहिए। स्टॉक अपस्ट्रीम popsicle इसे रूट नहीं कर सकता — यह हमेशा के लिए
kernel page retry N/12में घूमता रहता है। देखें Kernel MTE।
बिल्ड दो प्रीलोडर स्वादों में आते हैं, PRELOADER=retail (डिफ़ॉल्ट) और PRELOADER=eng। वे अलग-अलग टारगेट निर्देशिकाओं का चयन करते हैं ताकि कोई एक दूसरे के पते अधिलेखित न कर सके — देखें Preloader variant।
फ़ैक्ट्स नीचे रिटेल पूर्ण-OTA पैकेज से पढ़े गए हैं — मेटाडेटा, A/B पेलोड मैनिफेस्ट, और payload.bin से निकाली गई boot/vendor_boot/dtbo इमेज।
यहाँ SoC से ज़्यादा कर्नेल मायने रखता है: warhol उसी GKI ब्रांच पर है जिस पर अपस्ट्रीम popsicle (android16-5, 4K पेज), एक पैचलेवल के अंतर पर — 6.12.38 बनाम popsicle का सत्यापित 6.12.23। स्ट्रक्ट ऑफ़सेट अभी भी हर बिल्ड के लिए पुनः उत्पन्न करने पड़ते हैं; केवल एक्सप्लॉइट का आकार आगे बढ़ता है। किसी अलग OS3.0.x बिल्ड को अपना target.h चाहिए।
एक्सप्लॉइट चेन कर्नेल के बजाय GKI ब्रांच के संस्करण-लॉक है। rt_mutex वेटर लेआउट, pselect स्टैक ओवरले और pipe_buffer स्ट्रक्ट ऑफ़सेट सभी कर्नेल पर निर्भर हैं, इसलिए पुरानी ब्रांच पर समान-विक्रेता बेस की तुलना में 6.12/android16 बेस बेहतर है।
x-spy/CVE-2026-43499-popsicle एकमात्र सार्वजनिक इम्प्लीमेंटेशन है जो 6.12/android16 GKI लाइन (Xiaomi 17 / Pro / Ultra, कर्नेल 6.12.23-android16-5) पर सत्यापित है, इसलिए source/ उसी से लिया गया है और जितना संभव हो उतना अपस्ट्रीम के करीब रखा गया है — दो लाइनों को छोड़कर (देखें Kernel MTE, जिसकी इस डिवाइस को काम करने के लिए ज़रूरत ही है)।
MediaTek डेल्टा का अधिकांश भाग बिल्ड-टाइम टारगेट जनरेशन तक सीमित है, दो हिस्सों में:
p0_phys_offset और p0_kernel_phys_load को Qualcomm xbl_config पार्टीशन से पढ़ता है, जो warhol के पास नहीं है। generate_target.py --dtb इसके बजाय vendor_boot FDT के /memory नोड से DRAM बेस पढ़ता है, और उसी तरह नीचे की ओर पूर्णांकित करता है जैसे arm64_memblock_init करता है। कर्नेल का भौतिक लोड पता प्रीलोडर की mb_kernel रिज़र्वेशन से --preloader के साथ पढ़ा जाता है; इस डिवाइस के लिए डेल्टा 0 निकलता है, और lk कर्नेल को कहीं और रखे जाने पर बूट करने से इनकार कर देता है।Image के बजाय boot.img के अंदर LZ4-legacy कम्प्रेस्ड भेजता है, इसलिए जनरेटर विश्लेषण से पहले उसे डिकम्प्रेस करता है।WARHOL_PORT.md §2 में पूरी व्युत्पत्ति है।
MediaTek lk कर्नेल को कम्पाइल-टाइम कॉन्स्टेंट पर लोड नहीं करता — यह mb_kernel नामक DRAM रिज़र्वेशन खोजता है और जाँच करता है कि वह ठीक उसी पर उतरा है। वह रिज़र्वेशन प्रीलोडर द्वारा की जाती है, यही वजह है कि फ़ोन पर चल रहा प्रीलोडर बिल्ड यहाँ एक बिल्ड इनपुट है: यह वह एकमात्र चीज़ है जो boot.img के बाहर P0_KERNEL_PHYS_LOAD को स्थानांतरित कर सकती है, और वहाँ गलत मान का अर्थ गलत लीनियर-मैप एलियास और एक मृत फ़ोन है, न कि एक असफल एक्सप्लॉइट।
इसलिए दो वेरिएंट, अलग-अलग टारगेट निर्देशिकाओं के रूप में रखे गए:
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
दोनों आर्टिफैक्ट अलग-अलग नामों से साथ-साथ रखे जाते हैं। इस फर्मवेयर पर वे बाइट-समान निकलते हैं — यह नीचे दिए गए विश्लेषण का परिणाम है, उसका शॉर्टकट नहीं, इसलिए दोनों अभी भी अलग-अलग बनाए और नामित किए जाते हैं।
दोनों प्रीलोडरों की मेमोरी-लेआउट तालिकाएँ स्वयं पढ़ें:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
इस फर्मवेयर के लिए दोनों तालिकाएँ समान हैं, दोनों में mb_kernel.start = 0x80000000, इसलिए eng target.h रिटेल वाले से बाइट-समान निकलता है और दोनों बिल्ड एक ही preload.so उत्पन्न करते हैं।
एक्सप्लॉइट तक जो पहुँचता है वह अंतर P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET है, और इसके दोनों पद समान रूप से ठोस साक्ष्य पर नहीं टिकते:
P0_KERNEL_PHYS_LOAD — eng इमेज से मापा गया, ऊपर mb_kernel.start के रूप में।P0_PHYS_OFFSET — तर्क द्वारा स्थापित किया गया, मापा नहीं गया, क्योंकि यह memstart_addr है, जिसे कर्नेल उस /memory नोड से लेता है जिसे lk रनटाइम पर उत्सर्जित करता है उसके अनुसार जो प्रीलोडर DRAM इनिशियलाइज़ेशन के बाद रिपोर्ट करता है, न कि उस स्थिर FDT से जिसे जनरेटर पढ़ता है। तर्क यह था कि दोनों बिल्ड पूरे DRAM पथ को साझा करते हैं — समान dramc/emi/mblock/memory_layout स्रोत, eng-केवल स्ट्रिंग्स के बीच कोई मेमोरी-लेआउट कोड नहीं — और eng प्रीलोडर का एक अतिरिक्त रिज़र्वेशन, 3 MiB गतिशील security_fe_rsv जिसमें mapping=1 है, न तो निश्चित-पते वाली प्रविष्टि को विस्थापित कर सकता है और न ही DRAM के नीचे एक छेद बना सकता है। डिवाइस रन ने इसे सुलझा दिया: लीनियर-मैप एलियास eng के अंतर्गत उतरे, इसलिए पद सही है।पूरी व्युत्पत्ति targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md में।
डिवाइस रन के नोट्स:
ro.boot.verifiedbootstate=green और flash.locked=1 अपरिवर्तित रहे। यह तब तक एक खुला प्रश्न था जब तक इसे आज़माया नहीं गया; यह efuse'd रूट कुंजी हैश द्वारा तय होता है, और यह बिल्ड रिटेल जैसी ही कुंजियों से हस्ताक्षरित है।%s META DIS केवल-रिटेल स्ट्रिंग है, जबकि eng में एक पूर्ण META इम्प्लीमेंटेशन है जो रिटेल में नहीं है (Enable fastmeta., FAST META GPIO: %d, META_COM PORT: %d, read_meta_proinfo, …)। यह यहाँ सीधे Android में बूट हुआ, लेकिन यदि eng-फ्लैश किया गया फ़ोन कभी कहीं और पहुँचे, तो यही संभावित कारण है और कोई target.h मान इसमें शामिल नहीं है।mb_kernel को स्थानांतरित करता है, तो दोनों टारगेट अलग हो जाते हैं — उन्हें अलग निर्देशिकाओं में रखना ही उसे मौन होने से रोकता है।इस कर्नेल पर KASAN_HW_TAGS चल रहा है — /proc/cmdline में kasan.stack_ring_size=524288 है — इसलिए स्लैब पॉइंटर्स बिट्स 59:56 में आवंटन टैग रखते हैं। अपस्ट्रीम popsicle बिना टैग वाले पॉइंटर्स मानता है और इसलिए इस डिवाइस को बिल्कुल रूट नहीं कर सकता: यह अनिश्चित काल तक [-] kernel page retry N/12 mode=1 में लूप करता है, और लॉग में कुछ भी यह नहीं बताता कि क्यों।
source/ में दो लाइनें इसे ठीक करती हैं, और दोनों warhol-mte-fix.patch में हैं:
केवल जाँचें अनटैग करती हैं। पॉइंटर्स स्वयं टैग किए हुए रहते हैं, क्योंकि टैग उस मेमोरी के लिए सही है जिसकी ओर वे इशारा करते हैं — यही वह चीज़ है जो एक कर्नेल डीरेफ़रेंस को उनकी ज़रूरत होती है।
इसके लिए arm64.memtag.bootctl न पढ़ें: वह प्रॉपर्टी यूज़रस्पेस MTE नियंत्रण है और यह कुछ नहीं बताती कि कर्नेल अपने स्वयं के आवंटन टैग कर रहा है या नहीं।
यह रिपॉज़िटरी एक पोर्ट है। एक्सप्लॉइट चेन का श्रेय अपस्ट्रीम को जाता है।
source/ exploit core (upstream popsicle + the kernel-MTE fix; see warhol-mte-fix.patch)
src/ main.c slide.c fops.c pipe.c util.c preload.c su_daemon.c + kernelsnitch/
targets/ per-build generated target.h, one dir per fingerprint x preloader variant
warhol-OS3.0.304.0.WPSJPXM/ PRELOADER=retail
warhol-OS3.0.304.0.WPSJPXM-eng/ PRELOADER=eng
tools/
payload_dump.py extract partitions from an A/B payload.bin (verifies size+sha256)
kernel_banner.py read the Linux banner out of a boot.img
bootinfo.py dump boot / vendor_boot header fields
generate_target.py target.h generator; upstream's, MediaTek-only, plus kernel decompression
preloader_memlayout.py dump/diff a MediaTek preloader's static DRAM reservation table
lz4legacy.py LZ4 legacy-frame decompressor (kernel images)
out/ build artifacts (gitignored)
कुछ भी तब तक नहीं बनता जब तक target.h मौजूद न हो — Makefile गलत कर्नेल के विरुद्ध बाइनरी उत्सर्जित करने के बजाय ज़ोर से विफल होता है।
# 1. extract boot.img straight out of the OTA zip (payload.bin is stored uncompressed,
# so --base seeks into the zip; no need to unpack 7.6 GB first)
python3 tools/payload_dump.py <ota.zip> --base 5081 -p boot,vendor_boot,dtbo -o <dir>
# 2. confirm the kernel banner — every offset downstream is pinned to it
python3 tools/kernel_banner.py <dir>/boot.img
python3 tools/bootinfo.py <dir>/boot.img
# 3. generate the target header. --preloader reads the kernel's physical load address
# out of the preloader's mb_kernel reservation; pass the preloader the phone runs.
python3 tools/generate_target.py \
--boot <dir>/boot.img --dtb <dir>/vendor_boot.img --preloader <preloader.bin> \
-o targets/warhol-OS3.0.304.0.WPSJPXM/target.h
# 4. build
make preload # DEVICE=warhol-OS3.0.304.0.WPSJPXM, PRELOADER=retail by default
हाथ में प्रीलोडर इमेज के बिना, --kernel-phys-delta 0 पुराना रूप है और इस फर्मवेयर के लिए वही हेडर देता है — लेकिन यह एक पठन के बजाय एक दावा है, इसलिए --preloader को प्राथमिकता दें। दोनों पास करने पर जनरेटर उन्हें क्रॉस-चेक करता है और बेमेल होने पर विफल हो जाता है।
--base 5081 इस विशेष OTA के लिए payload.bin का लोकल-हेडर ऑफ़सेट है; किसी अन्य पैकेज के लिए इसे META-INF/com/android/metadata में ota-property-files से पढ़ें।
एक Android NDK (NDK_ROOT / ANDROID_NDK_HOME) और llvm-objdump आवश्यक है।
# <device> is the target name: <fingerprint> for PRELOADER=retail, <fingerprint>-eng for eng
adb push out/preload-<device>.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"
cred और SELinux स्थिति मेमोरी में पैच की जाती हैं, इसलिए इसे हर बूट के बाद दोहराना पड़ता है। बूटलोडर अनलॉक आवश्यक नहीं है।
अपने स्वामित्व वाले हार्डवेयर पर शोध के लिए। इसे चलाने से डिवाइस बूटलूप या ब्रिक हो सकता है और वारंटी शून्य हो जाएगी। किसी भी प्रकार की कोई वारंटी नहीं।
अपस्ट्रीम प्रोजेक्ट उनके संबंधित लेखकों द्वारा लाइसेंस प्राप्त हैं; यह पोर्ट उनकी शर्तों को वहन करता है।
| आइटम | मान | स्रोत |
|---|
| कोडनेम | warhol | pre-device=warhol |
| बिल्ड फ़िंगरप्रिंट | Xiaomi/warhol_global/warhol:16/BP2A.250605.031.A3/OS3.0.304.0.WPSJPXM:user/release-keys | post-build |
| इन्क्रीमेंटल | OS3.0.304.0.WPSJPXM (JP ग्लोबल) | post-build-incremental |
| Android | 16, SDK 36 | post-sdk-level=36 |
| सुरक्षा पैच | 2026-05-01 | post-security-patch-level |
| OTA प्रकार | A/B (payload.bin, CrAU, 39 पार्टीशन) | ota-type=AB |
| SoC | MediaTek MT6993 (Dimensity 9500) | vendor_boot DTB में mediatek,mt6993-* कम्पेटिबल; कमांडलाइन bootopt=64S3,32N2,64N2 |
| कर्नेल | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k, clang 19.0.1 | निकाली गई boot.img से पढ़ा गया बैनर |
| बूट इमेज | हेडर v4, कर्नेल 18,898,125 B, LZ4-legacy कम्प्रेस्ड, कोई ramdisk नहीं | tools/bootinfo.py |
PRELOADER= | फ़ोन पर प्रीलोडर | टारगेट निर्देशिका | स्थिति |
|---|
retail (डिफ़ॉल्ट) | शिपिंग preloader_<device>.bin, फास्टबूट ROM के preloader_raw.img से बाइट-समान | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ डिवाइस पर 2026-07-26 को सत्यापित |
eng | इंजीनियरिंग बिल्ड, preloader_<device>_eng.bin | targets/warhol-OS3.0.304.0.WPSJPXM-eng/ | ✅ डिवाइस पर 2026-07-26 को सत्यापित |
| फ़ाइल | परिवर्तन | क्यों |
|---|
src/util.c | kernelsnitch_setup(..., mte_enabled=1) | 0 के साथ, mm_struct खोज केवल बिना टैग वाले उम्मीदवारों को आज़माती है और टैग किए गए पॉइंटर तक कभी नहीं पहुँच सकती — एक संरचनात्मक चूक, बदकिस्मती नहीं |
src/util.c | is_kernel_ptr() / is_direct_ptr() रेंज जाँच से पहले अनटैग करते हैं | अन्यथा कर्नेल मेमोरी से पढ़ा गया एक टैग किया हुआ पॉइंटर लीनियर-मैप में नहीं होने के कारण अस्वीकार कर दिया जाता है (direct-entry-fatal reason=bad-task-or-cpu) |
src/kernelsnitch/kernelsnitch.h | टैग स्वीप < 15 → < 16 | टैग 0xf है बिना टैग/सभी-से-मेल खाने वाला पॉइंटर, इसलिए स्वीप अब बिना MTE वाले कर्नेल को भी कवर करता है — mte_enabled=1 दोनों तरह से सुरक्षित है |
| रिपॉज़िटरी | यहाँ भूमिका |
|---|
| https://github.com/MobiusM/CVE-2026-43499 | मूल CVE-2026-43499 PoC / क्रैश ट्रिगर |
| https://github.com/x-spy/CVE-2026-43499-popsicle | इस पोर्ट का आधार। Xiaomi 17 Pro Max (popsicle), कर्नेल 6.12.23-android16-5; source/ और tools/generate_target.py यहीं से आते हैं |
| https://github.com/Kananosa/CVE-2026-43499-For-Xiaomi-17T-chagall | सबसे करीबी सहोदर डिवाइस (Xiaomi 17T, chagall, MediaTek भी)। popsicle-व्युत्पन्न, एक प्रीबिल्ट preload.so भेजता है — इसके संकलित-इन कॉन्स्टेंट ने भौतिक लोड ऑफ़सेट की पुष्टि की |
| https://github.com/MiCode/Xiaomi_Kernel_OpenSource | स्ट्रक्ट लेआउट की क्रॉस-चेकिंग के लिए Xiaomi कर्नेल स्रोत |