
CVE-2020-15368، المعروف أيضًا باسم "كيفية استغلال برنامج تشغيل ضعيف"
استغلال وإثبات المفهوم (PoC) لـ CVE-2020-15368. أعدت Asrock تغليف برنامج تشغيل rweverything لأداة التحكم في إضاءة RGB الخاصة بها ووقّعته. لقد "حمّوه" عن طريق تشفير استدعاءات ioctl الخاصة بهم... لول. عثرنا على هذا CVE بالصدفة الصيف الماضي، وعلى حد علمي لا يزال برنامج التشغيل غير مُصحح. التأثير بالطبع هو تنفيذ تعليمات برمجية عشوائية في النواة، إلخ. لذا استمتعوا بهذا "0day" لول.
إذا كنت تريد أن تجادلني حول ما إذا كانت هذه CVE حقيقية وصادقة، فلا تتردد في التواصل معي على تويتر، يمكننا أن نخوض معركة كبيرة على وسائل التواصل الاجتماعي العامة وسيكون الأمر مثيرًا حقًا لكل المعنيين! سأشتري حتى نطاقًا لهذا الخطأ إذا كنت ترغب في ذلك. الأمر كله يتعلق بالتسويق!!!!
على أي حال، هذا الخطأ سيئ للغاية، لذا سأستخدمه كدرس تعليمي حول كيفية اختراق برنامج تشغيل ضعيف نموذجي. لهذا، هذا المنشور موجه للمبتدئين. ستتعلم كيفية استغلال برنامج تشغيل ضعيف. هناك الكثير من برامج التشغيل السيئة الأخرى مثل هذا في الخارج. العالم بحوزتك. استمتع
إخلاء مسؤولية: هذا المنشور مقدم لأغراض تعليمية فقط. تقع على عاتق القارئ مسؤولية الامتثال لجميع القوانين المحلية والولائية والاتحادية المعمول بها. لا يتحمل مؤلفو هذا المنشور أي مسؤولية ولا يكونون مسؤولين عن أي إساءة استخدام أو ضرر ناتج عن البرنامج الوارد في هذا المنشور.
عالقون في الحجر الصحي، أنا ورفاقي في السكن (Pear0، Codetector) كنا نعبث على اللوحة الأم الجديدة من Asrock الخاصة بـ Pear0. كانت مصابيح LED الحمراء الساطعة مزعجة للغاية ولم يكن من الممكن تكوينها على لينكس. لذلك كانت خطتنا هي تفكيك برنامج تشغيل ويندوز الذي يتحكم فيها وإعادة إنتاج عمليات الإدخال/الإخراج على لينكس.
باختصار، لم يمر وقت طويل حتى أدركنا أن برنامج التشغيل هو حرفيًا مجرد برنامج تشغيل عام يمنح وصولًا عشوائيًا للقراءة/الكتابة إلى أي شيء. يشمل ذلك سجلات التحكم مثل CR3 وCR4 والذاكرة الفعلية، إلخ. برامج التشغيل من هذا القبيل مخصصة للاستخدام كأداة تصحيح أخطاء وموقع البائع يوضح ذلك بوضوح.

اعتقدنا أن هذا مضحك للغاية. إنه أمر مثير للغاية في المرة الأولى التي تجعل فيها جهاز الكمبيوتر يتعطل ثلاثيًا (triple fault) ويعيد التشغيل بقوة من مساحة المستخدم. (ربما أقل إثارة في المرة العشرين.) على أي حال، أبلغنا عن الخطأ ثم نسينا أمره لمدة عام.
كمبتدئ في النواة، كنت أتساءل كيف يمكنني فعليًا تحميل برنامج التشغيل والتفاعل معه. اتضح أن الأمر سهل للغاية.
يمكنك فقط إنشاء خدمة لبرنامج التشغيل في Process Hacker (من الواضح أنه يتطلب صلاحيات المسؤول لتحميل برامج التشغيل). ثم يمكنك النقر بزر الماوس الأيمن وتشغيله. نعم، الأمر بهذه البساطة حقًا.

يمكننا عرض كائن الجهاز الخاص بنا في WinObjEx64.

يمكننا حتى اللعب مع الجهاز في FileTest.


كل هذه الأدوات الثلاثة مذهلة، خاصة PH و FileTest. إنها مثل سكين الجيش السويسري، وينبغي أن تكون في صندوق أدوات كل محلل عكسي على ويندوز. على سبيل المثال، على حد فهمي، وجد Jonas L عددًا لا يحصى من ثغرات تصعيد الامتيازات المحلية (LPE) في ويندوز بمجرد العبث في FileTest. لذا فإن ويندوز لديه حقًا بعض الأدوات الرائعة للعبث. أتمنى لو كان هناك مثل هذا الشيء على لينكس.
Rweverything لديه ioctl يأخذ ioctl كمعامل، والذي يتحكم في العملية التي سيتم تنفيذها (قراءة ذاكرة، كتابة ذاكرة، قراءة MSR، إلخ)، وunion لبعض المعاملات الخاصة بالعملية مثل عنوان المصدر، عنوان الوجهة، إلخ. عندما قارنّا كود برنامجي التشغيل:

🤔🤔🤔🤔🤔🤔🤔🤔🤔
ومع ذلك، يقوم برنامج التشغيل بمحاولة رديئة لتأمين نفسه من خلال الغموض (security by obscurity) عبر اشتراط أن تكون جميع استدعاءات ioctl مشفرة بشكل مناسب بمفتاح AES مضمّن في الكود. الكود (بعد بعض التنظيف) يبدو هكذا:
if ( IoControlCode == 0x22EC00 )
{
char enc_key[32];
memset(enc_key, 0, sizeof(enc_key));
memmove(enc_key, "C110DD4FE9434147B92A5A1E3FDBF29A", 32ui64);
memcpy(enc_key + 13, ioctl_args->key, 16);
size_t cb_decrypted = 0;
my_decrypted_cmd* decryptedCmd = NULL;
DWORD iv_size = ioctl_args->iv_size;
DWORD input_size = *(DWORD*)(ioctl_args + bufferLen - 6);
// really just calls BCrypt API to get an AES implementation
if ( (unsigned int)decrypt_ioctl_params(enc_key, 32, ioctl_args->iv, iv_size, ioctl_args + bufferLen - input_size - 6, input_size, &decryptedCmd, &cb_decrypted) )
{
// Decryption failed
if ( decryptedCmd )
ExFreePoolWithTag(decryptedCmd, 0);
irp->IoStatus.Status = 0xC000000D;
goto Fail_Out;
}
IoControlCode = decryptedCmd->opcode;
Rweverything_Args = &decryptedCmd->args;
}
else // not 0x22EC00
{
if ( IoControlCode != 0x22E858 &&
IoControlCode != 0x22E860 &&
IoControlCode != 0x22E800 &&
IoControlCode != 0x22E804 )// whitelisted control codes
IoControlCode = 0; // block everything else
}
يسمح برنامج التشغيل ببعض العمليات المملة التي تقوم ببعض عمليات PMIO، لكن جميع رموز التحكم "الممتعة" محجوبة خلف روتين فك التشفير هذا. على الرغم من وجود قائمة السماح الصريحة هذه، إلا أنها لا تزال تتضمن جميع وظائف Rweverything الخطيرة. بدلاً من إخفاء هذه الميزات الخطيرة، كان ينبغي عليهم على الأرجح إزالتها تمامًا.
أيضًا، المثير للاهتمام، أنه يسمح للمستخدم بتحديد جزء من المفتاح (؟؟؟)، ولست أدري لأي سبب. الكود ببساطة مكتوب بشكل سيئ للغاية.
على أي حال، من السهل نسبيًا كتابة كود العميل لاستخدام هذه الواجهة البرمجية المشفرة الغريبة وتمرير أي استدعاءات ioctl نريدها. لن أملّكم بالتفاصيل.
نفتح مقبضًا (handle) لبرنامج التشغيل ونستخدم DeviceIoControl لاستدعاء ioctl، أمر قياسي جدًا.
HANDLE hDevice = CreateFileA("\\\\.\\GlobalRoot\\Device\\AsrDrv104", GENERIC_READ | GENERIC_WRITE | SYNCHRONIZE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
// ... set up the encrypted ioctl data
BOOL result = DeviceIoControl(hDevice, 0x22EC00, ioctl_data, in_buf_size, out_buf, sizeof(out_buf), &bytes_returned, NULL);
الآن بعد أن أصبحنا قادرين على التواصل مع الجزء المخفي من Rweverything في برنامج التشغيل، كان أول شيء أردت فعله هو إحداث انهيار (crash) لأعرف أن عميل برنامج التشغيل الخاص بي يعمل.
الطريقة الأكثر مباشرة لتحقيق ذلك هي الكتابة فوق CR3 ببيانات عشوائية. أعلم أن بعضكم من المبتدئين وهذا لا بأس به، لذا سأشرح بالتفصيل. أنا غبي أيضًا، لذا ربما سيساعدك هذا على التعلم. إذا كنت تعرف ما تفعله، فيمكنك تخطي هذا.
في معمارية x86، عندما يكون الترحيل (paging) مفعلًا (في معظم الأوقات في أي نظام تشغيل حديث)، يشير CR3 إلى العنوان الأساسي الفعلي لدليل جدول الصفحات العلوي. إذا كنت لا تعرف ما يعنيه ذلك، فاقرأ مقال ويكيبيديا عن الذاكرة الافتراضية.
عندما نستبدل CR3 ببيانات عشوائية، مثل 0x0000000000000000، يتم مسح الـ TLB، وعند محاولة تنفيذ التعليمة التالية، سيحاول المعالج (وتحديدًا وحدة إدارة الذاكرة MMU) ترجمة مؤشر التعليمات إلى عنوان فعلي. يمكن اعتبار ترجمة العنوان سلسلة من عمليات التنقل في جدول الصفحات تبدأ من CR3. الآن يشير CR3 إلى الذاكرة الفعلية عند العنوان 0، وهي موجودة فعليًا ويمكن الوصول إليها؛ ومع ذلك، فمن غير المرجح للغاية أن تكون جدول صفحات صالحًا. (إدخالات جدول الصفحات، أو PTEs باختصار، يجب أن تتبع بنية محددة.)