https://medium.com/numen-cyber-labs/the-story-of-a-high-vulnerability-in-move-reference-safety-verify-module-2340f3d8c642
0x0 مقدمة
منذ بعض الوقت ، وجدنا حرجةوهن في Aptos Movevm. بعد نوبة من البحث العميق ، وجدنا ثغرة خطيرة أخرى وهي أيضًا تجاوز عدد صحيح ولكن هذه المرة ، إنها مثيرة للاهتمام للغاية.
نعلم أن لغة النقل تتحقق من وحدة الكود قبل تنفيذ الرمز الثانوي. في وحدة الشفرة ، تحقق من أنها مقسمة إلى 4 خطوات. يحدث هذا الخطأ في reference_safety. سنقوم بمزيد من التفاصيل حول هذا الموضوع أدناه.
تحدد هذه الوحدة وظائف النقل للتحقق من السلامة المرجعية لهيئة الإجراء. تشمل عمليات التحقق (على سبيل المثال لا الحصر) التحقق من عدم وجود مراجع متدلية ، والوصول إلى المراجع القابلة للتغيير آمن ، والوصول إلى مراجع التخزين العالمية آمن.
هذه هي نقطة دخول التحقق ، وسوف تستدعي دالة التحليل.
في دالة التحليل ، سيتم التحقق من الوظيفة مع كل كتلة أساسية ، فما هي الكتلة الأساسية؟
في البناء المترجم ، الكتلة الأساسية عبارة عن تسلسل كود بخط مستقيم بدون فروع باستثناء الإدخال ولا توجد فروع خارجة إلا عند الخروج.
في لغة الحركة ، كيف نحدد الكتلة الأساسية؟
في لغة النقل ، يتم تحديد الكتلة الأساسية عن طريق اجتياز الرمز الثانوي ، وإيجاد جميع تعليمات الفرع ، وإرشادات التكرار. يمكنك رؤية الكود الأساسي أدناه:
فيما يلي مثال على كتلة التعليمات البرمجية الخاصة بلغة النقل. يمكننا أن نرى أن هناك 3 كتل أساسية. يتم تحديد الفروع من خلال التعليمات: BrTrue ، Branch ، Ret.
0x1 إشارة السلامة في التحرك
يدعم النقل نوعين من المراجع:ثابت - معرّف بـ & amp؛ (على سبيل المثال & amp؛ T) ومتقلب - & amp؛ mut (على سبيل المثال & amp؛ mut T). يمكنك استخدام مراجع غير قابلة للتغيير (& amp؛) لقراءة البيانات من الهياكل واستخدام (& amp؛ mut) لتعديلها. باستخدام النوع المناسب من المراجع ، فإنك تساعد في الحفاظ على الأمان ويجب أن تعرف ما إذا كانت هذه الطريقة تغير القيمة أو تقرأ فقط.
يوجد أدناه مثال من البرنامج التعليمي الرسمي لـ Move:
في المثال أعلاه ، يمكننا أن نرى أن mut_ref_t هو المرجع القابل للتغيير للقيمة t.
لذلك ، تحاول وحدة السلامة المرجعية لـ Move تأكيد ما إذا كان المرجع صالحًا أم لا ، وذلك من خلال أخذ الوظيفة كوحدة ، ومسح الكتل الأساسية في الوظيفة ، والحكم على ما إذا كانت جميع العمليات المرجعية قانونية من خلال التحقق من تعليمات الرمز الثانوي.
يوضح الشكل أدناه الروتين الذي يتحقق من خلاله من سلامة المرجع.
الحالة هنا هي AbstractState التي تحتوي على رسم بياني للإستعارة والسكان المحليين حيث يتم استخدام كل منهما لتأمين المراجع.
استعارة الرسم البياني هو رسم بياني يمثل العلاقة بين مراجع السكان المحليين.
يمكننا أن نرى من الشكل أعلاه ، هناك أدولة ما قبل الذي يتضمن السكان المحليين واستعارة الرسم البياني (L ، BG) ثم تنفيذ الكتلة الأساسية سيؤدي إلى إنشاء ملفدولة ما بعد مع (L '، BG') ، ثم يتم دمج الحالة قبل وبعد لتحديث حالة الكتل ونشر الشرط اللاحق لهذه الكتلة إلى الكتل اللاحقة. هذا مثلبحر العقد التحسين في المروحة المروحية V8.
الكود أدناه هو الحلقة الرئيسية المقابلة للشكل أعلاه. أولاً ، قم بتنفيذ كود الكتلة (سيعيد خطأ تحليل إذا لم ينجح تنفيذ التعليمات) ثم حاول دمجدولة ما قبل ودولة ما بعد من خلال تحديد ما إذا كانJoin_result تم تغييره أم لا. إذا تم تغييرها واحتوت الكتلة الحالية على نقطة حافة (مما يعني وجود حلقة) ، فسوف تقفز مرة أخرى إلى بداية الحلقة ، في الجولة التالية ستستمر في تنفيذ هذه الكتلة حتىدولة ما بعد مساوي لدولة ما قبل أو إحباط بسبب خطأ ما.
كيف نحكم ما إذا كانت نتيجة الانضمام قد تغيرت أو لم تتغير؟
من خلال الكود أعلاه ، يمكننا معرفة ما إذا كانت نتيجة الانضمام قد تغيرت أم لا من خلال الحكم على ما إذا كانت العلاقة بين السكان المحليين وعلاقة الاقتراض قد تغيرت أم لا. تُستخدم وظيفة الانضمام هنا لتحديث حالة الرسم البياني للسكان المحليين واستعارةها.
باستخدام رمز Join_ أدناه ، فإن السطر 6 هو تهيئة خريطة محلية جديدة ، ويتم استخدام السطر 9 لتكرار كل الفهرس في السكان المحليين إذا كانت القيمة كلها بلا ، قبل وبعد تنفيذ الكتلة حتى لا تدخل في الجديد خرائط السكان المحليين. إذا كانت الحالة السابقة ذات قيمة ، فإن الحالة اللاحقة لا شيء ، فنحن بحاجة إلى تحرير معرّف brow_graph ، مما يعني إلغاء علاقة استعارة القيمة. العكس هو نفسه أيضًا. على وجه الخصوص ، عند وجود كلتا القيمتين وتماثلهما ، قم بإدراجهما في الخريطة الجديدة مثل الخطوط 30-33 ثم ادمج رسم بياني في السطر 38.
من الأعلى يمكننا أن نرى self.iter_locals () هي أرقام السكان المحليين. ولاحظ أن هذا المحلي لا يشمل فقط السكان المحليين الحقيقيين للوظيفة ولكن أيضًا المعلمات.
0x2 الضعف
هنا عبرنا جميع الرموز المتعلقة بالثغرة الأمنية ، هل وجدتها؟
إذا لم تتمكن من العثور على الثغرة الأمنية ، فلا يهم. سأخوض عملية إطلاق الثغرات الأمنية بالتفصيل أدناه.
أولاً في كود التفجير إذا كان طول المعلمات يضيف طولًا محليًا أكبر من 256. يبدو أنه لا توجد مشكلة ، أليس كذلك؟
لكن هذه الوظيفة ستعيد التكرار بنوع العنصر u8.
لذا في الدالة Join_ () هي function_view.parameters (). len () و function_view.locals (). القيمة المجمعة len () أكبر من 256.
في الكود ,محلي في self.iter_locals () ، المتغير المحلي من النوع u8. بعد 256 تكرارًا ، سيؤدي ذلك إلى تجاوز السعة. بعد الفائض ، تكون قيمة المحلي 8.
في الواقع ، لدى Move الإجراء الروتيني للتحقق من الرقم المحلي ، ولكن للأسف يتحقق فقط من السكان المحليين في وحدة التحقق من الحدود ، وليس بما في ذلك طول المعلمات.
يبدو أن المطورين يعرفون هنا ضرورة التحقق من المعلمات + قيمة السكان المحليين. ومع ذلك ، يتحقق الرمز من عدد السكان المحليين في وحدة التحقق من الحدود , ولا يتضمن طول المعلمة.
0x3 انقل تجاوز إلى DoS
نعلم أن هناك حلقة رئيسية لمسح كتلة التعليمات البرمجية وبعد استدعاء وظيفة execute_block والانضمام إلى الحالة. إذا كان رمز النقل موجودًا ، فستنتقل الحلقة إلى بداية الكتلة للتنفيذ مرة أخرى.
لذلك ، إذا قمنا بإنشاء كتلة رمز حلقة واستغلنا الفائض لتغيير حالة الكتلة ، فإنه يجعل خريطة السكان المحليين الجديدة في كائن AbstractState بشكل مختلف عن السابق ، ثم ننفذ الكتلة مرة أخرى باستخدام الوظيفة execute_block ، فنحن نعرف هذه الوظيفة يحلل رمز بايت الرمز ويمنح الوصول إلى السكان المحليين ، لذلك إذا لم تكن قيمة الإزاحة المرجعية موجودة في خريطة AbstractState المحلية الجديدة ، فسيؤدي ذلك إلى DoS.
بعد تدقيق الكود ، وجدت أنه باستخدام كود التشغيل MoveLoc / CopyLoc / FreeRef ، يمكننا تحقيق هذا الهدف.
دعونا نرى هنا وظيفة copy_loc وهي الكود الذي تم استدعاؤه بواسطة وظيفة execute_block في مسار الملف:
تحرك / لغة / مدقق في الشفرة عن طريق الانتقال / src / reference_safety / abstract_state.rs
في السطر 287 ، يحاول الكود الحصول على القيمة المحلية بواسطة LocalIndex كمعامل وإذا لم يكن LocalIndex موجودًا ، فسيؤدي ذلك إلى الذعر ، عندما تنفذ العقدة هذا الرمز الشرير ، ستؤدي إلى تعطل العقدة بأكملها.
0x4PoC
ها هو PoC ، الذي يمكنك إعادة إنتاجه في git الالتزام: add615b64390ea36e377e2a575f8cb91c9466844
هذا هو سجل الأعطال:
خيط "regression_tests :: reference_analysis :: PoC" أصيب بالذعر عند "يسمى & # x60؛ الخيار :: unsrap () & # x60؛ على & # x60 ؛ بلا & # x60 ؛ value ، language / move-bytecode-verifier / src / reference_safety / abstract_state.rs: 287: 39
ملاحظة: تشغيل باستخدام & # x60 ؛ RUST_BACKTRACE = 1 & # x60 ؛ متغير البيئة لعرض backtrace
خطوات تحريك DoS:
يمكننا أن نرى أن كتلة الكود عبارة عن فرع بدون شرط ، وفي كل مرة يتم فيها تنفيذ فرع التعليمات الأخير (0) ، سوف يقفز مرة أخرى إلى التعليمات الأولى لذلك سوف يستدعي execute_block وينضم لوظيفة عدة مرات.
1. في المرة الأولى هنا قمنا بتعيين المعلمات على SignatureIndex (0) ، سيقود السكان المحليون إلى SignatureIndex (0) num_locals 132 * 2 = 264. لذلك بعد استدعاء
جعل طول السكان المحليين الجدد 264-256 = 8
2. في المرة الثانية عند تنفيذ وظيفة execute_block وتنفيذ التعليمة الأولى copy_local (57) ، فإن 57 هي إزاحة السكان المحليين الذين يحتاجون إلى الدفع إلى المكدس ولكن هذه المرة السكان المحليون فقط مع الطول 8 ، الإزاحة 57 غير موجودة ، لذلك سيؤدي هذا إلى أن وظيفة get (57) .unwrap () لا تعيد شيئًا وتسبب الذعر.
0x5 ملخص
هذه هي القصة الكاملة حول هذا الضعف. منه نتعلم أن :
أولاً ، تُظهر هذه الثغرة الأمنية أنه لا يوجد رمز آمن تمامًا. تقوم لغة Move بإجراء تحقق ثابت جيد قبل تنفيذ الكود ، ولكن تمامًا مثل هذه الثغرة الأمنية ، يمكن تجاوز فحص الحدود السابق تمامًا من خلال الثغرة الأمنية الفائضة.
ثانيًا ، تدقيق الكود مهم جدًا ، حيث يمكن للمبرمجين أن يكونوا مهملين في بعض الأحيان. بصفتنا قادة أمان لغة Move ، سنواصل البحث بعمق في القضايا الأمنية لـ Move.
النقطة الثالثة هي أنه بالنسبة لـ Move language ، نوصي بأن يضيف مصممو اللغة المزيد من رموز التحقق لمنع حدوث مواقف غير متوقعة.
حاليًا ، تقوم لغة Move بشكل أساسي بإجراء سلسلة من الفحوصات الأمنية في مرحلة التحقق ، لكنني أعتقد أن هذا لن يكون كافياً. بمجرد تجاوز التحقق ، لا يوجد الكثير من التعزيزات الأمنية في مرحلة وقت التشغيل ، مما سيؤدي إلى تعميق المزيد من المخاطر ، مما يتسبب في مشاكل أكثر خطورة. أخيرًا ، اكتشفنا ثغرة أخرى في لغة Move ، وسنكشفها لك قريبًا.