
إثبات المفهوم 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:
سنركز على فرع flip. pbVar5 هو العنوان الذي أقدمه من وضع المستخدم ويمكن أن يكون أي عنوان مستهدف أختاره. أكتب البايت 0x0C إلى DAT_TARGET_EX (بمساعدة IOCTL_GET_VERSION وبدائية الإنقاص التعسفي)، وأقوم أيضًا بتصفير بايت واحد في العنوان المستهدف. الاستدعاء الأول لهذا IOCTL يعين 0xC0 في العنوان المستهدف، والذي يتم إنقاصه بعد ذلك إلى 0xBF. الاستدعاء الثاني يعين 0xFF في العنوان المستهدف (0xBF | 0xC0 = 0xFF). بمجرد أن يحتوي الهدف على 0xFF، أقوم فقط بإنقاصه إلى القيمة المطلوبة.
سأكتب بايتًا واحدًا في كل مرة وأكون حذرًا من KeBugCheckEx في ObfReferenceObject.
قراءة تعسفية
بالنسبة لبدائية القراءة، أستخدم التقنية الموصوفة هنا (@carrot_c4k3). أقوم ببساطة باستبدال كائن UNICODE_STRING في النواة (ExpManufacturingInformation) ثم استدعاء NtQuerySystemInformation. نظرًا لأن ObfReferenceObject يستدعي KeBugCheckEx، سأقوم بتصفير 8 بايتات مجاورة لـ ExpManufacturingInformation.
هذا كل شيء، الآن لدينا قراءة/كتابة تعسفية، يمكننا استخدام هذه البدائيات للقيام بالعديد من الأشياء. البرنامج غير محمل افتراضيًا، لذا سأستغله في سيناريو BYOVD وأعين PPL لعملية ما.
الاستغلال الذي وصفته أعلاه يعمل في جميع إصدارات ويندوز ولكنه غير مستقر بسبب KeBugCheckEx. ولكن في ويندوز 11 22h2+ هناك تقنية تسمى ioring. تقوم هذه التقنية ببساطة باستبدال ioring->Buffer بعنوان يمكن التحكم فيه. على وجه التحديد، يمكننا استبدال ioring->Buffer وحجمه بـ 0x083600000000 و0x836 على التوالي (باستخدام IOCTL_GET_VERSION). باستخدام هذه التقنية، أقوم فقط بعمليتي كتابة ثم أستخدم بدائية القراءة/الكتابة بشكل مستقر جدًا. لاحظ أن هذا النهج يتطلب تسريب عنوان النواة.
لا يقوم ويندوز بتحميل البرنامج في الحالة الافتراضية. لذا تحتاج إلى تحميله يدويًا. الملف ltmdm64.sys موجود في C:\Windows\System32\DriverStore\...\ltmdm64.sys، قم بتشغيل هذا الأمر كمسؤول وقم بتشغيل الاستغلال:
sc create ltmdm64_srv binPath="C:\Windows\System32\DriverStore...\ltmdm64.sys" type=kernel && sc start ltmdm64_srv
سيستخدم الاستغلال تقنية ioring لإيقاف تشغيل PPL الخاص بـ lsass.exe ويستخدم تقنيتي القائمة على البيانات فقط لتعيين PPL إلى notepad.exe (على ويندوز 11 24h2 تحتاج إلى تمكين SeDebugPriv)
https://github.com/user-attachments/assets/05a35b38-d26c-484f-9fb7-137f8fe8c079
أبلغت عن هذا الخطأ إلى ZDI. ولكن يبدو أنه مكرر مع تقديم Fabian Mosch وJordan Jay إلى MSRC، لذا فإن هذا PoC يظهر الخطأ فقط وأقدر عملهم. تقريبًا أول CVE لي 😍