01.guessNumber
2023-09-14 13:43:08 # 22.MetaTrustCTF2023

guessNumber

有幸参与本次出题,本题题解如下

描述

  • 考点难度:中等偏高
  • 考点方向:内联汇编,CREATE2,假地址,预编译合约
  • 知识点
    • CREATE2:常规
    • 内联汇编:零值槽位:内存0x60~0x80;immutable变量的初始化赋值方式,本题是,内存0x80~0xa0、内存0xa0~0xc0和内存0xc0~0xe0
    • 假地址:土狗合约常用来欺骗用户,看似mytoken变量是合约A,但不是new出来的,传入的是合约B
    • 预编译合约:0x0000000000000000000000000000000000000002每个节点都预编译了它,任何值传进去都是做sha2-256返回bytes32
  • 题目场景描述:这是一个有趣的猜数游戏,如果你成功猜测到4个正确的值,那么你可以得到我们100 token的奖励

  • 题目部署方式

    1. 部署合约B(注意B合约不要给出来)
    2. 合约B地址作为参数,部署合约GuessGame
    3. 调用captureTheFalg()返回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
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
// 将这个代码给玩家
pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract A{
function number() pure external returns(uint256){
return 10;
}
}

contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender,100);
}

}

contract GuessGame {
uint256 private immutable random01;
uint256 private immutable random02;
uint256 private immutable random03;
A private immutable random04;
MyToken private immutable mytoken;

constructor(A _a) public {
mytoken = new MyToken();

random01 = uint160(msg.sender);
random02 = uint256(keccak256(address(new A()).code));
random03 = block.timestamp;
random04 = _a;
pureFunc();
}

function pureFunc() pure internal {
assembly{
mstore(0x80,1)
mstore(0xa0,2)
mstore(0xc0,32)
}
}

function guess(uint256 _random01, uint256 _random02, uint256 _random03, uint256 _random04) external payable returns(bool){

if(msg.value > 100 ether){
// 100 eth! you are VIP!
}else{
uint256[] memory arr;
uint256 money = msg.value;
assembly{
mstore(_random01, money)
}
require(random01 == arr.length,"wrong number01");
}

uint256 y = ( uint160(address(msg.sender)) + random01 + random02 + random03 + _random02) & 0xff;
require(random02 == y,"wrong number02");

require(uint160(_random03) < uint160(0x0000000000fFff8545DcFcb03fCB875F56bedDc4));
(,bytes memory data) = address(uint160(_random03)).staticcall("Fallbacker()");
require(random03 == data.length,"wrong number03");

require(random04.number() == _random04, "wrong number04");

mytoken.transfer(msg.sender,100);
payable(msg.sender).transfer(address(this).balance);

return true;
}

function captureTheFalg() external view returns(bool){
return mytoken.balanceOf(address(this)) == 0;
}

}
1
2
3
4
5
6
// 这个合约代码不要给玩家!!!
pragma solidity 0.8.21;

contract B{
uint256 public number = 11;
}

题解

假设我们用0x5B38Da6a701c568545dCfcB03FcB875f56beddC4用户进行解题

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
pragma solidity 0.8.21;

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract A{
function number() pure external returns(uint256){
return 10;
}
}

contract MyToken is ERC20 {
constructor() ERC20("MyToken", "MTK") {
_mint(msg.sender,100);
}

}

contract GuessGame {
uint256 private immutable random01;
uint256 private immutable random02;
uint256 private immutable random03;
A private immutable random04;
MyToken private immutable mytoken;

constructor(A _a) public {
mytoken = new MyToken();

random01 = uint160(msg.sender);
random02 = uint256(keccak256(address(new A()).code));
random03 = block.timestamp;
random04 = _a; // 不要输入A的合约的地址,输入B合约的地址
pureFunc();
}

function pureFunc() pure internal {
assembly{
// 1,2,32才是实际的random01、random02、random03的值
mstore(0x80,1)
mstore(0xa0,2)
mstore(0xc0,32)
}
}

function guess(uint256 _random01, uint256 _random02, uint256 _random03, uint256 _random04) external payable returns(bool){
if(msg.value > 100 ether){
// 100 eth! you are VIP!
}else{
// 零槽位
// _random01 = 0x60 = 96 && msg.value = 1 wei
uint256[] memory arr;
uint256 money = msg.value;
assembly{
mstore(_random01, money)
}
require(random01 == arr.length,"wrong number01");
}

// CREATE2
// C4 + 1 + 2 + 32 + ? = 2
// msg.sender = 0x5B38Da6a701c568545dCfcB03FcB875f56beddC4
// 231 + ? = 2 ==> 0xE7(231) + ? = 0x02(2) ==> ?=27
// ==> _random02=27
// 玩家需要自行计算自己的_random02,27是举个例子
uint256 y = ( uint160(address(msg.sender)) + random01 + random02 + random03 + _random02) & 0xff;
require(random02 == y,"wrong number02");

// 似乎想用CREATE2爆破?这难度非常高,爆破要非常久
// 不不不,这里的考点不是CREATE2而是precompile contract
// _random03 = 0x0000000000000000000000000000000000000002 sha2-256 input: any output: bytes32
require(uint160(_random03) < uint160(0x0000000000fFff8545DcFcb03fCB875F56bedDc4));
(,bytes memory data) = address(uint160(_random03)).staticcall("Fallbacker()");
require(random03 == data.length,"wrong number03");

// random 并不是new 出来的。B合约不verify,反编译可以得到number()为11,或者直接获取number()
// _random04 = 11
require(random04.number() == _random04, "wrong number04");

mytoken.transfer(msg.sender,100);
payable(msg.sender).transfer(address(this).balance);

return true;
}

function captureTheFalg() external view returns(bool){
return mytoken.balanceOf(address(this)) == 0;
}

}
1
2
3
4
5
6
// 11才是真正的random04的值
pragma solidity 0.8.21;

contract B{
uint256 public number = 11;
}