أزال عميل Steam ثغرة خطيرة كانت مختبئة هناك لمدة عشر سنوات

الصورة

يتحدث كبير الباحثين توم كورت أوف كونتيكست ، وهي شركة لأمن المعلومات ، عن كيفية تمكنه من اكتشاف خطأ محتمل خطير في كود عميل ستيم.

لاحظ لاعبو الكمبيوتر المهتمون بالأمن أن Valve أصدرت مؤخرًا تحديثًا جديدًا لعميل Steam.

في هذا المنشور ، أريد أن أعرض أعذارًا لممارسة الألعاب في العمل لسرد قصة خطأ ذي صلة كان موجودًا في عميل Steam لمدة عشر سنوات على الأقل ، والتي قد تؤدي حتى يوليو من العام الماضي إلى تنفيذ التعليمات البرمجية عن بُعد (تنفيذ التعليمات البرمجية عن بُعد ، RCE) في 15 مليون عميل نشط.

منذ شهر يوليو ، عندما جمعت Valve (أخيرًا) كودها مع تمكين الحماية من الاستغلال الحديثة ، يمكن أن تؤدي فقط إلى فشل العميل ، وكان RCE ممكنًا فقط مع وجود ثغرة منفصلة لتسرب المعلومات.

أعلنا عن Valve ثغرة أمنية في 20 فبراير 2018 ، وبفضل رصيد الشركة ، تم إصلاحه في فرع بيتا بعد أقل من 12 ساعة. تم نقل الإصلاح إلى الفرع الثابت في 22 مارس 2018.

الصورة

مراجعة قصيرة


كان أساس الثغرة هو تلف الكومة داخل مكتبة عميل Steam ، والتي يمكن تسميتها عن بعد ، في ذلك الجزء من الرمز الذي كان متضمنًا في استرداد مخطط البيانات المجزأ من عدة حزم UDP مستلمة.

يقوم عميل Steam بتبادل البيانات من خلال البروتوكول الخاص به (بروتوكول Steam) ، والذي يتم تنفيذه أعلى UDP. هناك مجالان في هذا البروتوكول مثيران للاهتمام بشكل خاص بسبب الضعف:

  • طول الرزمة
  • إجمالي طول مخطط البيانات المعاد بناؤه

حدث الخطأ بسبب عدم وجود فحص بسيط. لم تتحقق الشفرة من أن طول مخطط البيانات المجزأ الأول أقل من أو يساوي الطول الإجمالي لمخطط البيانات. يبدو هذا بمثابة إشراف شائع نظرًا لأنه يتم إجراء الفحص لجميع الحزم اللاحقة التي تنقل أجزاء من مخطط البيانات.

بدون أخطاء تسريب بيانات إضافية ، من الصعب جدًا التحكم في تلف الكومة على أنظمة التشغيل الحديثة ، لذا يصعب تنفيذ التعليمات البرمجية عن بُعد. ومع ذلك ، في هذه الحالة ، بفضل مخصص ذاكرة Steam و ASLR ، اللذين كانا غائبين في الملف الثنائي steamclient.dll (حتى يوليو الماضي) ، يمكن استخدام هذا الخطأ كأساس لاستغلال موثوق به للغاية.

فيما يلي وصف تقني لثغرة أمنية واستغلالها المرتبط بها حتى
تنفيذ التعليمات البرمجية.

تفاصيل الضعف


المعلومات اللازمة للفهم


بروتوكول


قامت الجهات الخارجية (على سبيل المثال ، https://imfreedom.org/wiki/Steam_Friends ) ، بناءً على تحليل حركة المرور التي تم إنشاؤها بواسطة عميل Steam ، بإجراء هندسة عكسية وإنشاء وثائق تفصيلية لبروتوكول Steam. في البداية ، تم توثيق البروتوكول في عام 2008 ولم يتغير كثيرًا منذ ذلك الحين.

يتم تنفيذ البروتوكول كبروتوكول إرسال مع إنشاء اتصال عبر دفق من مخططات بيانات UDP. الحزم ، وفقًا للوثائق الموجودة على الرابط أعلاه ، لها الهيكل التالي:

الصورة

جوانب مهمة:

  • تبدأ جميع الحزم بـ 4 بايت من " VS01 "
  • يصف packet_len طول المعلومات المفيدة (بالنسبة لمخططات البيانات غير المجزأة ، تكون القيمة مساوية لطول البيانات)
  • يصف النوع نوع الحزمة ، والتي يمكن أن تحتوي على القيم التالية:
    • 0x2 مصادقة المكالمة
    • 0x4 قبول الاتصال
    • 0x5 إعادة تعيين الاتصال
    • 0x6 الحزمة عبارة عن جزء من مخطط بيانات
    • 0x7 Package مخطط بيانات منفصل
  • الحقول المصدر والوجهة هي معرّفات تم تعيينها لتوجيه الحزم بشكل صحيح على اتصالات متعددة داخل عميل Steam
  • في حالة ما إذا كانت الحزمة جزءًا من مخطط بيانات:
    • يشير Split_count إلى عدد الأجزاء التي تم تقسيم مخطط البيانات إليها
    • تشير data_len إلى الطول الإجمالي لمخطط البيانات المسترد
  • تحدث المعالجة الأولية لحزم UDP هذه في دالة CUDPConnection :: UDPRecvPkt داخل steamclient.dll

التشفير


يتم تشفير المعلومات المفيدة لحزمة مخطط البيانات بواسطة AES-256 باستخدام مفتاح يتم التفاوض عليه بين العميل والخادم في كل جلسة. يتم إجراء المفاوضات الرئيسية على النحو التالي:

  • يقوم العميل بإنشاء مفتاح عشوائي AES 32 بايت ، ويقوم RSA بتشفيره باستخدام مفتاح Valve العام قبل إرساله إلى الخادم.
  • يمكن للخادم ، الذي لديه مفتاح خاص ، فك تشفير هذه القيمة وقبولها كمفتاح AES-256 ، والذي سيتم استخدامه في الجلسة
  • بعد الموافقة على المفتاح ، يتم تشفير جميع المعلومات المفيدة في الجلسة الحالية باستخدام هذا المفتاح.

الضعف


توجد ثغرة أمنية داخل طريقة RecvFragment لفئة CUDPConnection . لا توجد رموز في نسخة إصدار مكتبة Steamclient ، ومع ذلك ، عند البحث من خلال الخطوط الثنائية في وظيفة تهمنا ، تم العثور على رابط إلى " CUDPConnection :: RecvFragment ". يتم إدخال هذه الوظيفة عندما يتلقى العميل حزمة UDP تحتوي على مخطط بيانات Steam من النوع 0x6 ("جزء من مخطط بيانات").

1. تبدأ الوظيفة بفحص حالة الاتصال للتأكد من أنها في حالة " متصل ".
2. بعد ذلك ، يتم فحص حقل data_len في مخطط بيانات Steam للتأكد من أنه يحتوي على أقل من 0x20000060 بايت (يبدو أنه تم اختيار هذه القيمة بشكل عشوائي).
3. في حالة اجتياز الفحص ، تتحقق الوظيفة مما إذا كان الاتصال يجمع أجزاء من بعض مخطط البيانات أم أنه أول حزمة من الدفق.

الصورة

4. إذا كانت هذه هي الحزمة الأولى في الدفق ، فسيتم التحقق من حقل Split_count لمعرفة عدد الحزم التي سيمتد هذا الدفق
5. إذا تم تقسيم الدفق إلى عدة حزم ، فسيتم التحقق من الحقل seq_no_of_first_pkt للتأكد من أنه يطابق الرقم التسلسلي للرزمة الحالية. هذا يضمن أن الحزمة هي الأولى في الدفق.
6. يتم فحص حقل data_len مرة أخرى مقابل حد 0x20000060 بايت. بالإضافة إلى ذلك ، تم التحقق من أن Split_count أقل من 0x709b حزم.

الصورة

7. إذا تم استيفاء هذه الشروط ، فسيتم تعيين قيمة منطقية للإشارة إلى أننا نجمع الآن أجزاء. كما يتحقق من عدم وجود مخزن مؤقت مخصص لتخزين الأجزاء.

الصورة

8. إذا لم يكن مؤشر المخزن المؤقت لمجموعة الأجزاء صفرًا ، فسيتم تحرير المخزن المؤقت الحالي للمجموعات وتم تخصيص مخزن مؤقت جديد (انظر المستطيل الأصفر في الشكل أدناه). هذا هو المكان الذي يظهر فيه الخطأ. من المتوقع تخصيص المخزن المؤقت لمجموعة الأجزاء بحجم بايت data_len . إذا كان كل شيء ناجحًا (ولم يتحقق الرمز - خطأ طفيف) ، فسيتم نسخ المعلومات المفيدة من مخطط البيانات إلى هذا المخزن المؤقت باستخدام memmove ، واثقًا من أن عدد وحدات البايت للنسخ موضح في packet_len .

كان أهم إشراف للمطور هو عدم تنفيذ الاختيار " packet_len أقل من أو يساوي data_len ". هذا يعني أنه من الممكن نقل data_len أقل من packet_len ولديها ما يصل إلى 64 كيلوبايت من البيانات (نظرًا لأن حقل packet_len بعرض 2 بايت) تم نسخها إلى مخزن مؤقت صغير جدًا ، مما يجعل من الممكن استغلال تلف الكومة.

الصورة

استغلال الضعف


يفترض هذا القسم وجود حل بديل لـ ASLR. هذا يؤدي إلى حقيقة أنه قبل بدء العملية ، يُعرف عنوان البداية لـ steamclient.dll.

انتحال الحزمة


من أجل تلقي حزم UDP المهاجمة من قبل العميل ، يجب عليه فحص مخطط البيانات الصادرة (العميل -> الخادم) ، التي يتم إرسالها من أجل معرفة معرفات اتصال العميل / الخادم ، وكذلك الرقم التسلسلي. بعد ذلك ، يجب أن يقوم المهاجم بانتحال عناوين IP ومنافذ المصدر / الوجهة مع معرفات العميل / الخادم وزيادة الرقم التسلسلي المكتسب بمقدار واحد.

إدارة الذاكرة


لتخصيص ذاكرة أكبر من 1024 (0x400) بايت ، يتم استخدام مخصص نظام قياسي. لتخصيص ذاكرة أقل من أو تساوي 1024 بايت ، يستخدم Steam مخصصه الخاص الذي يعمل بنفس الطريقة على جميع الأنظمة الأساسية المدعومة. لن تناقش هذه المقالة بالتفصيل هذا الموزع ، باستثناء الجوانب الرئيسية التالية:

  1. يتم طلب كتل كبيرة من الذاكرة من مخصص النظام ، والتي يتم تقسيمها بعد ذلك إلى أجزاء من حجم ثابت للاستخدام ضمن طلبات تخصيص ذاكرة عميل Steam.
  2. يتم إجراء التحديد بالتسلسل ، بين الأجزاء المستخدمة لا توجد بيانات وصفية تفصل بينها.
  3. تقوم كل كتلة كبيرة بتخزين قائمة الذاكرة المجانية الخاصة بها ، والتي يتم تنفيذها كقائمة مرتبطة بشكل فردي.
  4. يشير الجزء العلوي من قائمة الذاكرة المجانية إلى أول جزء حر في الذاكرة ، وتشير أول 4 بايت من هذا الجزء إلى الجزء الحر التالي (إن وجد).

تخصيص الذاكرة


عندما يتم تخصيص الذاكرة ، يتم فصل أول كتلة حرة من أعلى قائمة الذاكرة الخالية ، ويتم نسخ أول 4 بايت من هذه الكتلة المقابلة لـ next_free_block إلى متغير عضو freelist_head داخل فئة المخصص .

تحرير الذاكرة


عندما يتم تحرير كتلة ، يتم نسخ حقل freelist_head إلى أول 4 بايت من الكتلة المحررة ( next_free_block ) ، ويتم نسخ عنوان الكتلة المحررة إلى متغير عضو freelist_head لفئة الموزع.

كيفية الحصول على تسجيل بدائي


يحدث تجاوز سعة المخزن المؤقت في كومة الذاكرة المؤقتة ، واعتمادًا على حجم الحزم التي تسببت في حدوث تلف ، يمكن التحكم في تخصيص الذاكرة إما عن طريق مخصص Windows القياسي (عند تخصيص ذاكرة أكبر من 0x400 بايت) أو عن طريق مخصص Steam الخاص (عند تخصيص ذاكرة أقل من 0x400 بايت). نظرًا لغياب الإجراءات الأمنية في موزع Steam الخاص بي ، قررت أنه من الأسهل استخدامه لاستغلالها.

دعونا نعود إلى القسم الخاص بإدارة الذاكرة: من المعروف أن الجزء العلوي من قائمة الذاكرة المجانية للكتل ذات حجم معين يتم تخزينه كمتغير عضو في فئة الموزع ، ويتم تخزين المؤشر إلى الكتلة المجانية التالية في القائمة كأول 4 بايت من كل كتلة حرة من القائمة.

إذا كان هناك كتلة حرة بجوار الكتلة التي حدث فيها تجاوز السعة ، فإن تلف الكومة يسمح لنا بالكتابة فوق مؤشر next_free_block . إذا كنت تعتقد أنه يمكن إعداد مجموعة لهذا ، فيمكن ضبط مؤشر next_free_block المعاد كتابته على عنوان للكتابة ، وبعد ذلك سيتم كتابة تخصيص الذاكرة اللاحق في هذا المكان.

ما يجب استخدامه: مخططات البيانات أو الأجزاء


يحدث خطأ في تلف الذاكرة في التعليمات البرمجية المسؤولة عن معالجة أجزاء من مخططات البيانات (حزم من النوع 6). بعد حدوث الضرر ، تكون وظيفة RecvFragment () في حالة تتوقع فيها تلقي المزيد من الأجزاء. ومع ذلك ، إذا وصلوا ، يتم إجراء فحص:

fragment_size + num_bytes_already_received < sizeof(collection_buffer)

ولكن من الواضح أن هذه ليست مثل هذه الحالة ، لأن الحزمة الأولى لدينا قد انتهكت بالفعل هذه القاعدة (وجود خطأ ممكن لتخطي هذا الاختيار) وسيحدث خطأ. لتجنب ذلك ، تحتاج إلى تجنب الأسلوب CUDPConnection :: RecvFragment () بعد تلف الذاكرة.

لحسن الحظ ، لا يزال بإمكان CUDPConnection :: RecvDatagram () تلقي ومعالجة الحزم المرسلة من النوع 7 (مخططات البيانات) حتى يصبح RecvFragment () صالحًا ، ويمكن استخدامه لبدء التسجيل البدائي.

قضايا التشفير


من المتوقع أن يتم تشفير الحزم المتلقاة بواسطة RecvDatagram () و RecvFragment () . في حالة RecvDatagram () ، يتم تنفيذ فك التشفير على الفور تقريبًا بعد الاستلام. في حالة RecvFragment () ، تحدث بعد استلام الجزء الأخير في الجلسة.

تنشأ مشكلة استغلال الثغرة الأمنية لأننا لا نعرف مفتاح التشفير الذي تم إنشاؤه في كل جلسة. هذا يعني أن أي كود OP / كود shell الذي نرسله سيتم "فك تشفيره" باستخدام AES256 ، والذي سيحول بياناتنا إلى قمامة. لذلك ، من الضروري العثور على طريقة للتشغيل ، والتي تكون ممكنة تقريبًا فور استلام الحزمة ، قبل أن تتمكن إجراءات فك التشفير من معالجة المعلومات المفيدة الواردة في المخزن المؤقت للحزمة.

كيفية تحقيق تنفيذ التعليمات البرمجية


بالنظر إلى قيود فك التشفير الموصوفة أعلاه ، يجب إجراء العملية قبل فك تشفير البيانات الواردة. يفرض هذا قيودًا إضافية ، لكن المهمة لا تزال ممكنة: يمكنك إعادة كتابة المؤشر بحيث يشير إلى كائن CWorkThreadPool المخزن في مكان يمكن التنبؤ به داخل قسم البيانات من الملف الثنائي. على الرغم من أن التفاصيل والوظائف الداخلية لهذه الفئة غير معروفة ، يمكن افتراضها من خلال اسمها أنها تدعم مجموعة من سلاسل المحادثات التي يمكنك استخدامها عندما تحتاج إلى القيام "بالعمل". بعد دراسة العديد من خطوط التصحيح في ملف ثنائي ، يمكنك أن تفهم أنه من بين هذه الأعمال هناك تشفير وفك تشفير ( CWorkItemNetFilterEncrypt ، CWorkItemNetFilterDecrypt ) ، لذلك عندما يتم وضع هذه المهام في قائمة الانتظار ، يتم استخدام فئة CWorkThreadPool . من خلال استبدال هذا المؤشر وكتابة المكان المطلوب فيه ، يمكننا محاكاة مؤشر vtable و vtable المرتبط به ، مما يسمح لنا بتنفيذ التعليمات البرمجية ، على سبيل المثال ، عندما يتم استدعاء CWorkThreadPool :: AddWorkItem () ، والذي يجب أن يحدث قبل أي عمليات فك التشفير.

يوضح الشكل أدناه الاستغلال الناجح للضعف حتى مرحلة السيطرة على سجل EIP.

الصورة

من الآن فصاعدًا ، يمكنك إنشاء سلسلة ROP تؤدي إلى تنفيذ التعليمات البرمجية التعسفية. يوضح الفيديو أدناه كيف يبدأ المهاجم عن بعد حاسبة Windows في إصدار مصحح بالكامل من Windows 10.


لتلخيص


إذا وصلت إلى هذا الجزء من المقالة ، أشكركم على مثابرتكم! آمل أن تفهم أن هذا خطأ بسيط للغاية كان من السهل جدًا استغلاله بسبب نقص الوسائل الحديثة للحماية من الاستغلال. ربما كان الرمز الضعيفة قديمًا جدًا ، ولكن بخلاف ذلك كان يعمل بشكل جيد ، لذلك لم ير المطورين الحاجة إلى فحصه أو تحديث البرامج النصية للبناء. الدرس هنا هو أنه من المهم للمطورين مراجعة التعليمات البرمجية القديمة بشكل دوري وبناء أنظمة للتأكد من أنها تتوافق مع معايير الأمان الحديثة ، حتى إذا ظلت وظيفة التعليمات البرمجية نفسها دون تغيير. كان من المدهش أن تجد في عام 2018 مثل هذا الخطأ البسيط مع مثل هذه العواقب الوخيمة على منصة برمجيات شائعة جدًا. يجب أن يكون هذا حافزًا للبحث عن نقاط الضعف هذه لجميع الباحثين!

أخيرًا ، يجدر الحديث عن عملية الكشف المسؤول عن المعلومات. أبلغنا عن هذا الخطأ إلى Valve في رسالة إلى فريق الأمان الخاص بها ( security@valvesoftware.com ) في حوالي الساعة 4 مساءً بتوقيت غرينتش وبعد 8 ساعات فقط ، تم إنشاء إصلاح وإطلاقه في عميل بيتا Steam. وبفضل هذا ، أصبحت Valve الآن في المركز الأول في جدولنا (الخيالي) للمسابقة "من سيصلح الثغرة بشكل أسرع" - وهو استثناء ممتع مقارنة بالكشف عن الأخطاء لشركات أخرى ، والتي غالبًا ما تؤدي إلى عملية موافقة طويلة.

صفحة تصف تفاصيل جميع تحديثات العميل

Source: https://habr.com/ru/post/ar413003/


All Articles