
إثبات المفهوم CVE-2025-24990 (برنامج تشغيل Agere Systems)
ltmdm64.sys). هذا البرنامج قديم جدًا ولا يتم تحميله افتراضيًا على جهاز الاختبار الخاص بي، لذا سأستغله في سيناريو BYOVD. ومن المثير للاهتمام، وفقًا لبحثي، أن هذا البرنامج موجود منذ ويندوز 7 ويحتوي على خطأ واحد على الأقل. هنا
في ذلك الوقت، لم يتخذ MSRC أي إجراء 🤡
تستخدم بعض أكواد IOCTL داخل هذا البرنامج METHOD_NEITHER ولكنها لا تتحقق مما إذا كان عنوان المخزن المؤقت المقدم من المتصل هو من وضع المستخدم أو وضع النواة. إليك مثال على كود IOCTL قمت بفك ترميزه باستخدام OSR:
هذا يعني أنه يمكنك توفير عنوان نواة لواجهة برمجة التطبيقات DeviceIoControl وسيتعامل معه البرنامج بشكل طبيعي.
لاحظ أنه يجب عليك تجاوز kASLR أولاً لتسريب عنوان النواة، وسأستخدم EnumDeviceDrivers (على ويندوز 24h2 تحتاج إلى SeDebugPriv للقيام بذلك).
المشكلة تكمن في IOCTL 0x802b200f (ud_response). مرة أخرى، لا يتحقق توزيع هذا IOCTL من العنوان الذي أقدمه من وضع المستخدم، لكنني سأستغله لاحقًا.
يستدعي ud_response الدالة ll_load_diagnostics، وسأصل إلى الكود التالي:
في البداية، المتغير العام eeprom غير مهيأ، لذا سيحتوي على NULL. إليك كود بسيط سيؤدي إلى تشغيل هذا.
سأستغل هذا لاحقًا.
يقوم هذا IOCTL ببساطة بتحويل سلسلة إصدار البرنامج "8.36" إلى الرقم 0x836 (كقيمة DWORD) ويكتبها إلى العنوان المقدم من المتصل (بفضل METHOD_NEITHER). تقنيًا، يمكنني كتابة هذه البايتات الأربعة (36 08 00 00) إلى عنوان نواة عشوائي. سأستغل هذا لاستبدال المتغيرات العامة للبرنامج وتغيير مسار التنفيذ.
سأسمي هذا 0x802b2003 باسم IOCTL_GET_VERSION
كتابة بايت فارغ تعسفي واحد:
بالعودة إلى حالة إلغاء المرجعية الفارغ، أستخدم واجهة برمجة التطبيقات VirtualAlloc لتخصيص عنوان ثابت (0x083600000000). ثم أستخدم IOCTL_GET_VERSION لكتابة البايتات الأربعة الموصوفة أعلاه إلى *(eeprom + 4). عندما يقوم البرنامج لاحقًا بإلغاء مرجعية eeprom، سيقرأ من العنوان الذي خصصته.
بعد إصلاح إلغاء المرجعية الفارغ، يكتب IOCTL سلسلة إلى العنوان الذي أقدمه من وضع المستخدم، بناءً على حجم المخزن المؤقت.
يوضح هذا الكود ببساطة ما وصفته أعلاه، قم بتخصيص مخزن مؤقت واملأه بـ 0xAA، وأصلح إلغاء المرجعية الفارغ، ثم استدعِ البرنامج. لاحظ أنني أخصص 11 بايتًا ولكنني أوفر حجم مخزن مؤقت يبلغ 10 فقط للبرنامج لمعرفة كيف يتصرف.
يكتب تسلسلًا ثابتًا من البايتات إلى المخزن المؤقت الخاص بي ثم يقوم بتصفير البايت الأخير (الحادي عشر)، على الرغم من أنني أوفر حجمًا يبلغ 10 فقط. يستبدل آخر 0xAA في المخزن المؤقت الخاص بي بـ 0x00. يشير هذا إلى أنه إذا قمت بتوفير حجم 0، فإن البرنامج لا يزال يكتب بايتًا واحدًا 0x00 في العنوان المستهدف.
إنقاص تعسفي
الآن لدي البايت الفارغ والتعسفي مع 4 بايتات ثابتة، دعني أصنع بدائية أخرى.
سيقوم هذا IOCTL بتعيين المتغير العام LtMsgEvent إلى المخزن المؤقت الخاص بي في وضع المستخدم ثم يتحقق مما إذا كان WDM فارغًا ثم يعيد تعيينه إلى الصفر مرة أخرى.
ثم في 0x802b2207 سيستدعي واجهة برمجة التطبيقات ObfReferenceObject.
في الحالة الأولية، يكون WDM فارغًا ولكن بمساعدة IOCTL_GET_VERSION يمكنني تعيين WDM إلى 0x36 (حجمه 1 بايت فقط) وLtMsgEvent لا يزال المخزن المؤقت الخاص بي. ثم سأقوم بتصفير WDM واستدعاء 0x802b2207. أخيرًا أصل إلى ObfReferenceObject. سأسمي هذين الأمرين IOCTL باسم IOCTL_SET_LtMsgEvent وIOCTL_DEREF_LtMsgEvent.
تقنية الاستغلال باستخدام ObfReferenceObject تغير PreviousMode الخاص بـ KTHREAD الخاص بنا من UserMode إلى KernelMode يمكنك القراءة عنها هنا). ومع ذلك، قامت ويندوز بإصلاح هذا الاستغلال، لذا لا يمكننا استخدامه.
لكن البدائية في ObfReferenceObject لا تزال موجودة. تطرح واجهة برمجة التطبيقات 0x30 من العنوان الذي نقدمه، وتحول النتيجة إلى عدد صحيح 8 بايت، ثم تطرح 1.
*(signed long long)(LtMsgEvent-0x30) -= 1
لكن المشكلة أنها تتحقق مما إذا كانت القيمة التالية هي 0 أو ما إذا كانت القيمة الحالية < 1 (تُفسر كعدد صحيح 8 بايت مع الإشارة). إذا تحقق أي من الشرطين، فإنها تقفز إلى KeBugCheckEx وتتسبب في تعطل النظام.
كتابة تعسفية
مع وجود الإنقاص التعسفي، أحتاج إلى إيجاد مكان آخر لكتابة البايت 0xFF ثم إنقاصه إلى البايت الذي أريده، وقد وجدت هذا IOCTL 0x802b2243: