
استغلال تصعيد الامتيازات المحلي يستهدف 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.
| البند | القيمة | المصدر |
|---|---|---|
| الاسم الرمزي | 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 |
النواة هنا أهم من المعالج 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 منفصلين:
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 |
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، فإن الهدفين يتباعدان — وإبقاؤهما كدليلين منفصلين هو ما يمنع حدوث ذلك بصمت.