https://medium.com/@numencyberlabs/analysis-of-the-first-critical-0-day-vulnerability-of-aptos-move-vm-8c1fd6c2b98e
1. مقدمة
تزداد شعبية لغة برمجة Move مؤخرًا بسبب المزايا القوية التي تتمتع بها على لغة Ethereum's Solidity. يستخدم Move في العديد من المشاريع المعروفة ، مثل Aptos و Sui. حديثاً،أمان ويب 3 اكتشف منتج الكشف عن الثغرات الأمنية ثغرة أمنية ذات مستوى حرج في الجهاز الظاهري (VM) لسلسلة Aptos العامة. ما اكتشفناه هو أن ثغرة في اللغة يمكن أن تتسبب في تعطل عقد Aptos وتسبب رفض الخدمة. في هذه المقالة ، نأمل أن يكون لديك فهم أفضل للغة Move وأمنها من خلال شرح هذه الثغرة الأمنية. كشركة رائدة في أبحاث أمان لغة Move ، سنستمر في تقديم مساهمة مستمرة لأمنها البيئي.
2. مفاهيم مهمة للغة الحركة
الوحدات والنصوص
يحتوي Move على نوعين مختلفين من البرامج: الوحدات النمطية والبرامج النصية. الوحدات النمطية هي مكتبات تحدد الأنواع الهيكلية والوظائف التي تعمل على تلك الأنواع. تحدد أنواع الهياكل نمط التخزين العام لـ Move ، وتحدد وظائف الوحدة النمطية قواعد تحديث التخزين. يتم تخزين الوحدات نفسها أيضًا في التخزين العالمي. النصوص هي نقاط دخول إلى الملفات التنفيذية ، على غرار الوظيفة الرئيسية في اللغات التقليدية. تستدعي البرامج النصية عادةً وظائف الوحدات النمطية المنشورة لتحديث التخزين العام. البرامج النصية هي أجزاء مؤقتة من التعليمات البرمجية التي لم يتم نشرها في التخزين العالمي. قد يحتوي ملف مصدر النقل (أو وحدة الترجمة) على وحدات نمطية ونصوص متعددة. ومع ذلك ، فإن نشر الوحدات النمطية أو تنفيذ البرامج النصية يستخدم عمليات منفصلة للآلة الظاهرية (VM).
بالنسبة لمن هم على دراية بأنظمة التشغيل ، تشبه وحدة Move وحدة المكتبة الديناميكية التي يتم تحميلها عند تشغيل الملف التنفيذي للنظام ، ويشبه البرنامج النصي البرنامج الرئيسي. يمكن للمستخدمين كتابة البرامج النصية الخاصة بهم للوصول إلى التخزين العام ، بما في ذلك الكود الذي يستدعي الوحدة.
التخزين العالمي
الغرض من برنامج Move هو القراءة والكتابة إلى التخزين العالمي على شكل شجرة. لا يمكن للبرنامج الوصول إلى نظام الملفات أو الشبكة أو أي بيانات خارج هذه الشجرة.
في رمز زائف ، يبدو التخزين العالمي كما يلي:
من الناحية الهيكلية ، فإن التخزين العالمي عبارة عن غابة تتكون من أشجار متجذرة في عنوان حساب. يمكن لكل عنوان تخزين بيانات الموارد ورمز الوحدة النمطية. كما يوضح الرمز الزائف أعلاه ، يمكن لكل عنوان تخزين قيمة مورد واحدة على الأكثر من نوع معين ووحدة واحدة على الأكثر من اسم معين.
MOVE مبدأ الآلة الافتراضية
الجهاز الظاهري movevm و evm هو نفسه ، حيث يحتاج إلى ترجمة كود المصدر إلى كود بايت ، ثم تنفيذه في الجهاز الظاهري. يوضح الرسم البياني التالي العملية.
1. يتم تحميل الرمز الثانوي من خلال الوظيفة execute_script
2. تنفيذ وظيفة load_script ، تُستخدم هذه الوظيفة بشكل أساسي لإلغاء تسلسل الرمز الثانوي ، والتحقق مما إذا كان الرمز الثانوي قانونيًا ، وإذا فشل التحقق ، فسيتم إرجاعه على أنه فشل
3. بعد التحقق الناجح ، يتم بعد ذلك تنفيذ كود الرمز الثنائي الحقيقي
4. تنفيذ الرمز الثانوي ، والوصول إلى أو تعديل حالة التخزين العالمي ، بما في ذلك الموارد والوحدات النمطية
ملاحظة: هناك العديد من الميزات الأخرى المتعلقة بـ Move ، لكننا لن نقدمها جميعًا هنا ، وسنواصل تحليل ميزات لغة النقل من منظور الأمان.
3. وصف الضعف
تتضمن هذه الثغرة الأمنية بشكل أساسي وحدة التحقق. قبل الحديث عن الثغرة الأمنية المحددة ، سيتم تقديم وظيفة وحدة التحقق و StackUsageVerifier :: check.
وحدة التحقق
نحن نعلم أنه قبل التنفيذ الحقيقي لرمز الرمز الثانوي ، سيكون هناك تحقق من الرمز الثانوي ، ويمكن تقسيم التحقق إلى عدد من العمليات الفرعية على التوالي.
هم انهم:
مدقق الحدود ، يستخدم بشكل أساسي للتحقق من أمان حدود الوحدة والنص. يتضمن ذلك التحقق من حدود التوقيع والثوابت وما إلى ذلك.
المدقق الازدواجية ، وهي وحدة نمطية تنفذ مدقق للتحقق مما إذا كان كل متجه في CompiledModule يحتوي على قيم مختلفة
مدقق التوقيع ، والتي تتحقق من صحة بنية الحقل عند استخدام التوقيع لمعلمات الوظيفة والمتغيرات المحلية وأعضاء الهيكل
الاتساق ، والذي يتحقق من اتساق التعليمات
الثوابت تستخدم للتحقق من أن الثوابت من النوع الأصلي وأن بيانات الثوابت متسلسلة بشكل صحيح إلى نوعها
CodeUnitVerifier ، للتحقق من صحة كود جسم الوظيفة ، عبر stack_usage_verifier.rs و abstract_interpreter.rs على التوالي
script_signature ، للتحقق من أن البرنامج النصي أو وظيفة الإدخال هي توقيع صالح
تحدث الثغرة الأمنية ضمن عملية التحقق
CodeUnitVerifier :: Verify_script (التكوين ، البرنامج النصي)؟ ؛
وظيفة. يمكنك أن ترى أن هناك العديد من عمليات التحقق الفرعية هنا.
هذه عبارة عن مجموع اختباري آمن للمكدس ، ومجموع اختباري آمن من النوع ، ومجموع اختباري محلي آمن متغير ، ومجموع اختباري آمن مرجعي. تنشأ الثغرة الأمنية في عملية التحقق من أمان المكدس.
التحقق من أمان المكدس (StackUsageVerifier :: التحقق)
تُستخدم هذه الوحدة للتحقق من استخدام الكتل الأساسية في تسلسل تعليمات الرمز الثانوي لوظيفة ما بطريقة متوازنة. يجب أن تتأكد كل كتلة أساسية ، باستثناء تلك التي تنتهي بكود التشغيل Ret (الرجوع إلى المتصل) ، من أنها تترك الكتلة بنفس ارتفاع المكدس كما في البداية. بالإضافة إلى ذلك ، بالنسبة لأي كتلة أساسية ، يجب ألا يكون ارتفاع المكدس أقل من ارتفاع المكدس في بداية الكتلة.
قم بالتكرار خلال جميع الكتل للتحقق من استيفاء الشروط المذكورة أعلاه:
تتكرر الحلقة للتحقق من شرعية جميع الكتل الأساسية.
تفاصيل الضعف
كما تم تقديمه سابقًا ، نظرًا لأن movevm عبارة عن آلة افتراضية مكدس ، عند التحقق من شرعية التعليمات ، فمن الواضح أنه أولاً ، نحتاج إلى التأكد من صحة التعليمات البرمجية الثانوية ، وثانيًا ، نحتاج إلى التأكد من أن ذاكرة المكدس هي قانوني بعد استدعاء الكتلة ، أي تمت موازنة المكدس بعد عملية المكدس.التحقق من الحظر
تستخدم الوظيفة لإنجاز الغرض الثاني.
كما نرى منالتحقق من الحظر
رمز ، سيتم تكرار جميع التعليمات الموجودة في كتلة كود الكتلة ثم التحقق مما إذا كان تأثير كتلة التعليمات على المكدس قانونيًا عن طريق الإضافة أو الطرحعدد_البوبس
ودفعات
. أولا ، من خلالمكدس_حجم_زيادة العلامة & lt ؛ عدد_البوبس
لتحديد ما إذا كانت مساحة المكدس قانونية. لوعدد_البوبس
أكبر منتكديس_حجم_زيادة
، هذا يعني أن عدد فرق بايت كود البايت أكبر من حجم المكدس نفسه ، ويتم إرجاع الخطأ وفشل المجموع الاختباري لرمز البايت. ثم عبرstack_size_increment - = num_pops ؛ stack_size_increment + = num_pushes ؛
، يعمل هذان التعليمان على تعديل التأثير على ارتفاع المكدس بعد تنفيذ كل تعليمات. وأخيرًا ، عندما تنتهي الحلقة ،تكديس_حجم_زيادة
يجب أن تكون مساوية لـ 0 ، أي بعد الاحتفاظ بالعمليات في هذه الكتلة ، يجب أن تكون المكدس متوازنة.
يبدو أنه لا يوجد شيء خاطئ هنا ، ولكن لأنه في تنفيذ 16 سطرًا من التعليمات البرمجية ، فإنه لا يحدد ما إذا كان هناك تجاوز عدد صحيح ، مما يؤدي إلى ثغرة أمنية في تجاوز عدد صحيح يمكن التحكم فيها بشكل غير مباشر عن طريق إنشاء عدد كبير من التدفقات ، stack_size_increment . فكيف نبني مثل هذا العدد الهائل من الدفعات؟
يبدو أنه لا توجد مشكلة ، ولكن نظرًا لأنه يتم تنفيذ السطر السادس عشر من التعليمات البرمجية هنا ، فلا يتم الحكم على ما إذا كان هناك تجاوز عدد صحيح أم لا. نتيجة لذلك ، فإنتكديس_حجم_زيادة
يمكن التحكم فيها بشكل غير مباشر عن طريق بناء حجم كبيردفعات
، مما أدى إلى عدم حصانة تجاوز عدد صحيح.
هنا نحتاج أولاً إلى تقديم تنسيق ملف move bytecode.
نقل تنسيق ملف Bytecode
مثل ملفات Windows PE ، أو ملفات linux ELF ، قم بنقل ملفات bytecode التي تنتهي بـ .mv ، والملفات نفسها لها تنسيق معين.
الأول هو السحر ، القيمة A11CEB0B ، تليها معلومات الإصدار ، وعدد الجداول ، وبعد ذلك رؤوس الجداول ، يمكن أن يكون هناك العديد من الجداول. نوع الجدول هو نوع الجدول ، بإجمالي أنواع 0x10 (كما هو موضح على الجانب الأيمن من الشكل) ، لمزيد من التفاصيل قد ترغب في عرض وثائق لغة النقل ، التالي هو إزاحة الجدول ، وطول الطاولة. بعد ذلك تكون محتويات الجدول ، وأخيرًا البيانات المحددة ، هناك نوعان ، للوحدة النمطية ، وهي بيانات خاصة بالوحدة النمطية ، لنوع البرنامج النصي ، وهي بيانات خاصة بالبرنامج النصي.
تنسيق ملف ضار تم إنشاؤه
نحن هنا نتفاعل مع Aptos في البرنامج النصي ، لذلك نقوم بإنشاء تنسيق الملف الموضح أدناه للتسبب في تجاوز stack_size_increment:
أولاً ، دعنا نشرح تنسيق ملف الرمز الثانوي هذا :
+ 0x00–0x03: كلمة سحرية 0xA11CEB0B
+ 0x04–0x7: إصدار تنسيق الملف , نسخته هي 4
+ 0x8–0x8: عدد الجداول ، القيمة هي 1
+ 0x9–0x9: نوع جدول ونوعه توقيعات
+ 0xa-0xa: هو جدول تعويض ، القيمة هي 0
+ 0xb-0xb: طول الجدول , القيمة هي 0x10
+ 0xc-0x18: هي بيانات رمز التوقيعات
بدءًا من 0x22 ، يعد جزءًا من رمز الوظيفة الرئيسية للبرنامج النصي.
من خلال أداة فك التجميع ، يمكننا أن نرى أن كود التفكيك للتعليمات كما يلي :
من بينها ، الرموز المقابلة للتعليمات الثلاثة 0 و 1 و 2 هي البيانات الموجودة في المربع الأحمر والمربع الأخضر والمربع الأصفر على التوالي.
LdU64 ليس له علاقة بالثغرة الأمنية نفسها. لن ندخل في الكثير من التفاصيل هنا ، ولكن يمكنك التحقق من الكود إذا كنت مهتمًا. نركز هنا على شرح تعليمات VecUnpack. تتمثل وظيفة VecUnpack في دفع جميع البيانات إلى المكدس عند مواجهة كائن المتجه في الكود.
في هذا الملف المُنشأ ، قمنا بإنشاء VecUnpack مرتين عدد متجهها هو 3315214543476364830,18394158839224997406 على التوالي.
عندما تكون الوظيفةتعليمات_تأثير
يتم تنفيذ السطر الثاني من الكود أدناه بالفعل :
بعد تنفيذتعليمات_تأثير
دالة ، تقوم بإرجاع (1،3315214543476364830) للمرة الأولى. في هذا الوقت ، stack_size_increment هو 0 ، و num_pops هو 1 ، و num_pushes هو 3315214543476364830. العائد الثاني هو (1،18394158839224997406). عند التنفيذ مرة أخرىstack_size_increment + = num_pushes ؛
stack_size_increment هو بالفعل 0x2e020210021e161d (3315214543476364829).
num_pushes هو 0xff452e02021e161e (18394158839224997406) ، عند إضافة الاثنين ، يكون أكبر من القيمة القصوى لـ u64 ، مما يؤدي إلى اقتطاع البيانات ، وتصبح قيمة stack_size_increment 0x12d473012043c2c3b ، مما يتسبب في تجاوز عدد صحيح ، مما يؤدي إلى تعطل Apt مما يؤدي بدوره إلى توقف العقدة عن العمل. نظرًا لخصائص الأمان للغة الصدأ ، فلن يتسبب ذلك في المزيد من تأثيرات أمان الكود مثل C / C ++.
4. تأثير الضعف
نظرًا لحدوث هذه الثغرة الأمنية في وحدة تنفيذ Move ، بالنسبة للعقد الموجودة في السلسلة ، إذا تم تنفيذ كود الرمز الثانوي ، فسوف يتسبب ذلك في هجوم DoS. في الحالات الشديدة ، يمكن إيقاف شبكة Aptos تمامًا ، مما يتسبب في أضرار لا تُحصى ، ويكون له تأثير خطير على استقرار العقدة.
5. الإصلاح الرسمي
عندما اكتشفنا هذه الثغرة الأمنية ، أبلغنا فريق Aptos الرسمي بها ، وقاموا بسرعة بإصلاحها. يمكنك الرجوع إلى الشكل أدناه للحصول على لقطة شاشة للإصلاح.
رابط الرمز ذي الصلة أدناه: