
محوّل استغلال CVE-2026-43499 لمعالج MT6985 MediaTek Dimensity 9300 (vivo PD2241)
الخلاصة: استهلكت 68M توكن، ولم أحصل على صلاحيات root.
السبب: هجوم التوقيت KernelSnitch غير موثوق على Dimensity 9200 من MTK، وCONFIG_PANIC_ON_OOPS=yلا يترك مجالاً للتجربة والخطأ.
هذه المقالة توثق رحلة الأخطاء الكاملة، كمرجع لمن يأتي بعدنا لتجنب هذه المزالق.
| المشروع | القيمة |
|---|
| الجهاز | vivo PD2241 (Dimensity 9200 / MT6985), Android 15 |
| البرنامج الثابت | PD2241_A_15.2.10.2.W10.V000L1 |
| النواة | 5.15.178-android13-8-gfb31f5bdd612-dirty |
| مُحمّل الإقلاع | مقفل (ro.boot.flash.locked=1) |
| SELinux | Enforcing |
| panic_on_oops | مُفعّل → أي OOPS في النواة = إعادة تشغيل فورية |
| الاستغلال | CyberMeowfia — CVE-2026-43499 (IonStack) |
| شجرة المصدر | android_15.0_kernel_MT6985 (5.15.178) — غير مطابقة لإصدار الجهاز (المصدر هو android15 GKI، والجهاز يعمل بنظام android13 GKI) |
تم الاستخراج من arch/arm64/include/asm/memory.h + include/linux/fs.h + android/abi_gki_aarch64.xml:
KIMAGE_TEXT_BASE = 0xffffffc008000000, VA_BITS=39, DIRECT_MAP=256GBlayout-offset-in-bits)iopoll (android13 GKI), IOCTL=0x48, open=0x68atomic_t usage = 4 بايت, uid=0x04slab_cache=0x18الاكتشاف الرئيسي: المصدر هو android15 GKI، والجهاز هو android13 GKI — إزاحات task_struct تختلف بمقدار 0x40~0x88 بايت، ولا يمكن نسخها حرفياً من المصدر.
OTA zip (8.3GB)
→ payload.bin (8.2GB)
→ payload_dumper → boot.img (96MB, v4 header)
→ فك ضغط LZ4 → Image (50MB ARM64)
→ kallsyms-finder → 187810 رمزاً
تم استخراج الرموز من إصدارين من البرنامج الثابت (15.2.7.6 / 15.2.10.2) — نفس الرمز يختلف بين الإصدارين بمقدار 10KB~200KB، يجب استخدام الإصدار الصحيح.
# داخل rt_mutex_adjust_pi:
LDR x21, [x19, #0x8b0] → pi_blocked_on = 0x8b0 (قيمة android15، وليست frankel)
هذا يؤكد أن تخطيط task_struct هو فرع android15، وليس android13 الخاص بـ frankel.
NDK r29, make PROJECT=android_15.0_kernel_MT6985 → preload.so (150KB).
[+] preload starting pid=25414
[+] p0 profile ... تم تحميل جميع الرموز بشكل صحيح
[-] KernelSnitch mm_struct leak failed ← أحياناً لا تظهر هذه الرسالة (نجاح عرضي)
[+] slide child context route=pselect ← بدء العملية الفرعية لتسريب KASLR slide
[انفجار النواة] ← rt_mutex_adjust_prio_chain+0x1b0
تعليمة الانهيار (capstone):
ldar w8, [x27] ; x27 = waiter->lock (محملة من [x28, #0x38])
; قيمة x27 غير صالحة → لا يوجد تعيين لصفحة الذاكرة → translation fault
; → die() → panic → إعادة تشغيل
KernelSnitch هو مدخل الاستغلال بالكامل — فهو يسرب عنوان mm_struct عبر فروق التوقيت في جدول تجزئة futex:
MT6985 يحتوي على CONFIG_KASAN_HW_TAGS=y → النواة تستخدم وسوم MTE لتمييز تخصيصات slab
مؤشر mm_struct يحمل وسم KASAN → futex_hash يُحسب بناءً على المؤشر الموسوم
لكن مسح bruteforce يغطي الـ direct map (عناوين غير موسومة) → التجزئة المحسوبة غير متطابقة
حتى مع إضافة تكرار وسوم MTE (0-14 أي 15 نوعاً)، على نظام VA_BITS=39
بتات الوسم (bit56-59) تتداخل مع بتات توسيع الإشارة → بعض تركيبات الوسوم تولد عناوين غير صالحة → تفويت
أجهزة Pixel لا تحتوي على KASAN_HW_TAGS، وهذه الآلية تعمل هناك. MTK لا تعمل.
CONFIG_PANIC_ON_OOPS هو القاتلPixel: OOPS في النواة → dump_stack → متابعة التشغيل → يمكن إعادة محاولة الاستغلال
MT6985: OOPS في النواة → die() → panic() → إعادة تشغيل فورية → لا مجال للتجربة والخطأ
أي انحراف بسيط في سلسلة π يؤدي لانهيار كامل، بينما في Pixel الانحراف يعني فقط "لم تنجح هذه المرة، جرب مجموعة عناوين أخرى".
كما أن مُحمّل الإقلاع مقفل (flash.locked=1) → لا يمكن وميض نواة مخصصة لإزالة هذا الخيار.
شجرة المصدر: 5.15.178 android15 GKI
الجهاز: 5.15.178-android13 (بائع vivo)
رغم أن رقم الإصدار الرئيسي هو 5.15.178 في كليهما، إلا أن فرع GKI مختلف (android13 مقابل android15)، وتخطيطات البنى الحرجة مثل task_struct/cred غير متطابقة. تم التنقل مراراً بين مجموعتي إزاحات frankel/android15، وفي النهاية تم التثبيت عبر التفكيك فقط.
| التعديل | الهدف | النتيجة |
|---|---|---|
THRESHOLD_MULT 10→5→3 | خفض عتبة كشف التصادم | <5 إيجابيات كاذبة كثيرة جداً |
APPENDED_FUTEXES 4096→8192 | زيادة فرق سلسلة التجزئة | بلا تأثير |
REPEAT_MEASUREMENT/AVERAGE | زيادة دقة أخذ العينات | بلا تأثير |
MTE=1 | جعل bruteforce يتكرر عبر الوسوم | أبطأ، بل قلل الانهيارات |
MM_STRUCT_SZ 0x500→0x400 | تصحيح خطوة mm_struct | ضروري، ABI الفعلي 992 بايت |
IDENTITY_END 64GB→256GB | توسيع نطاق المسح | بطيء جداً (تكرار MTE)، وما زال غير متطابق |
| إزاحات TASK: android15↔frankel | تثبيت الإزاحة الصحيحة | التفكيك يؤكد android15 |
| إزاحات FOPS: android15↔frankel | android13 لا يحتوي iopoll | استخدام frankel |
في exploit/targets/android_15.0_kernel_MT6985/target.h:
| الفئة | درجة الثقة | طريقة التحقق |
|---|---|---|
| تخطيط الذاكرة | صحيح | حساب memory.h + التحقق عبر kallsyms _text |
| إزاحات الرموز (22 رمزاً) | صحيحة | مستخرجة من boot.img الخاص بـ 15.2.10.2 |
| إزاحات task_struct | صحيحة | ABI XML + تفكيك capstone (pi_blocked_on=0x8b0) |
| إزاحات FOPS | خاطئة (تم تصحيحها في 2026-07-31) | القيمة الأصلية منسوخة من frankel (android13 بلا iopoll)؛ ABI شجرة المصدر الفعلي يحتوي iopoll@0x30، ioctl=0x50، open=0x70 — انظر VERIFICATION.md |
| إزاحات CRED | صحيحة (تم التحقق منها) | ABI XML: uid=0x04، securebits=0x24، caps=0x28، security=0x78 |
التجميع ينجح، والتشغيل يعمل، والخسارة كانت في الميل الأخير.
CONFIG_PANIC_ON_OOPS — إما بوميض نواة مخصصة (يتطلب فك قفل مُحمّل الإقلاع)، أو إيجاد جهاز MT6985 آخر يكون هذا الخيار معطلاً افتراضياً/proc/self/pagemap — مقيد على هذا الجهاز (يعيد أصفاراً)/proc/mtk_*) — موجودة لكنها تحتاج مزيداً من التحليلsymbols/kallsyms_PD2241_15.2.10.2.txt — جدول رموز كامل لإصدار 15.2.10.2، يمكن لمن يأتي بعدنا استخدامه مباشرةdevice_config.txt — إعدادات النواة الفعلية للجهاز، يمكن رؤية ما غيّره البائعexploit/targets/android_15.0_kernel_MT6985/target.h — إزاحات البنى تم التحقق منهاscripts/server_compile.py — ترجمة آلية، إعادة الترجمة سريعة بعد تغيير المعاملاتtar -xf قد يفشل مع ملفات zip الكبيرة، استخدم Python zipfile أو فك الضغط يدوياً أولاًupdate_metadata_pb2.py المُولّد يتطلب protobuf 5.x، يجب حذف سطر استيراد runtime_version يدوياً./preload.so مباشرة يسبب segfault: يجب استخدام /system/bin/linker64 /data/local/tmp/preload.solayout-offset-in-bits محسوب بواسطة المترجم، أدق 100 مرة من العد اليدوي لـ 5000 بايتCONFIG_ANDROID_VENDOR_OEM_DATA=y, CONFIG_SCHED_INFO=y, CONFIG_RSC_* → انحراف عن GKI القياسيboot.img → kernel.bin → فك ضغط LZ4 → Image → kallsyms-finder → جدول الرموز07/28 تنزيل مستودع CyberMeowfia + مصدر MT6985
07/29 تحليل المصدر (memory.h, fs.h, ABI XML, بنى مختلفة)
فك حزم البرنامج الثابت (payload.bin → boot.img → Image)
استخراج الرموز (kallsyms-finder → 187810 رمزاً)
جولات ترجمة متعددة + انهيارات متعددة + تحقق بالتفكيك
5 محاولات إعادة تلقائية → كلها فشلت
كتابة هذا التقرير
-------------------------------------------
الإجمالي: ~68M توكن، 0 قشرة root
2026-07-29، عشت لأروي القصة
بعد الحصول على android_15.0_kernel_MT6985.tar.gz (شجرة مصدر 5.15.178 + ABI XML) تم إجراء
تحقق متقاطع شامل، التفاصيل في VERIFICATION.md. الملخص:
target.h، مع الاستفادة من
leak_kernel_base() المدمج في الاستغلال للتحقق الذاتي على الجهاز الحقيقي كشبكة أمان.KSNITCH_MTE_ENABLED=1 فعالاً فعلاً
(كان util.c الأصلي يثبت mte=0)؛rt_mutex_adjust_prio_chain+0x1b0 تقع في مرحلة سلسلة pselect/pi، قبل
التحقق الذاتي من FOPS؛ بعد التصحيحات أعلاه يستحق إعادة الاختبار على الجهاز.الجهاز (PD2241, compiler251203103903) — أكثر من 8 جولات اختبار على الجهاز الفعلي:
MM_STRUCT_SZ=0x400 (القيمة 0x500 الأصلية كانت
تسبب انحراف شبكة المسح وإيجابيات كاذبة فقط) + تكرار وسوم MTE 0..15 (وسم مؤشر mm للجهاز
يتغير) + إزالة الوسم من العناوين المزيفة. الآن يمكن العثور على mm_struct الحقيقي بشكل مستقر.rt_mutex_adjust_prio_chain+0x1b0: سباق توقيت سلسلة
pselect/pi (الـ waiter المزيف لا يصل إلى الإزاحة الصحيحة في مكدس النواة). جدولة RSC من
vivo عدّلت مسار futex/pi، وقد تدمر هذا السباق جذرياً. محاذاة مكدس slide أصبحت معلمة
عبر متغير البيئة SLIDE_SHIFT، ولم يكتمل المسح.القرار النهائي: النزول إلى فك قفل مُحمّل الإقلاع المدفوع (عدم الاعتماد على هذا المسار بعد الآن).