
استغلال تصعيد الامتيازات المحلي يستهدف CVE-2026-43499 لهاتف Xiaomi 17T Pro (warhol) على نظام Android 16 مع معالج MediaTek MT6993 SoC.
ثغرة CVE-2026-43499 (IonStack) لتصعيد الصلاحيات محليًا، منقولة إلى Xiaomi 17T Pro (warhol) — MediaTek MT6993، Android 16.
خاص بـ GKI 6.12 / android16 فقط. إن overlay الخاص بـ waiter، وترتيب الـ stack الخاص بـ
pselect، وكل إزاحات struct مثبتة على هذا الفرع، لذلك لا ينتقل أي شيء هنا إلى 6.6 أو 6.1 أو 5.10 — تلك تحتاج إلى قاعدة مبنية لها. يرفضgenerate_target.pyأي لافتة (banner) أخرى بدلًا من إصدار header قد يسبب خطأ (fault) على الجهاز.
الحالة: تعمل. تم التحقق منها على الجهاز بتاريخ 2026-07-26 من إقلاع نظيف، تحت كلا متغيرَي preloader —
uid=0(root) context=u:r:kernel:s0، SELinux متساهل (permissive)، مع تثبيتsu. الجذر ليس دائمًا: أعد تشغيل سطرLD_PRELOADبعد كل إقلاع. سباق pselect ليس مضمونًا 100%: فقد يتسبب تشغيل فاشل في انهيار (panic) الهاتف، وتكرار المحاولة بعد إعادة التشغيل أمر طبيعي. انظرWARHOL_PORT.mdللسجل الكامل.
هذا الجهاز يحتاج إلى إصلاح kernel-MTE. لا يمكن لإصدار popsicle الأصلي غير المعدّل (stock upstream) تثبيت الجذر عليه — يظل عالقًا في
kernel page retry N/12إلى الأبد. انظر قسم Kernel MTE.
تأتي البنيات بنكهتين من preloader: PRELOADER=retail (الافتراضي) و PRELOADER=eng. تختاران دليلَي target مختلفين بحيث لا يمكن لأيٍّ منهما الكتابة فوق عناوين الآخر — انظر متغير Preloader.
قُرئت الحقائق أدناه من حزمة OTA الكاملة للتجزئة (retail) — البيانات الوصفية، وmanifest الخاص بحمولة A/B، وصور boot/vendor_boot/dtbo المستخرجة من payload.bin.
النواة هنا أهم من المعالج SoC: تقع warhol على نفس فرع GKI الذي يعتمد عليه popsicle الأصلي (upstream) (android16-5، صفحات 4K)، بفارق مستوى تصحيح واحد — 6.12.38 مقابل 6.12.23 المُتحقق منها على popsicle. ما تزال إزاحات struct بحاجة إلى إعادة توليد لكل بنية؛ فقط هيئة الاستغلال هي التي تنتقل. أي بنية مختلفة من OS3.0.x تحتاج إلى target.h خاص بها.
سلسلة الاستغلال مربوطة إصدارًا (version-locked) بفرع GKI، وليس بـ SoC. إن تخطيط waiter الخاص بـ rt_mutex، وoverlay الـ stack الخاص بـ pselect، وإزاحات struct الخاصة بـ pipe_buffer كلها تتبع النواة، لذلك تتفوق قاعدة 6.12/android16 على قاعدة من نفس البائع على فرع أقدم.
x-spy/CVE-2026-43499-popsicle هو التطبيق العام الوحيد المُتحقق منه على خط GKI 6.12/android16 (Xiaomi 17 / Pro / Ultra، النواة 6.12.23-android16-5)، لذلك أُخذ source/ منه وأُبقي قريبًا من upstream قدر الإمكان — باستثناء سطرين (انظر قسم Kernel MTE، الذي يحتاجه هذا الجهاز ليعمل إطلاقًا).
معظم الفروقات الخاصة بـ MediaTek محصورة في توليد target في وقت البناء، في جزأين:
يقرأ upstream قيمتي p0_phys_offset وp0_kernel_phys_load من قسم xbl_config الخاص بـ Qualcomm، وهو غير موجود على warhol. يقرأ generate_target.py --dtb بدلًا من ذلك قاعدة DRAM من عقدة /memory في FDT الخاصة بـ vendor_boot، مقرّبًا إياها إلى الأسفل بنفس الطريقة التي يفعلها arm64_memblock_init. عنوان التحميل الفعلي للنواة يُقرأ من حجز mb_kernel في الـ preloader عبر --preloader؛ وتكون قيمة الفرق 0 لهذا الجهاز، ويرفض lk إقلاع نواة موضوعة في أي مكان آخر.
توزّع MediaTek النواة مضغوطة بصيغة LZ4-legacy داخل boot.img بدلًا من صورة arm64 Image مجردة، لذلك يقوم المولّد بفك ضغطها قبل التحليل.
WARHOL_PORT.md §2 يحتوي على الاشتقاق الكامل.
لا يحمّل lk الخاص بـ MediaTek النواة عند ثابت محدد في وقت الترجمة (compile-time constant) — بل يبحث عن حجز DRAM باسم mb_kernel ويتحقق من أنها حطّت عليه تمامًا. هذا الحجز يقوم به preloader، ولهذا السبب تُعد بنية الـ preloader التي يعمل بها الهاتف مدخلًا في البناء هنا أصلًا: فهو الشيء الوحيد خارج boot.img القادر على تحريك P0_KERNEL_PHYS_LOAD، والقيمة الخاطئة هناك تعني alias خاطئًا للخريطة الخطية وهاتفًا ميتًا بدلًا من استغلال فاشل.
ومن ثم يوجد متغيران، يُحفظان كدليلَي target منفصلين:
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
يُحفظ القطعان (artifact) جنبًا إلى جنب تحت اسمين مختلفين. على هذا الـ firmware يخرجان متطابقين بايتًا ببايت — وهذه نتيجة التحليل أدناه، وليست طريقة مختصرة لتجاوزه، لذلك ما يزال الاثنان يُبنيان ويُسمَّيان بشكل منفصل.
اقرأ جدولَي تخطيط الذاكرة للـ preloaderين بنفسك باستخدام:
python3 tools/preloader_memlayout.py <preloader.bin> --diff <preloader_eng.bin>
بالنسبة لهذا الـ firmware فإن الجدولين متطابقان، وmb_kernel.start = 0x80000000 في كليهما، لذلك يخرج target.h الخاص بـ eng مطابقًا بايتًا ببايت لذاك الخاص بـ retail، وينتج كلا البنائين نفس preload.so.
ما يصل فعلًا إلى الاستغلال هو الفرق P0_KERNEL_PHYS_LOAD − P0_PHYS_OFFSET، وحدّاه لا يستندان إلى أدلة بنفس الدرجة من الصلابة:
P0_KERNEL_PHYS_LOAD — مقاس من صورة eng، كما في mb_kernel.start أعلاه.
P0_PHYS_OFFSET — كان مُستدَلًّا عليه لا مُقاسًا، لأنه يمثل memstart_addr، الذي تأخذه النواة من عقدة /memory التي يصدرها lk في وقت التشغيل بناءً على ما يبلغه الـ preloader بعد تهيئة DRAM، وليس من FDT الثابت الذي يقرؤه المولّد. كان الاستدلال أن البنيتين تتشاركان مسار DRAM بالكامل — نفس مصادر dramc/emi/mblock/memory_layout، ولا يوجد كود خاص بتخطيط الذاكرة بين السلاسل الحصرية لبنية eng — وأن حجز eng الإضافي الوحيد، وهو security_fe_rsv ديناميكي بحجم 3 MiB مع mapping=1، لا يمكنه إزاحة مدخل بعنوان ثابت ولا حفر ثقب في أسفل DRAM. التشغيل على الجهاز حسم الأمر: استقرت aliases الخريطة الخطية في مواضعها الصحيحة تحت eng، لذا فالحد صحيح.
الاشتقاق الكامل في targets/warhol-OS3.0.304.0.WPSJPXM-eng/NOTES.md.
ملاحظات من التشغيل على الجهاز:
يقبل BootROM هذا الـ preloader من نوع eng — فقد أقلع بشكل طبيعي إلى Android، مع بقاء ro.boot.verifiedbootstate=green وflash.locked=1 دون تغيير. كان ذلك سؤالًا مفتوحًا حتى جُرِّب؛ يُحسم الأمر بهاش مفتاح الجذر المحروق في efuse (efuse'd root key hash)، وهذه البنية موقعة بنفس مفاتيح retail.
يُبقي الـ preloader من نوع eng وضع META / التنزيل المصنعي (factory download mode) فعّالًا: سلسلة %s META DIS حصرية بـ retail فقط، بينما يحمل eng تنفيذ META كاملًا لا تملكه retail (Enable fastmeta.، FAST META GPIO: %d، META_COM PORT: %d، read_meta_proinfo، …). لقد أقلع مباشرة إلى Android هنا، لكن إذا انتهى هاتف مومَّض بنسخة eng في حالة أخرى، فهذا هو السبب المرجح ولا تُتَّهم أي قيمة في target.h.
إذا غيّر firmware لاحق موضع mb_kernel، فإن الهدفين يتباعدان — وإبقاؤهما كدليلين منفصلين هو ما يمنع حدوث ذلك بصمت.
تعمل هذه النواة بـ KASAN_HW_TAGS — يحمل /proc/cmdline القيمة kasan.stack_ring_size=524288 — لذلك تحمل مؤشرات slab وسم تخصيص (allocation tag) في البتات 59:56. يفترض popsicle الأصلي (upstream) مؤشرات غير موسومة، وبالتالي لا يمكنه تثبيت الجذر على هذا الجهاز إطلاقًا: يظل يدور في [-] kernel page retry N/12 mode=1 إلى ما لا نهاية، دون أن يوضح السجل السبب.
سطران في source/ يصلحان ذلك، وكلاهما موجود في warhol-mte-fix.patch:
فقط الفحوصات هي التي تزيل الوسوم. المؤشرات نفسها تبقى موسومة، لأن الوسم هو الصحيح للذاكرة التي تشير إليها — وهذا بالضبط ما يحتاجه تفكيك إشاراتها (dereference) داخل النواة.
لا تعتمد على arm64.memtag.bootctl في هذا الشأن: فهذه الخاصية هي أداة تحكم MTE على مستوى مساحة المستخدم (userspace)، ولا تخبر شيئًا عما إذا كانت النواة توسم تخصيصاتها الخاصة.
هذا المستودع عبارة عن نقل (port). الفضل في سلسلة الاستغلال يعود إلى upstream.
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
في حال عدم توفر صورة preloader في متناول اليد، فإن --kernel-phys-delta 0 هو الصيغة الأقدم ويعطي نفس الـ header لهذا الـ firmware — لكنه تأكيد (assertion) وليس قراءة، لذلك يُفضَّل --preloader. تمرير كليهما معًا يجعل المولّد يتحقق منهما تبادليًا ويفشل عند عدم التطابق.
--base 5081 هو إزاحة الترويسة المحلية (local-header offset) لـ payload.bin الخاصة بنسخة OTA هذه تحديدًا؛ اقرأها من ota-property-files في META-INF/com/android/metadata لأي حزمة أخرى.
يتطلب 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 في الذاكرة، لذلك يجب تكرار ذلك بعد كل إقلاع. لا يلزم فتح قفل محمل الإقلاع (bootloader unlock).
لأغراض البحث على أجهزة تملكها. قد يؤدي تشغيل هذا إلى حلقة إقلاع (bootloop) أو تعطيل الجهاز نهائيًا (brick)، وسيلغي الضمان. لا يوجد أي ضمان من أي نوع.
المشاريع الأصلية (upstream) مرخصة من قبل مؤلفيها كلٌّ بحسبه؛ وهذه النقل تحمل شروطها.
| البند | القيمة | المصدر |
|---|
| الاسم الرمزي | 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 global) | 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) | قيم التوافق (compatible) mediatek,mt6993-* في DTB الخاصة بـ vendor_boot؛ cmdline bootopt=64S3,32N2,64N2 |
| النواة | 6.12.38-android16-5-g1d46253471dd-ab15048002-4k، clang 19.0.1 | لافتة (banner) مقروءة من boot.img المستخرج |
| صورة الإقلاع | header v4، نواة بحجم 18,898,125 بايت، مضغوطة LZ4-legacy، بدون ramdisk | tools/bootinfo.py |
PRELOADER= | الـ preloader على الهاتف | دليل الـ target | الحالة |
|---|
retail (الافتراضي) | ملف preloader_<device>.bin المُوزَّع مع الجهاز، مطابق بايتًا ببايت لـ preloader_raw.img في ROM الخاص بـ fastboot | targets/warhol-OS3.0.304.0.WPSJPXM/ | ✅ تم التحقق منه على الجهاز 2026-07-26 |
eng | بنية الهندسة (engineering)، 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() يزيلان الوسم (untag) قبل فحص النطاق | المؤشر الموسوم المقروء من ذاكرة النواة يُرفض بخلاف ذلك باعتباره خارج الخريطة الخطية (direct-entry-fatal reason=bad-task-or-cpu) |
src/kernelsnitch/kernelsnitch.h | مسح الوسوم < 15 → < 16 | الوسم 0xf هو المؤشر غير الموسوم/المطابق للكل (match-all)، لذلك يغطي المسح الآن أيضًا نواة بدون MTE — mte_enabled=1 آمن في الحالتين |
| المستودع | الدور هنا |
|---|
| https://github.com/MobiusM/CVE-2026-43499 | PoC الأصلي لـ CVE-2026-43499 / مشغّل الانهيار (crash trigger) |
| 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 مبنيًا مسبقًا — ثوابته المدمجة (compiled-in) أكدت إزاحة التحميل الفعلية |
| https://github.com/MiCode/Xiaomi_Kernel_OpenSource | مصادر نواة Xiaomi، للتحقق المتبادل من تخطيط struct |